What's the best way to do its own View?

Hello

As EZGui classes don’t work (yet) with layouts,, I wanted to do my own View which would be composed of a Slider and a NumberBox.

My first instinct was to subclass View

MySlider : View {
	var slider;
	var numberBox;

	*new {
		^super.new.init();
	}

	init {
		numberBox = NumberBox(this, Rect(0,0, 60, 60));
		slider = Slider(this, Rect(60, 0, 200, 60));
		this.layout = HLayout([numberBox, stretch: 1], [slider, stretch: 5]);
		slider.action = {
			numberBox.value = slider.value;
		};
		numberBox.action = {
			slider.value = numberBox.value;
		};
	}
}

The slider and numberBox are synced.

But of course, other elements should register to MySlider value changes with something like

(
~window = Window();
~slider = MySlider();
~window.layout = HLayout(~slider);
~slider.action = { |slider|
	slider.value.postln;
};
~window.front;
)

But I haven’t done the plumbing between the slider/numberbox in MySlider and the window.
How do I propagate the value to MySlider listeners ?

*** EDIT ***

I did that, but is it the most elegant way ?

MySlider : View {
	var <>action;
	var slider;
	var numberBox;
	var <value;

	*new {
		^super.new.init();
	}

	init {
		numberBox = NumberBox(this, Rect(0,0, 60, 60));
		slider = Slider(this, Rect(60, 0, 200, 60));
		this.layout = HLayout([numberBox, stretch: 1], [slider, stretch: 5]);
		slider.action = {
			this.value = slider.value;
			this.doAction();
		};
		numberBox.action = {
			this.value = numberBox.value;
			this.doAction();
		};
	}

	value_ { |v|
		numberBox.value = v;
		slider.value = v;
		value = v;
	}

	doAction {
		this.action.value(this);
	}
}

Thanks !

Geoffroy

I’d suggest using layout constraints rather than explicit sizes. Also, you don’t need to explicitly assign the parent if you’re adding things to a layout.

Here’s a quick attempt at factoring this in a more clean way - also, using the Connection quark for binding values! Hope the gives some ideas:

(
var view, numberBox, slider, value;

value = NumericControlValue(spec:ControlSpec(0, 100, default:30));

// LAYOUT
numberBox = NumberBox();
slider = Slider()
	.orientation_(\horizontal)
	.maxHeight_(numberBox.bounds.height);
view = View().layout_(HLayout(
	[numberBox, stretch: 1], 
	[slider, stretch: 5]
));
view.front;

// BINDING
ConnectionList.newFrom([
	value.signal(\value).connectTo(numberBox.valueSlot),
	value.signal(\input).connectTo(slider.valueSlot),
	numberBox.signal(\value).connectTo(value.valueSlot),
	slider.signal(\value).connectTo(value.inputSlot)
]).freeAfter(view);

value.signal(\value).connectToUnique({
	|obj, what, value|
	"Value: %".format(value).postln;
});

)

Obviously var view would be your this in a class context. It’s a tough call as to whether value is a member of the class or not - normally, you wouldn’t want view components to store values. For cases like this, I’ve sometimes added a cv getter/setter, so I can set up some NumericControlValue's separately, and then bind them to my GUI such that components like NumberBox have access to the value, and Slider can access the normalized 0..1 version of the value. In my above example, the // Binding bit would go in the cv_ setter (along with disconnecting from an old cv if one was already bound)

My workaround for EZGui and layouts is to pass individual views directly to layout. Of course this is not a proper “fix”, and I’m not sure if it would be useable to make a new class, but thought I’d share in case it’s useful. (It certainly has quirks, e.g. problems \horz vs \vert layout seems to be overridden by the bounds originally passed to the EGgui obj…)

(
v = View(bounds: 200@200).front;
k = EZSlider(v, 180@20, "amp (dB):", \db, layout: \horz);
v.layout_(
	HLayout(
		k.labelView,
		k.sliderView.maxHeight_(40),
		k.numberView.maxWidth_(40)
	)
);
)
1 Like

This is my solution:

I generally avoid subclassing View. We have SCViewHolder to implement a “has-a” relationship (instead of subclassing, “is-a”). The Adapter design pattern promotes looser coupling, probably better in the long run.

The SCViewHolder is holding a View, and this View’s layout contains the number box and slider.

hjh

Thanks a lot @scztt, @jamshark70 and @MarcinP. Your solutions are very different, all valid, a lot to learn ! Have a great day !

For the record, I’ve used @MarcinP’s solution (which discovered myself too) with even more complex/compound GUI objects, including Stethoscope. If you’re doing a more complex app, the (extra) trick is to simply create views that you don’t show immediately, although you can definitely pass them as references to “old school” GUI objects that get their parent view in the constructor, and also add a (new style) layout manager to this empty view to to make scaling work properly. For example, with Stethoscope I do something like

(w = Window.new("scope in app", Rect(20, 20, 600, 700));
i = View().layout_(VLayout());
b = Button().states_([["left stuff", Color.grey, Color.white]]);
w.layout = HLayout(b, i);
c = Stethoscope.new(s, view:i);
w.onClose = { c.free };
w.front;)

For amusement purposes, you can “steal” a subset of PdefGui’s buttons this way (i.e. by changing their parent view after they get drawn), and show them somewhere else (without even showing the whole PdefGui in any visible view), although I only did that as a quick hack, after which I wrote my own player-buttons object.

1 Like

Thank you ! That’s good to know, sure I will use that trick in the future.

I see I can’t edit that post anymore… I wanted to post some screenshots too to show the difference between that code

and if you omit the extra VLayout on the inner view i even though that’s used just to contain the Stethoscope, i.e. just

i = View() // also needs .layout_(VLayout()) otherwise...

The Stethoscope doesn’t scale when you resize the latter window, but it does with the former.