New proposal: rather than have methods do things, have methods create requests to do things.
At the top level, there is a channel ui.Do
which accepts the results of setter methods:
ui.Do <- label.SetText("Hello")
ui.Do <- checkbox.SetChecked(true)
Getters, however, are a bit more work. Not only do we need to send a request, we need to get something back from it. So getters will require three lines:
req := checkbox.Checked()
ui.Do <- req
checked := (<-req.Result).(bool) // <-req.BoolResult?
This is work; we could probably have a wrapper function:
checked := ui.GetBool(checkbox.Checked())
To get rid of the problem caused by atomicity of event handlers, the event handlers will receive its own private ui.Do
equivalent:
button.Clicked = func(c ui.Doer) { // type Doer chan<- [something]
c <- button2.Disable()
}
While in an event handler, requests on ui.Do
will stall.
Unfortunately, it's still possible for things outside the library to screw with us: a timer can fire and start an interaction, for instance. There would need to be some resiliency.
We can solve this for the timer case by providing our own timer event wrapper.
Another possibility is to provide a means to connect foreign events. For instance, something like
type ForeignEvent interface {
Chan() interface{} // must be a receivable channel of any type
Stop()
}
func NewForeignEvent(channel interface{}, stop func()) ForeignEvent
// prototype for foreign event handlers:
func ForeignEventHandler(c ui.Doer, which ForeignEvent, v interface{})
and then you just wrap your foreign event and let package ui take care of dispatch and cancellation.
This design unfortunately does not consider dialog box modality, which is still a major issue on Windows, because the ocmmon dialogs are still code-modal... perhaps something like
msgreq := ui.MsgBox(w, "This is a message box!",
"The w parameter is just a hint to say which window the box should appear on top of. " +
"Some platforms may not respect it.")
ui.Do <- msgreq
<-msgreq.Result
and then all sends on ui.Do are ignored until the dialog is dismissed, just like with event handlers.
The getters feel too clumsy IMO, messing with
interface{}
and losing types for just this is not good, and the Do channel being exposed is very dangerous.I think a design like this would solve some of the problems:
We have a ui.Request struct
Let's make
ui.Do()
a function, and name the channel asui.tasks
, which is not exposed. Basicallyui.Do()
isSo the Setter code just needs to be like this
It's the same, but our channel is protected now :)
What's the point of Completed in my code? I think for Getters they don't modify anything, doing no harm, so it's typically unnecessary to make requests. But in case someone wants synchronous, there's a good way. Do() returns a Completed channel, it gets updated when the main thread completes the operation, so just do this if it's required:
It must return the right value here.
What do you think?