aboutsummaryrefslogtreecommitdiffstats
path: root/layout/border.go
blob: c60e602340e8763da09392b63d31272223c9fc00 (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
package layout

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"

	"github.com/faiface/gui"
)

// NewBorder creates a layout with a margin, border, and padding drawn around itself.
func NewBorder(env gui.Env, opts ...BorderOption) gui.Env {
	o := evalBorderOptions(opts...)

	events := make(chan gui.Event)                       // to child
	draws := make(chan func(draw.Image) image.Rectangle) // from child

	go func(events chan<- gui.Event, draws <-chan func(draw.Image) image.Rectangle) {
		defer close(env.Draw())
		defer close(events)

		redraw := func(area image.Rectangle) {
			env.Draw() <- drawBorder(o)
			fmt.Println("area, content:", area, borderContentArea(area, o))
			events <- gui.Resize{borderContentArea(area, o)} // translate and forward to child
		}

		// Forward first resize event to child and draw border
		event := (<-env.Events()).(gui.Resize) // first event guaranteed to be Resize
		area := event.Rectangle
		go redraw(area)

		for {
			select {
			case event := <-env.Events(): // event from parent
				switch event := event.(type) {
				case gui.Resize:
					area = event.Rectangle
					go redraw(area)
				default:
					go func() { events <- event }() // forward to child
				}
			case f, ok := <-draws: // draw call from child
				if !ok {
					return
				}
				env.Draw() <- drawSubImage(f, borderContentArea(area, o))
			}
		}
	}(events, draws)

	return NewRegion(borderEnv{events, draws}, color.Transparent, Full())
}

// BorderContentArea returns the drawable area inside the margin,
// border, and padding of a border layout, given the overall area
// of the layout and the options.
func borderContentArea(area image.Rectangle, o borderOptions) image.Rectangle {
	top := o.margin.top + o.border.top + o.padding.top
	right := o.margin.right + o.border.right + o.padding.right
	bottom := o.margin.bottom + o.border.bottom + o.padding.bottom
	left := o.margin.left + o.border.left + o.padding.left
	return Inset(top, right, bottom, left)(area)
}

func drawBorder(o borderOptions) func(draw.Image) image.Rectangle {
	return func(img draw.Image) image.Rectangle {
		r := Inset(o.margin.top, o.margin.right, o.margin.bottom, o.margin.left)(img.Bounds())
		mask := image.NewAlpha(image.Rect(0, 0, r.Dx(), r.Dy()))
		draw.Draw(mask, mask.Bounds(), image.Opaque, image.ZP, draw.Src)                                                                           // mask border
		draw.Draw(mask, Inset(o.border.top, o.border.right, o.border.bottom, o.border.left)(mask.Bounds()), image.Transparent, image.ZP, draw.Src) // unmask inside border
		draw.DrawMask(img, r, &image.Uniform{o.Color}, image.ZP, mask, image.ZP, draw.Over)
		fmt.Println("border:", r)
		return r
	}
}

// BorderOption is a functional option for the NewBorder constructor.
type BorderOption func(o *borderOptions)

type borderOptions struct {
	margin, border, padding sides
	color.Color
}

type sides struct{ top, right, bottom, left int }

// Margin option sets the margin size, in pixels, on all sides.
func Margin(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("margin %d < 0", px))
	}
	return func(o *borderOptions) { o.margin = sides{px, px, px, px} }
}

// MarginTop option sets the top margin size, in pixels.
func MarginTop(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("margin %d < 0", px))
	}
	return func(o *borderOptions) { o.margin.top = px }
}

// MarginRight option sets the right margin size, in pixels.
func MarginRight(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("margin %d < 0", px))
	}
	return func(o *borderOptions) { o.margin.right = px }
}

// MarginBottom option sets the bottom margin size, in pixels.
func MarginBottom(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("margin %d < 0", px))
	}
	return func(o *borderOptions) { o.margin.bottom = px }
}

// MarginLeft option sets the left margin size, in pixels.
func MarginLeft(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("margin %d < 0", px))
	}
	return func(o *borderOptions) { o.margin.left = px }
}

// Border option sets the border thickness (in pixels) and color on all sides.
func Border(thick int, c color.Color) BorderOption {
	if thick < 0 {
		panic(fmt.Sprintf("border thickness %d < 0", thick))
	}
	return func(o *borderOptions) {
		o.border = sides{thick, thick, thick, thick}
		o.Color = c
	}
}

// BorderColor sets the color of the border.
func BorderColor(c color.Color) BorderOption {
	return func(o *borderOptions) { o.Color = c }
}

// BorderTop sets the top border thickness, in pixels.
func BorderTop(thick int) BorderOption {
	if thick < 0 {
		panic(fmt.Sprintf("border thickness %d < 0", thick))
	}
	return func(o *borderOptions) { o.border.top = thick }
}

// BorderRight sets the right border thickness, in pixels.
func BorderRight(thick int) BorderOption {
	if thick < 0 {
		panic(fmt.Sprintf("border thickness %d < 0", thick))
	}
	return func(o *borderOptions) { o.border.right = thick }
}

// BorderBottom sets the bottom border thickness, in pixels.
func BorderBottom(thick int) BorderOption {
	if thick < 0 {
		panic(fmt.Sprintf("border thickness %d < 0", thick))
	}
	return func(o *borderOptions) { o.border.bottom = thick }
}

// BorderLeft sets the left border thickness, in pixels.
func BorderLeft(thick int) BorderOption {
	if thick < 0 {
		panic(fmt.Sprintf("border thickness %d < 0", thick))
	}
	return func(o *borderOptions) { o.border.left = thick }
}

// Padding option sets the padding size, in pixels, on all sides.
func Padding(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("padding %d < 0", px))
	}
	return func(o *borderOptions) { o.padding = sides{px, px, px, px} }
}

// PaddingTop option sets the top padding size, in pixels.
func PaddingTop(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("padding %d < 0", px))
	}
	return func(o *borderOptions) { o.padding.top = px }
}

// PaddingRight option sets the right padding size, in pixels.
func PaddingRight(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("padding %d < 0", px))
	}
	return func(o *borderOptions) { o.padding.right = px }
}

// PaddingBottom option sets the bottom padding size, in pixels.
func PaddingBottom(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("padding %d < 0", px))
	}
	return func(o *borderOptions) { o.padding.bottom = px }
}

// PaddingLeft option sets the left padding size, in pixels.
func PaddingLeft(px int) BorderOption {
	if px < 0 {
		panic(fmt.Sprintf("padding %d < 0", px))
	}
	return func(o *borderOptions) { o.padding.left = px }
}

func evalBorderOptions(opts ...BorderOption) borderOptions {
	o := borderOptions{}
	for _, opt := range opts {
		opt(&o)
	}
	return o
}

type borderEnv struct {
	events <-chan gui.Event
	draw   chan<- func(draw.Image) image.Rectangle
}

// Events implements the Env interface.
func (b borderEnv) Events() <-chan gui.Event { return b.events }

// Draw implements the Env interface.
func (b borderEnv) Draw() chan<- func(draw.Image) image.Rectangle { return b.draw }