aboutsummaryrefslogtreecommitdiffstats
path: root/layout.go
diff options
context:
space:
mode:
Diffstat (limited to 'layout.go')
-rw-r--r--layout.go106
1 files changed, 106 insertions, 0 deletions
diff --git a/layout.go b/layout.go
new file mode 100644
index 0000000..f0cce7f
--- /dev/null
+++ b/layout.go
@@ -0,0 +1,106 @@
+package gui
+
+import "image"
+
+// Scheme represents the appearance and behavior of a layout.
+type Scheme interface {
+ // The Partitioner represents the way to divide space among the children.
+ // It takes a parameter of how much space is available, and returns a space for each child.
+ Partitioner
+
+ // The Intercepter transforms an Env channel to another.
+ // This way the Layout can emit its own Events, re-emit previous ones,
+ // or even stop an event from propagating, think win.MoScroll.
+ // It can be a no-op.
+ Intercepter
+}
+
+// Partitioner divides a large Rectangle into several smaller sub-Rectangles.
+type Partitioner interface {
+ Partition(image.Rectangle) []image.Rectangle
+}
+
+// NewLayout takes an array of uninitialized `child' Envs and multiplexes the `parent' Env
+// according to the provided Scheme. The children receive the same events from the parent
+// aside from Resize, and their draw functions get redirected to the parent Env.
+//
+// The Scheme determines the look and behavior of the Layout. Resize events for each child
+// are modified according to the Partitioner. Other Events and draw functions can be modified
+// by the Intercepter.
+//
+// Killing the returned layout kills all of the children.
+func NewLayout(parent Env, children []*Env, scheme Scheme) Killable {
+ env := newEnv(parent, send, send, func() {})
+
+ // Capture Resize Events to be sent to the Partitioner.
+ resizeSniffer, resizes := newSniffer(env, func(e Event) (r image.Rectangle, ok bool) {
+ if resize, ok := e.(Resize); ok {
+ return resize.Rectangle, true
+ }
+ return image.Rectangle{}, false
+ })
+
+ intercepter := scheme.Intercept(resizeSniffer)
+
+ mux := NewMux(intercepter)
+ muxEnvs := make([]Env, len(children))
+ resizers := make([]Env, len(children))
+ resizerChans := make([]chan image.Rectangle, len(children))
+ for i, child := range children {
+ muxEnvs[i] = mux.MakeEnv()
+ resizerChans[i] = make(chan image.Rectangle)
+ resizers[i] = newResizer(muxEnvs[i], resizerChans[i])
+ *child = resizers[i]
+ }
+
+ go func() {
+ for rect := range resizes {
+ for i, r := range scheme.Partition(rect) {
+ resizerChans[i] <- r
+ }
+ }
+ for _, c := range resizerChans {
+ close(c)
+ }
+ }()
+
+ return env
+}
+
+// newSniffer makes an Env that forwards all Events and Draws unchanged, but emits a signal
+// whenever a certain event is encountered. It returns the new Env and the signal channel.
+//
+// Each Event from parent is passed to sniff(). If sniff() accepts the Event, the return
+// value of sniff() is sent to the signal channel.
+//
+// signal is closed automatically when the sniffer dies.
+func newSniffer[T any](parent Env, sniff func(Event) (v T, ok bool)) (Env, <-chan T) {
+ signal := make(chan T)
+ env := newEnv(parent,
+ func(e Event, c chan<- Event) {
+ c <- e
+ if sig, ok := sniff(e); ok {
+ signal <- sig
+ }
+ },
+ send, // forward draw functions un-modified
+ func() {
+ close(signal)
+ })
+ return env, signal
+}
+
+// newResizer makes an Env that replaces the values all Resize events with
+// Rectangles received from the resize channel.
+// It waits for a Rectangle from resize each time a Resize Event is received from parent.
+func newResizer(parent Env, resize <-chan image.Rectangle) Env {
+ return newEnv(parent,
+ func(e Event, c chan<- Event) {
+ if _, ok := e.(Resize); ok {
+ e = Resize{<-resize}
+ }
+ c <- e
+ },
+ send, // forward draw functions un-modified
+ func() {})
+}