1// workspace_geometry_validator.go - Simplified geometry validation for split operations
2package browser
3
4import (
5 "fmt"
6 "log"
7
8 "github.com/bnema/dumber/pkg/webkit"
9)
10
11// GeometryValidator validates pane geometry constraints for splits and operations
12type GeometryValidator struct {
13 minPaneWidth int
14 minPaneHeight int
15 debugMode bool
16}
17
18// PaneGeometry represents the geometry of a pane
19type PaneGeometry struct {
20 X int
21 Y int
22 Width int
23 Height int
24 IsValid bool
25}
26
27// ValidationResult represents the result of a geometry validation
28type ValidationResult struct {
29 IsValid bool
30 Reason string
31 Geometry PaneGeometry
32 RequiresRevalidation bool
33}
34
35// NewGeometryValidator creates a new geometry validator
36func NewGeometryValidator() *GeometryValidator {
37 return &GeometryValidator{
38 minPaneWidth: 300, // Minimum width for usable pane
39 minPaneHeight: 200, // Minimum height for usable pane
40 debugMode: false,
41 }
42}
43
44// SetDebugMode enables or disables debug logging
45func (gv *GeometryValidator) SetDebugMode(debug bool) {
46 gv.debugMode = debug
47}
48
49// GetPaneGeometry extracts geometry information from a pane node
50func (gv *GeometryValidator) GetPaneGeometry(node *paneNode) PaneGeometry {
51 geometry := PaneGeometry{IsValid: false}
52
53 if node == nil || node.container == 0 {
54 if gv.debugMode {
55 log.Printf("[geometry] Invalid node or missing container")
56 }
57 return geometry
58 }
59
60 // Get widget allocation directly - GTK operations are always on main thread
61 allocation := webkit.WidgetGetAllocation(node.container)
62
63 geometry.X = allocation.X
64 geometry.Y = allocation.Y
65 geometry.Width = allocation.Width
66 geometry.Height = allocation.Height
67 geometry.IsValid = true
68
69 if gv.debugMode {
70 log.Printf("[geometry] Pane geometry: %dx%d at (%d,%d)",
71 geometry.Width, geometry.Height, geometry.X, geometry.Y)
72 }
73
74 return geometry
75}
76
77// prepareGeometryResult centralizes the common geometry validation logic used by
78// split/stack checks. It returns the computed geometry, a populated
79// ValidationResult (for early exits), and a boolean that indicates whether the
80// caller should continue with additional constraints.
81func (gv *GeometryValidator) prepareGeometryResult(node *paneNode) (PaneGeometry, ValidationResult, bool) {
82 geometry := gv.GetPaneGeometry(node)
83 result := ValidationResult{Geometry: geometry}
84
85 if !geometry.IsValid {
86 if geometry.Width == 0 && geometry.Height == 0 {
87 result.IsValid = true
88 result.Reason = "geometry pending allocation"
89 result.RequiresRevalidation = true
90 } else {
91 result.IsValid = false
92 result.Reason = "could not determine pane geometry"
93 }
94 return geometry, result, false
95 }
96
97 return geometry, result, true
98}
99
100// ValidateHorizontalSplit validates if a pane can be split horizontally
101func (gv *GeometryValidator) ValidateHorizontalSplit(node *paneNode) ValidationResult {
102 geometry, result, ok := gv.prepareGeometryResult(node)
103 if !ok {
104 return result
105 }
106
107 requiredWidth := gv.minPaneWidth * 2
108 if geometry.Width < requiredWidth {
109 result.IsValid = false
110 result.Reason = fmt.Sprintf("pane width %d < required %d for horizontal split",
111 geometry.Width, requiredWidth)
112 return result
113 }
114
115 if geometry.Height < gv.minPaneHeight {
116 result.IsValid = false
117 result.Reason = fmt.Sprintf("pane height %d < minimum %d",
118 geometry.Height, gv.minPaneHeight)
119 return result
120 }
121
122 result.IsValid = true
123 result.Reason = "horizontal split is valid"
124 return result
125}
126
127// ValidateVerticalSplit validates if a pane can be split vertically
128func (gv *GeometryValidator) ValidateVerticalSplit(node *paneNode) ValidationResult {
129 geometry, result, ok := gv.prepareGeometryResult(node)
130 if !ok {
131 return result
132 }
133
134 requiredHeight := gv.minPaneHeight * 2
135 if geometry.Height < requiredHeight {
136 result.IsValid = false
137 result.Reason = fmt.Sprintf("pane height %d < required %d for vertical split",
138 geometry.Height, requiredHeight)
139 return result
140 }
141
142 if geometry.Width < gv.minPaneWidth {
143 result.IsValid = false
144 result.Reason = fmt.Sprintf("pane width %d < minimum %d",
145 geometry.Width, gv.minPaneWidth)
146 return result
147 }
148
149 result.IsValid = true
150 result.Reason = "vertical split is valid"
151 return result
152}
153
154// ValidateSplit validates a split operation based on direction
155func (gv *GeometryValidator) ValidateSplit(node *paneNode, direction string) ValidationResult {
156 if node == nil {
157 return ValidationResult{IsValid: false, Reason: "node is nil"}
158 }
159
160 if node.isStacked {
161 return ValidationResult{IsValid: false, Reason: "cannot split stacked pane container"}
162 }
163
164 if !node.isLeaf {
165 return ValidationResult{IsValid: false, Reason: "can only split leaf panes"}
166 }
167
168 switch direction {
169 case "left", "right":
170 return gv.ValidateHorizontalSplit(node)
171 case "up", "down":
172 return gv.ValidateVerticalSplit(node)
173 default:
174 return ValidationResult{IsValid: false, Reason: fmt.Sprintf("unknown split direction: %s", direction)}
175 }
176}
177
178// ValidateStackOperation validates if a pane can be converted to a stack
179func (gv *GeometryValidator) ValidateStackOperation(node *paneNode) ValidationResult {
180 geometry, result, ok := gv.prepareGeometryResult(node)
181 if !ok {
182 return result
183 }
184
185 if node.isStacked {
186 result.IsValid = false
187 result.Reason = "pane is already stacked"
188 return result
189 }
190
191 minStackHeight := gv.minPaneHeight + 30 // Extra space for title bar
192 if geometry.Height < minStackHeight {
193 result.IsValid = false
194 result.Reason = fmt.Sprintf("pane height %d < required %d for stacking",
195 geometry.Height, minStackHeight)
196 return result
197 }
198
199 if geometry.Width < gv.minPaneWidth {
200 result.IsValid = false
201 result.Reason = fmt.Sprintf("pane width %d < minimum %d",
202 geometry.Width, gv.minPaneWidth)
203 return result
204 }
205
206 result.IsValid = true
207 result.Reason = "stack operation is valid"
208 return result
209}