Skip to content

Instantly share code, notes, and snippets.

@diamondburned
Last active April 4, 2020 00:37
Show Gist options
  • Save diamondburned/64672a0876e6e5127e0daf41c796978f to your computer and use it in GitHub Desktop.
Save diamondburned/64672a0876e6e5127e0daf41c796978f to your computer and use it in GitHub Desktop.
A better Gtk
package main
import (
"fmt"
"math/rand"
"time"
)
type IncrementThing struct {
gtk.Box
// local states
counter int
time time.Time
quote string
Label gtk.Label
Button gtk.Button
}
func NewIncrementThing() *IncrementThing {
t := &IncrementThing{}
t.SetSpacing(10)
t.SetHAlign(gtk.AlignCenter)
t.Add(t.Label)
t.Add(t.Button)
t.Label.SetLabelCallback(t.String) // or t.Label.SetLabel(&t.ActualString)
t.Button.SetLabel("inc!")
t.Button.SetImage("add")
t.Button.SetAlwaysShowImage(true)
t.Button.Connect("clicked", t.Increment)
go func() {
for time := range time.Tick(5 * time.Second) {
t.time = time
// Call update to request Gtk to diff the structure again. This is
// synchronous, meaning there's no need to atomically access or
// write t.time.
t.Update()
}
}()
return t
}
func (t *IncrementThing) String() string {
// Demonstrate asynchronous update of an individual widget:
go t.updateQuote()
return fmt.Sprintf("Counter: %d, Time: %v, Quote: %s", t.counter, t.time, t.quote)
}
// updateQuote triggers an update at the end.
func (t *IncrementThing) updateQuote() {
// Emulate load:
time.Sleep(time.Duration(1+rand.Intn(5)) * time.Second) // 1~6s
// Set a dumb quote:
t.quote = fmt.Sprintf("harr harr quote lol (updated at %v)", time.Now())
// You can cheat here by only request to update the label. String() is
// mounted to label, so there's no need to traverse anything else.
t.Label.Update()
}
func (t *IncrementThing) Increment() gtk.UpdateAction {
t.counter++
// Action draw behavior:
// 1. Gtk traverses through the nodes added into the container (this is
// already possible, as they already do that for ShowAll)
// 2. Gtk detects callbacks set with (*Object).Set*Callback, as these are
// not static and can change
// 3. Gtk compares the returns of the callbacks with the currently set
// properties in the widget itself. If it's different, update.
//
// This is a very reasonable approach, because widgets that have callbacks
// set to are typically very primitive. Their properties are typically only
// texts. This means that those callbacks are typically reasonable to run as
// well.
//
// Another thing worth mentioning would be the use of SetKey, which prevents
// Gtk from traversing too deep and updating everything.
// 1. There won't be a case of shifting slices causing Gtk to redraw
// everything, as the new widget would have a different ID, therefore Gtk
// shouldn't bother going down and diffing.
// - In this case, Gtk will just destroy the entire old widget.
// - Without this, Gtk would try and update every single component.
// 2. Getting rid of removed widgets would be much easier, as there
// wouldn't be a need to traverse the entire tree to know.
// https://reactjs.org/docs/reconciliation.html#keys
return gtk.ActionDraw
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment