1package daemon
2
3import (
4 "math"
5 "strconv"
6 "strings"
7
8 "github.com/bnema/vev/internal/domain"
9 "github.com/bnema/vev/internal/usecase/layout"
10 "github.com/bnema/vev/internal/usecase/mouse"
11)
12
13func hitTestPlacementLocked(tb *tab, col, row int) (layout.Placement, bool) {
14 if tb == nil || tb.tree == nil || tb.tree.Root == nil || !tb.size.Valid() {
15 return layout.Placement{}, false
16 }
17 placements, ok := solvedPlacementsLocked(tb)
18 if !ok {
19 return layout.Placement{}, false
20 }
21 for _, pl := range placements {
22 if pointInRect(col, row, pl.TitleBar) || pointInRect(col, row, pl.Content) {
23 return pl, true
24 }
25 }
26 return layout.Placement{}, false
27}
28
29type tabLayoutSnapshot struct {
30 fingerprint string
31 area domain.Rect
32 focus layout.PaneID
33 placements []layout.Placement
34 dividers []layout.Divider
35 ok bool
36}
37
38func solvedPlacementsLocked(tb *tab) ([]layout.Placement, bool) {
39 snap := solveTabLayoutLocked(tb)
40 return snap.placements, snap.ok
41}
42
43func solveTabLayoutLocked(tb *tab) tabLayoutSnapshot {
44 if tb == nil || tb.tree == nil || tb.tree.Root == nil || !tb.size.Valid() {
45 return tabLayoutSnapshot{}
46 }
47 area := domain.Rect{Width: tb.size.Cols, Height: tb.size.Rows}
48 placements, dividers, ok := layout.SolveWithDividers(tb.tree.Root, area)
49 return tabLayoutSnapshot{fingerprint: layoutFingerprint(tb.tree.Root), area: area, focus: tb.tree.Focus, placements: placements, dividers: dividers, ok: ok}
50}
51
52func layoutFingerprint(root *layout.Node) string {
53 var b strings.Builder
54 b.Grow(layoutFingerprintLength(root))
55 writeLayoutFingerprint(&b, root)
56 return b.String()
57}
58
59const weightFingerprintMaxLength = 16
60
61func layoutFingerprintLength(n *layout.Node) int {
62 if n == nil {
63 return 1
64 }
65 length := 2 + weightFingerprintMaxLength + 1 + paneIDFingerprintLength(n.Leaf) + 1 + paneIDFingerprintLength(n.Expanded) + 2
66 for _, child := range n.Children {
67 length += layoutFingerprintLength(child) + 1
68 }
69 return length
70}
71
72func paneIDFingerprintLength(id layout.PaneID) int {
73 length := len(id)
74 return decimalDigits(length) + 1 + length
75}
76
77func decimalDigits(value int) int {
78 if value == 0 {
79 return 1
80 }
81 digits := 0
82 for value > 0 {
83 value /= 10
84 digits++
85 }
86 return digits
87}
88
89func writeLayoutFingerprint(b *strings.Builder, n *layout.Node) {
90 if n == nil {
91 b.WriteByte('0')
92 return
93 }
94 b.WriteByte(byte('0' + n.Kind))
95 b.WriteByte(byte('0' + n.Dir))
96 writeWeightFingerprint(b, n.Weight)
97 b.WriteByte('|')
98 writePaneIDFingerprint(b, n.Leaf)
99 b.WriteByte('|')
100 writePaneIDFingerprint(b, n.Expanded)
101 b.WriteByte('[')
102 for _, child := range n.Children {
103 writeLayoutFingerprint(b, child)
104 b.WriteByte(',')
105 }
106 b.WriteByte(']')
107}
108
109func writeWeightFingerprint(b *strings.Builder, weight float64) {
110 const hex = "0123456789abcdef"
111 bits := math.Float64bits(weight)
112 started := false
113 for shift := uint(60); ; shift -= 4 {
114 digit := byte(bits >> shift & 0xf)
115 if digit != 0 || started || shift == 0 {
116 b.WriteByte(hex[digit])
117 started = true
118 }
119 if shift == 0 {
120 return
121 }
122 }
123}
124
125func writePaneIDFingerprint(b *strings.Builder, id layout.PaneID) {
126 s := string(id)
127 b.WriteString(strconv.Itoa(len(s)))
128 b.WriteByte(':')
129 b.WriteString(s)
130}
131
132func pointInRect(col, row int, r domain.Rect) bool {
133 return r.Width > 0 && r.Height > 0 && col >= r.X && col < r.X+r.Width && row >= r.Y && row < r.Y+r.Height
134}
135
136func focusPlacementLocked(tb *tab, id layout.PaneID) bool {
137 if tb == nil || tb.tree == nil {
138 return false
139 }
140 oldFocus := tb.tree.Focus
141 oldLayout := layoutFingerprint(tb.tree.Root)
142 tb.tree.Focus = id
143 setExpandedLocked(tb.tree.Root, id)
144 changed := oldFocus != tb.tree.Focus || oldLayout != layoutFingerprint(tb.tree.Root)
145 if changed {
146 tb.bumpLayoutGenerationLocked()
147 }
148 return changed
149}
150
151func setExpandedLocked(n *layout.Node, id layout.PaneID) bool {
152 if n == nil {
153 return false
154 }
155 if n.Kind == layout.Stack {
156 for _, child := range n.Children {
157 if child.Kind == layout.Leaf && child.Leaf == id {
158 n.Expanded = id
159 return true
160 }
161 }
162 }
163 for _, child := range n.Children {
164 if setExpandedLocked(child, id) {
165 return true
166 }
167 }
168 return false
169}
170
171func translateMouseEvent(ev mouse.Event, colOffset, contentYOffset int) mouse.Event {
172 ev.Col -= colOffset
173 ev.Row -= contentYOffset
174 ev.Raw = sgrOffset(ev.Raw, -colOffset, -contentYOffset-clientTopBarRows)
175 return ev
176}
177
178func (d *Daemon) exitCopyMode(ac *attachedClient) {
179 if ac == nil {
180 return
181 }
182 rt := ac.overlays
183 if rt == nil {
184 return
185 }
186 rt.copyMu.Lock()
187 rt.clearCopyModeLocked()
188 rt.copyMu.Unlock()
189}