aboutsummaryrefslogtreecommitdiffstats
path: root/layout.go
blob: f0cce7f867db387e2e995cc0bfb3cc3f49c6ab28 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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() {})
}