~eliasnaur/gio

2 2

communicating widgets

Details
Message ID
<20200203142128.GA58084@larrymbp14.local>
DKIM signature
missing
Download raw message
Any thoughts or guidance on how widgets should communicate, in a
general and uncoupled way?

I have some widgets that I'd like it to be *possible* for them to
communicate, but not *required*.  So for example having each have a
reference to the other(s) and calling methods on them is suboptimal.

I guess an obvious thought is channel(s) of some kind, or even an
event bus.  And in fact an event bus is exactly what I've implemented
for some communication.

About the only reason I haven't done it for more/all of the
communication is that the thought of uncoupled action-at-a-distance
also seems suboptimal.  (E.g. thinking "I throw this event onto the
bus and whatever happens, happens" just seems weird to me.)  But maybe
that's wrong and/or I just need to get over it.

I've done almost no Qt programming, but their idea of "signals and
slots" has always struck me as a reasonable way to go.  But that also
has the action-at-a-distance thing that bugs me.  (And again maybe I
just need to get over that.)

Browsers have a very well defined event framework, with bubbling and
cancellation and so on and so forth.  That seems *thorough* but also
*overkill* for most purposes.  And also the DOM has a very
well-defined parent/child relationship and a well defined interface to
that relationship, which, again, thorough but overkill.

At the end of the day it's all just Go, of course.  I guess if you
want your widgets to know about their parents, you just write that
code, and maybe define Parent and Child interfaces that widgets
can/must implement.

Anyway I thought I'd see how other people / apps / frameworks do it.
And I wondered if maybe "immediate mode" GUIs have a pattern that I
don't know about.

Thanks!

-- Larry
Details
Message ID
<C0CMWEUM7LEN.10CBAPI5U0OXO@toolbox>
In-Reply-To
<20200203142128.GA58084@larrymbp14.local> (view parent)
DKIM signature
pass
Download raw message
On Mon Feb 3, 2020 at 09:21, Larry Clapp wrote:
> Any thoughts or guidance on how widgets should communicate, in a
> general and uncoupled way?
>
> I have some widgets that I'd like it to be *possible* for them to
> communicate, but not *required*.  So for example having each have a
> reference to the other(s) and calling methods on them is suboptimal.
>
> I guess an obvious thought is channel(s) of some kind, or even an
> event bus.  And in fact an event bus is exactly what I've implemented
> for some communication.
>
> About the only reason I haven't done it for more/all of the
> communication is that the thought of uncoupled action-at-a-distance
> also seems suboptimal.  (E.g. thinking "I throw this event onto the
> bus and whatever happens, happens" just seems weird to me.)  But maybe
> that's wrong and/or I just need to get over it.
>
> I've done almost no Qt programming, but their idea of "signals and
> slots" has always struck me as a reasonable way to go.  But that also
> has the action-at-a-distance thing that bugs me.  (And again maybe I
> just need to get over that.)
>
> Browsers have a very well defined event framework, with bubbling and
> cancellation and so on and so forth.  That seems *thorough* but also
> *overkill* for most purposes.  And also the DOM has a very
> well-defined parent/child relationship and a well defined interface to
> that relationship, which, again, thorough but overkill.
>
> At the end of the day it's all just Go, of course.  I guess if you
> want your widgets to know about their parents, you just write that
> code, and maybe define Parent and Child interfaces that widgets
> can/must implement.
>
> Anyway I thought I'd see how other people / apps / frameworks do it.
> And I wondered if maybe "immediate mode" GUIs have a pattern that I
> don't know about.
>

Great question. I don't have a good answer.

Much of Gio is "from first principles", and I haven't looked much at
other immediate mode libraries let alone complete programs. So there's
a good chance you'll find some great patterns in e.g. Dear ImGui or
similar.

My favorite part of Gio event handling is the absence of callbacks,
and that you can respond to events when the program is prepared for them.

I've tried to generalize that concept to app-level events in
[Scatter](https://scatter.im), so even though action is still at a
distance, it's not (as) spooky. See

	https://git.sr.ht/~eliasnaur/scatter/tree/master/cmd/scatter/ui.go#L1196

where the high-level events are handled.

-- elias
Gregory Pomerantz
Details
Message ID
<02198650-88aa-f70c-a337-855368a13c4f@wow.st>
In-Reply-To
<20200203142128.GA58084@larrymbp14.local> (view parent)
DKIM signature
pass
Download raw message
On 2/3/20 9:21 AM, Larry Clapp wrote:

> Any thoughts or guidance on how widgets should communicate, in a
> general and uncoupled way?

With an immediate-mode GUI, your data structures depend entirely on what 
type of application you are making and your own decisions about how you 
want to do things, and are not dependent on decisions made by the 
designers of your UI library. My apps tend to just use a shared global 
state. I use channels for truly asynchronous events (such as Bluetooth, 
network traffic, etc) and put those into a select statement alongside 
the UI event channel. Since this all runs inside a single goroutine, you 
can be sure your application state is not getting updated behind the 
scenes while you are laying out widgets and rendering them. So widgets 
can safely communicate by sharing data :)

An immediate mode GUI is not concurrent by nature and can be implemented 
in a single thread without any locking or synchronization. You control 
exactly when and where your state is read or written. So for example you 
can set the value of a 'clicked' variable based on the state of ButtonA 
(by calling ButtonA.Clicked()), and use that result later to control the 
layout of your other widgets.

My apps will typically have an eventloop() function, called from main() 
as a separate goroutine, which looks something like this:

   w := ...
   th := ...
   gtx := ...

   // application state
   clicked := false
   connected := false

   for {
     select {
       case e := <-bluetooth.Events():
         ... // update application state
         w.Invalidate()
       case e := <-w.Events():
         ... // read and write application state
     }
   }

Personally I find these function-scoped variables less "spooky" than 
callbacks, and much less obscure than data encapsulated all over the 
place inside separate widget structs, as you might have in a more 
object-oriented design.

Because the shared state is function-scoped, you know that nothing 
outside of the eventloop() function can change it. You can further 
encapsulate data by using structs or closures.