GUI equivalent of s.sync

When setting GUI elements I often wonder if there is a method equivalent to s.sync for GUI elements - something that reports back to the language saying ‘I am done’. I guess I could wrap every single element of the greater routine in it’s own routine, but would be nice to keep everything in one big routine and be able to know when a part of the routine is done doing whatever I asked it to do, ie. close a window, open a new window, create a bunch of buttons etc.

There isn’t a universal approach, no. Perhaps you could give a little example of what you are trying to achieve?

Supercollider’s gui somewhat models Qts (its underlying library). In Qt you have signals and slots, signals are methods that call all the connected slots when evaluated - and slots just do stuff -, but in supercollider you pass a function to be evaluated on a signal, e.g, Node’s onFree, or View’s many ‘onX’ methods.

synth.onFree({ buffer.free });

You can add listeners to any lang-side object:

https://doc.sccode.org/Classes/Object.html#-addDependant

2 Likes

GUI calls are synchronous! In the following code, the console message is guaranteed to be printed after the window has been created:

w = Window();
w.front();
"Window is visible".postln;

However, GUI calls have to be executed on the AppClock. For example, the following will throw an error:

(
// run the code in a Routine
{
	w = Window();
	w.front();
	"Window is visible".postln;
}.fork;
)

ERROR: Qt: You can not use this Qt functionality in the current thread. Try scheduling on AppClock instead. ERROR: Primitive '_QWindow_AvailableGeometry' failed.

Instead, you have to schedule the code on the AppClock. The easiest way is to use defer:

(
// run the code in a Routine
{
    // inside the Routine, defer to the AppClock
	{
		w = Window();
		w.front();
		"Window is visible".postln;
	}.defer;
}.fork;
)

Although you can run any code on the AppClock, you should only do GUI-related things. If you want to execute some non-GUI code from within the AppClock, just start another Routine.

(
// run the code in a Routine
{
    // defer to AppClock
	{
		w = Window();
		w.front();
        // defer to TempoClock
		{
			"Window is visible".postln;
			// do some other non-GUI stuff
		}.fork;
	}.defer;
}.fork;
)
1 Like

Another alternative is to use…

Which will give you an excellent implementation of a promise.
This might be exactly what you are looking for!

There’s a little discussion here: Proposal: Function.await

1 Like

Thank you all for contributing. I have done a lot of testing the last couple of days. I isolated my main challenge: SC does not seem to wait for an update of the GUI before carrying on another update of the GUI which causes problems if the second update is dependent on the first update. @droptableuser - could this be fixed using addDependent and if so, what is the syntax in the above case?

Here is simple code outlining the problem:

// 2 functions to create GUIs of different orientations
(
~gui = {
	var v = View().front.alwaysOnTop_(true).background_(Color.green);
	v.layout_(VLayout(*2.collect{HLayout(*4.collect{Button().minSize_(30@20)})}));
};

~gui2 = {
	var v = View().front.alwaysOnTop_(true).background_(Color.green);
	v.layout_(VLayout(*4.collect{Button().minSize_(30@20)}));
};
)

// create 4 GUIs
(
w !? {w.close};
~g = 3.collect{~gui.()};
~g2 = ~gui2.();
w = Window().front.alwaysOnTop_(true);
z = View(w).front.alwaysOnTop_(true).background_(Color.red);
z.layout_(HLayout(VLayout(*~g), ~g2));
)

// Resize GUIs to smallest size

~g.collect{|n|n.fixedSize_(n.minSizeHint)}

// Resize outer view to smallest size

z.bounds = z.bounds.size_(z.sizeHint)

// This is the desired appearance
// Now try to do evertyhing in one go inside a Routine:
(
Routine{
	w !? {w.close};
	~g = 3.collect{~gui.()};
	~g2 = ~gui2.();
	w = Window().front.alwaysOnTop_(true);
	z = View(w).front.alwaysOnTop_(true).background_(Color.red);
	z.layout_(HLayout(VLayout(*~g), ~g2));
	~g.collect{|n|n.fixedSize_(n.minSizeHint)};
	z.bounds = z.bounds.size_(z.sizeHint)
}.play(AppClock)
)

// not the same result

z.bounds = z.bounds.size_(z.sizeHint);

// now the result is the same as before

Interesting…

so if you add even a small delay it seems to work, suggesting, the sizeHint is not updated until everything is fitted onto the screen.

(
Routine{
	w !? {w.close};
	~g = 3.collect{~gui.()};
	~g2 = ~gui2.();
	w = Window().front.alwaysOnTop_(true);
	z = View(w).front.alwaysOnTop_(true).background_(Color.red);
	z.layout_(HLayout(VLayout(*~g), ~g2));
	~g.collect{|n|n.fixedSize_(n.minSizeHint)};
	
	0.1.wait;
	z.resizeToHint(); // fyi, there is a method for this.

}.play(AppClock)
)

This makes sense as its supposed to give you a hint as to what the size ought to be, not what it actually is, whereas layouts are used to specify automatic set the size. The two systems don’t quite work together, which is what you are seeing here as you are overriding the size set by the layout.

What is needed is a away to connect to and from the underlying NOTIFY method of the q_property. FYI, in QML you would write z.onSizeHintChanged.connect( z.resizeToHint ).
Supercollider actually exposes getProperty and connect, but there is no documentation for them (nor can I figure them out), and it seems like the signals visible - View.meta.methods(false, true, false) - don’t provide a ‘relayouted’. I don’t know if anyone knows a way to do this?

Ultimately, I think it might be a mistake to both use Layouts (a process for automatically arranging objects) and manually changing the size yourself. Is it really necessary in your code?

hmm - not so sure addDependent will help in this scenario since there is no notification when the window or view is ready.

since you’re executing in a routine you can add a wait before resizing
e.g.

1.wait;
z.bounds = z.bounds.size_(z.sizeHint)

(i too struggle dealing with sizing of views and windows and fluctuate between having views that always fill their container space or are hard-coded fixed-size from initialization)

@jordan - I don’t think sizeHint has anything to do with it. It happens without sizeHint too, ie. when moving views around with w.bounds = (…). The wait thing works but in my experience the wait has to be quite long to be absolutely sure nothing goes wrong. In a part of my code I had to go as high as 0.2 secs between subsequent GUI operations. If I remove wait it sometimes work, or most of the time, and sometimes it don’t. It does not feel like an optimal solution in any case. Letting Layout handle everything results in pretty bad looking GUIs IMO and also it seems counter intuitive to operate a GUI with shifting dimensions, whereas moving the GUI to a different part of the screen is ok if the appearance is the same (to me). Making dynamic GUIs in SC is kicking my ass over and over:)

nice with resizeToHint, I did not find this method anywhere in the docs…

In this case, Scott’s Connection quark can also be helpful. We need more good model-view-controller design tools, we deal with it all the time.