Last active
April 4, 2020 00:37
-
-
Save diamondburned/64672a0876e6e5127e0daf41c796978f to your computer and use it in GitHub Desktop.
A better Gtk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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