aboutsummaryrefslogtreecommitdiffstats
path: root/mux.go
blob: e2b9f0be9438280c6395b6eefdfbc6c2eb48b322 (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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package gui

import (
	"image"
	"image/draw"

	"github.com/faiface/gui/lay/strain"
)

// Mux can be used to multiplex an Env, let's call it a root Env. Mux implements a way to
// create multiple virtual Envs that all interact with the root Env. They receive the same
// events, and their draw functions and constraints get redirected to the root Env.
type Mux struct {
	eventsIns chan chan<- Event
	draw      chan<- func(draw.Image) image.Rectangle
	impose    chan<- strain.Constraint
	finish    chan<- struct{}
}

// NewMux creates a new Mux that multiplexes the given Env. It returns the Mux along
// with a master Env. The master Env is just like any other Env created by the Mux,
// except that closing the master Env closes the whole Mux and all other Envs created
// by the Mux.
func NewMux(env Env) (mux *Mux, master Env) {
	finish := make(chan struct{})
	mux = &Mux{
		make(chan chan<- Event),
		env.Draw(),
		env.Impose(),
		finish,
	}

	go func() {
		var (
			eventsIns  []chan<- Event // one per child Env
			lastResize Event          // TODO: should we block until we receive the first one from the root Env?
		)
		defer func() {
			for _, evIn := range eventsIns {
				close(evIn)
			}
			close(mux.eventsIns)
			close(mux.finish)
			env.Close()
		}()
		for {
			select {
			case e := <-env.Events():
				if resize, ok := e.(Resize); ok {
					lastResize = resize
				}
				for _, eventsIn := range eventsIns {
					eventsIn <- e
				}
			case evIn := <-mux.eventsIns: // new env created by makeEnv()
				eventsIns = append(eventsIns, evIn)
				// Make sure to always send a resize event to a new Env if we got the size already.
				if lastResize != nil {
					evIn <- lastResize
				}
			case <-finish:
				return
			}
		}
	}()

	master = mux.makeEnv(true)
	return mux, master
}

// MakeEnv creates a new virtual Env that interacts with the root Env of the Mux. Closing
// the Draw() channel of the Env will not close the Mux, or any other Env created by the Mux
// but will delete the Env from the Mux.
func (mux *Mux) MakeEnv() Env {
	return mux.makeEnv(false)
}

type muxEnv struct {
	events <-chan Event
	draw   chan<- func(draw.Image) image.Rectangle
	impose chan<- strain.Constraint
	finish chan<- struct{}
}

func (m *muxEnv) Events() <-chan Event                          { return m.events }
func (m *muxEnv) Draw() chan<- func(draw.Image) image.Rectangle { return m.draw }
func (m *muxEnv) Impose() chan<- strain.Constraint              { return m.impose }

func (m *muxEnv) Close() {
	m.finish <- *new(struct{})
	close(m.finish)
	close(m.draw)
	close(m.impose)
}

func (mux *Mux) makeEnv(master bool) Env {
	eventsOut, eventsIn := MakeEventsChan()
	draws := make(chan func(draw.Image) image.Rectangle)
	imposes := make(chan strain.Constraint)
	finish := make(chan struct{})
	env := &muxEnv{eventsOut, draws, imposes, finish}

	mux.eventsIns <- eventsIn

	go func() {
		if master {
			// Close Mux and all other child Envs when master Env is closed
			defer func() { mux.finish <- *new(struct{}) }()
		}

		// When the master Env gets closed, the Mux closes all the Events()
		// channels of all the children Envs, and it also closes the Mux's draw channel. However,
		// some child Envs of the Mux may still send some drawing commmands before they realize
		// that their Events() channel got closed.
		//
		// That is perfectly fine if their drawing commands simply get ignored. This down here
		// is a little hacky, but (I hope) perfectly fine solution to the problem.
		//
		// When the Mux's draw channel gets closed, the line marked with ! will
		// cause panic. We recover this panic, then we receive, but ignore all further draw
		// commands, correctly draining the Env until it closes itself.
		defer func() {
			if recover() != nil {
				for range draws {
				}
			}
		}()
		for {
			select {
			case d := <-draws:
				mux.draw <- d // !
			case c := <-imposes:
				mux.impose <- c
			case <-finish:
				return
			}
		}
	}()

	return env
}