turbo-editors/turbo-corepublic Fork 0
v1.0.1
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

desktop.go · 216 lines · 5.6 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 21h ago1package ui
2
3import (
4 "github.com/gdamore/tcell/v2"
5
📦 Turbo Core f3ade8d k33g 13h ago6 "rickub.com/turbo-editors/turbo-core/theme"
🛟 Updated. 28d5985 k33g 21h ago7)
8
9// desktopPattern is the character Turbo Vision tiled its backdrop with.
10const desktopPattern = '░'
11
12// Desktop is the backdrop and the windows on it.
13//
14// Windows are kept in back-to-front order, so the last one is the active one:
15// drawing walks the slice forwards and event routing walks it backwards, which
16// is all the z-ordering a desktop needs.
17type Desktop struct {
18 Box
19 windows []*Window
20}
21
22// NewDesktop returns an empty desktop.
23func NewDesktop() *Desktop { return &Desktop{} }
24
25// Windows returns the windows, back to front. The slice is the desktop's own;
26// do not reorder it.
27func (d *Desktop) Windows() []*Window { return d.windows }
28
29// Count returns how many windows are open.
30func (d *Desktop) Count() int { return len(d.windows) }
31
32// Active returns the front window, or nil when the desktop is empty.
33func (d *Desktop) Active() *Window {
34 if len(d.windows) == 0 {
35 return nil
36 }
37 return d.windows[len(d.windows)-1]
38}
39
40// Add puts a window on top of the desktop and makes it active.
41//
42// desktop.Add(ui.NewWindow("main.go", view))
43func (d *Desktop) Add(w *Window) {
44 // The desktop is what a window is maximised into, so it is the desktop that
45 // knows how — and a window that has not been added to one draws no maximise
46 // box rather than one that would do nothing.
47 w.OnMaximize = func() { d.ToggleMaximize(w) }
48
49 d.windows = append(d.windows, w)
50 d.refresh()
51}
52
53// Remove takes a window off the desktop and reports whether it was there.
54func (d *Desktop) Remove(w *Window) bool {
55 for i, candidate := range d.windows {
56 if candidate == w {
57 d.windows = append(d.windows[:i], d.windows[i+1:]...)
58 d.refresh()
59 return true
60 }
61 }
62 return false
63}
64
65// Focus brings a window to the front and reports whether it was on the desktop
66// to begin with.
67func (d *Desktop) Focus(w *Window) bool {
68 if !d.Remove(w) {
69 return false
70 }
71 d.Add(w)
72 return true
73}
74
75// FocusNumber brings window number n to the front, counting from one, and
76// reports whether there is one. It is what Alt-1 … Alt-9 do.
77func (d *Desktop) FocusNumber(n int) bool {
78 if n < 1 || n > len(d.windows) {
79 return false
80 }
81 return d.Focus(d.windows[n-1])
82}
83
84// Next cycles to the window behind the active one, as F6 does.
85func (d *Desktop) Next() {
86 if len(d.windows) < 2 {
87 return
88 }
89 // The window at the back becomes the front one, and everything else slides
90 // back a place.
91 d.windows = append(d.windows[len(d.windows)-1:], d.windows[:len(d.windows)-1]...)
92 d.refresh()
93}
94
95// SetBounds places the desktop and takes its windows with it.
96//
97// A window follows the desktop edges its grow mode names, so one that filled
98// the terminal still fills it after the terminal is resized. None may end up
99// larger than the desktop, whatever its grow mode.
100//
101// This is called on every turn of the event loop, and does nothing at all when
102// the desktop has not actually changed size.
103func (d *Desktop) SetBounds(r Rect) {
104 previous := d.Bounds()
105 d.Box.SetBounds(r)
106
107 if previous == r {
108 return
109 }
110 for _, w := range d.windows {
111 w.followDesktop(previous, r)
112 }
113}
114
115// Tile lays every window out side by side in a grid, none overlapping.
116func (d *Desktop) Tile() {
117 if len(d.windows) == 0 {
118 return
119 }
120
121 columns := 1
122 for columns*columns < len(d.windows) {
123 columns++
124 }
125 rows := (len(d.windows) + columns - 1) / columns
126
127 cellWidth := max(d.Bounds().W/columns, MinWindowWidth)
128 cellHeight := max(d.Bounds().H/rows, MinWindowHeight)
129
130 for i, w := range d.windows {
131 w.place(Rect{
132 X: d.Bounds().X + (i%columns)*cellWidth,
133 Y: d.Bounds().Y + (i/columns)*cellHeight,
134 W: cellWidth,
135 H: cellHeight,
136 })
137 }
138}
139
140// cascadeStep is how far each cascaded window is offset from the one behind.
141const cascadeStep = 2
142
143// Cascade stacks the windows with their title bars all visible.
144func (d *Desktop) Cascade() {
145 area := d.Bounds()
146 for i, w := range d.windows {
147 offset := i * cascadeStep
148 w.place(Rect{
149 X: area.X + offset,
150 Y: area.Y + offset,
151 W: max(area.W-offset-cascadeStep, MinWindowWidth),
152 H: max(area.H-offset-cascadeStep, MinWindowHeight),
153 })
154 }
155}
156
157// ToggleMaximize gives one window the whole desktop, or puts it back where it
158// was if it already has it.
159//
160// desktop.ToggleMaximize(desktop.Active())
161func (d *Desktop) ToggleMaximize(w *Window) {
162 if w.Maximized() {
163 w.Restore()
164 return
165 }
166 w.Maximize(d.Bounds())
167}
168
169// refresh renumbers the windows and marks the front one active, which must
170// happen after any change to the stack.
171func (d *Desktop) refresh() {
172 for i, w := range d.windows {
173 w.SetNumber(i + 1)
174 w.SetActive(false)
175 }
176 if active := d.Active(); active != nil {
177 active.SetActive(true)
178 }
179}
180
181// Draw paints the backdrop and then every window from back to front.
182func (d *Desktop) Draw(p *Painter, th *theme.Theme) {
183 p.Fill(p.Size(), desktopPattern, th.Style(theme.KeyDesktop))
184 for _, w := range d.windows {
185 w.Draw(p, th)
186 }
187}
188
189// HandleKey gives the key to the active window.
190func (d *Desktop) HandleKey(ev *tcell.EventKey) bool {
191 active := d.Active()
192 return active != nil && active.HandleKey(ev)
193}
194
195// HandleMouse routes a click to the front-most window it lands on, bringing
196// that window forward first.
197//
198// A window in the middle of being dragged keeps receiving events wherever the
199// pointer goes, or a fast drag would escape the window and strand it.
200func (d *Desktop) HandleMouse(ev *tcell.EventMouse) bool {
201 if active := d.Active(); active != nil && active.Dragging() {
202 return active.HandleMouse(ev)
203 }
204
205 for i := len(d.windows) - 1; i >= 0; i-- {
206 w := d.windows[i]
207 if !w.hit(ev) {
208 continue
209 }
210 if ev.Buttons() == tcell.Button1 {
211 d.Focus(w)
212 }
213 return w.HandleMouse(ev)
214 }
215 return false
216}