-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathborder.go
More file actions
202 lines (186 loc) · 6.06 KB
/
Copy pathborder.go
File metadata and controls
202 lines (186 loc) · 6.06 KB
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
// Copyright (c) 2026 the go-widgets/toolkit authors. All rights reserved.
// Use of this source code is governed by a BSD-3-Clause license that can be
// found in the LICENSE file at the root of this repository.
package toolkit
import "github.com/go-widgets/painter"
// Border arranges up to five named regions — North, South, West, East and
// Center — the classic five-region border layout for application shells. North and
// South span the full width and take a fixed height; West and East then span the
// height that remains between them and take a fixed width; Center fills whatever
// is left. The precedence is structural, so regions may be assigned in any order
// and still lay out correctly (unlike Dock, which carves in insertion order).
//
// Any region may be nil (that edge simply contributes no band). Sizes are the
// extent along each region's own axis — NorthSize/SouthSize are heights,
// West/EastSize are widths — clamped to what the container can give (negative → 0).
//
// Border is a Widget: Draw paints every non-nil region; OnEvent routes by Bounds,
// translating into the matched region's local space.
type Border struct {
Base
North, South, East, West, Center Widget
NorthSize, SouthSize, EastSize, WestSize int
// NorthSplit/… add a draggable splitter between that edge region and the
// centre (with an optional resizable split). The app drives the drag — like Paned —
// via SplitHandleAt (which handle a point is on) and ResizeSplit (set the new
// size); OnResize fires after each resize.
NorthSplit, SouthSplit, EastSplit, WestSplit bool
OnResize func(side DockSide, size int)
handles []splitHandle
// resizing/resizeSide track an in-flight splitter drag: set when a press
// lands on a handle, consulted on EventMouseDrag to resize that edge.
resizing bool
resizeSide DockSide
}
// BorderSplitW is the pixel thickness of a Border splitter handle (matches
// PanedHandleW).
const BorderSplitW = 6
// splitHandle records a drawn splitter's edge and rectangle for hit-testing.
type splitHandle struct {
side DockSide
rect Rect
}
// NewBorder builds an empty Border; assign the region fields and their sizes
// directly before the first SetBounds.
func NewBorder() *Border { return &Border{} }
// SetBounds lays out the regions in border precedence (N, S, then W, E, then the
// Center fills the remainder), reusing dockCarve for each edge.
func (b *Border) SetBounds(r Rect) {
b.Base.SetBounds(r)
b.handles = b.handles[:0]
avail := r
place := func(w Widget, side DockSide, size int, split bool) {
if w == nil {
return
}
if size < 0 {
size = 0
}
var bar Rect
bar, avail = dockCarve(side, size, avail)
w.SetBounds(bar)
if split {
var handle Rect
handle, avail = dockCarve(side, BorderSplitW, avail)
b.handles = append(b.handles, splitHandle{side: side, rect: handle})
}
}
place(b.North, DockTop, b.NorthSize, b.NorthSplit)
place(b.South, DockBottom, b.SouthSize, b.SouthSplit)
place(b.West, DockLeft, b.WestSize, b.WestSplit)
place(b.East, DockRight, b.EastSize, b.EastSplit)
if b.Center != nil {
b.Center.SetBounds(avail)
}
}
// SplitHandleAt reports which splitter handle (if any) contains the surface point
// (px,py) — the app calls it on mouse-down to start a region resize drag.
func (b *Border) SplitHandleAt(px, py int) (side DockSide, ok bool) {
for _, h := range b.handles {
if h.rect.Contains(px, py) {
return h.side, true
}
}
return 0, false
}
// ResizeSplit sets the given edge region's size (clamped to [0, the border's
// extent on that axis]), re-lays out, and fires OnResize. The app computes size
// from the drag — e.g. NorthSize + dy for the north handle.
func (b *Border) ResizeSplit(side DockSide, size int) {
if size < 0 {
size = 0
}
r := b.Bounds()
switch side {
case DockTop, DockBottom:
if size > r.H {
size = r.H
}
default: // DockLeft, DockRight
if size > r.W {
size = r.W
}
}
switch side {
case DockTop:
b.NorthSize = size
case DockBottom:
b.SouthSize = size
case DockLeft:
b.WestSize = size
default: // DockRight
b.EastSize = size
}
b.SetBounds(r)
if b.OnResize != nil {
b.OnResize(side, size)
}
}
// regions returns the non-nil regions in draw/hit order (edges before centre).
func (b *Border) regions() []Widget {
out := make([]Widget, 0, 5)
for _, w := range []Widget{b.North, b.South, b.West, b.East, b.Center} {
if w != nil {
out = append(out, w)
}
}
return out
}
// Draw paints every non-nil region, then any splitter handles over the seams.
func (b *Border) Draw(p painter.Painter, theme *Theme) {
for _, w := range b.regions() {
w.Draw(p, theme)
}
for _, h := range b.handles {
// West/East handles are thin vertical bars; North/South are horizontal.
drawSplitterBar(p, h.rect, h.side == DockLeft || h.side == DockRight, theme)
}
}
// OnEvent forwards to the first region whose Bounds contains the point,
// translated into that region's local space.
func (b *Border) OnEvent(ev Event) {
pr := b.Bounds()
sx, sy := ev.X+pr.X, ev.Y+pr.Y
switch ev.Kind {
case EventClick:
// A press on a splitter handle grabs it for a resize.
if side, ok := b.SplitHandleAt(sx, sy); ok {
b.resizing, b.resizeSide = true, side
return
}
case EventMouseDrag:
if b.resizing {
b.ResizeSplit(b.resizeSide, b.sizeFromPointer(b.resizeSide, ev.X, ev.Y))
return
}
case EventMouseUp:
if b.resizing {
b.resizing = false
return
}
}
// Otherwise route to the region under the pointer (a nested Paned/Border
// then handles its own splitter drag through the same event).
for _, w := range b.regions() {
if w.HitTest(sx, sy) {
w.OnEvent(translateEvent(ev, pr, w.Bounds()))
return
}
}
}
// sizeFromPointer converts a Border-local pointer to the new edge-region size
// for a drag on the given side: the distance from the region's outer edge to
// the pointer.
func (b *Border) sizeFromPointer(side DockSide, lx, ly int) int {
r := b.Bounds()
switch side {
case DockTop:
return ly
case DockBottom:
return r.H - ly
case DockLeft:
return lx
default: // DockRight
return r.W - lx
}
}