1// workspace_css.go - CSS generation and styling for workspace panes
2package browser
3
4import (
5 "fmt"
6 "log"
7 "strings"
8
9 "github.com/bnema/dumber/internal/config"
10 "github.com/bnema/dumber/pkg/webkit"
11)
12
13// Helper functions for stacked pane CSS colors
14
15// getStackTitleBg returns the background color for stack titles based on theme
16func getStackTitleBg(isDark bool) string {
17 if isDark {
18 return "#404040"
19 }
20 return "#f0f0f0"
21}
22
23// getStackTitleHoverBg returns the hover background color for stack titles based on theme
24func getStackTitleHoverBg(isDark bool) string {
25 if isDark {
26 return "#505050"
27 }
28 return "#e0e0e0"
29}
30
31// getStackTitleTextColor returns the text color for stack titles based on theme
32func getStackTitleTextColor(isDark bool) string {
33 if isDark {
34 return "#ffffff"
35 }
36 return "#333333"
37}
38
39// getStackTitleBorderColor returns a subtle border color slightly darker than the title background
40func getStackTitleBorderColor(isDark bool) string {
41 if isDark {
42 return "#353535" // Slightly darker than #404040
43 }
44 return "#e5e5e5" // Slightly darker than #f0f0f0
45}
46
47// getActiveTabBg returns a distinct background color for the active tab
48func getActiveTabBg(isDark bool) string {
49 if isDark {
50 return "#707070" // Significantly lighter gray than inactive (#404040) - similar to stacked panes
51 }
52 return "#c0c0c0" // Significantly darker gray than inactive (#f0f0f0) - similar to stacked panes
53}
54
55// getActivePaneBorderColor returns the border color for active panes based on config and theme
56func getActivePaneBorderColor(styling config.WorkspaceStylingConfig, isDark bool) string {
57 // Use configured border color if set
58 if styling.BorderColor != "" {
59 // Check if it's a GTK theme variable (starts with @)
60 if strings.HasPrefix(styling.BorderColor, "@") {
61 return styling.BorderColor
62 }
63 // Return the configured color as-is (could be hex, rgb, etc.)
64 return styling.BorderColor
65 }
66
67 // Fallback to hardcoded colors based on theme
68 if isDark {
69 return "#4A90E2" // Deeper blue pastel for dark theme
70 }
71 return "#87CEEB" // Sky blue pastel for light theme
72}
73
74// getInactivePaneBorderColor returns the border color for inactive panes based on config and theme
75func getInactivePaneBorderColor(styling config.WorkspaceStylingConfig, isDark bool) string {
76 // Use configured border color if set
77 if styling.InactiveBorderColor != "" {
78 // Check if it's a GTK theme variable (starts with @)
79 if strings.HasPrefix(styling.InactiveBorderColor, "@") {
80 return styling.InactiveBorderColor
81 }
82 // Return the configured color as-is (could be hex, rgb, etc.)
83 return styling.InactiveBorderColor
84 }
85
86 // Fallback to hardcoded colors based on theme
87 if isDark {
88 return "#333333" // Dark border for dark theme
89 }
90 return "#dddddd" // Light border for light theme
91}
92
93// generateWorkspaceCSS generates the complete CSS for workspace panes and stacked panes
94func (wm *WorkspaceManager) generateWorkspaceCSS() string {
95 cfg := config.Get()
96 styling := cfg.Workspace.Styling
97
98 // Get appropriate colors based on GTK theme preference
99 var windowBackgroundColor string
100 isDark := webkit.PrefersDarkTheme()
101 if isDark {
102 windowBackgroundColor = "#2b2b2b" // Dark window background
103 } else {
104 windowBackgroundColor = "#ffffff" // Light window background
105 }
106
107 activeBorderColor := getActivePaneBorderColor(styling, isDark)
108 inactiveBorderColor := getInactivePaneBorderColor(styling, isDark)
109
110 // Pane mode border color - use configured value or fallback to blue
111 paneModeColor := styling.PaneModeBorderColor
112 if paneModeColor == "" {
113 paneModeColor = "#4A90E2" // Fallback to blue if not configured
114 }
115
116 // Tab mode border color - use configured value or fallback to orange
117 tabModeColor := styling.TabModeBorderColor
118 if tabModeColor == "" {
119 tabModeColor = "#FFA500" // Fallback to orange if not configured
120 }
121
122 // Stacked title border - subtle separator between titles
123 stackedTitleBorder := fmt.Sprintf("1px solid %s", getStackTitleBorderColor(isDark))
124
125 // UI Scale calculations for title bar elements (base values at 1.0 scale)
126 uiScale := styling.UIScale
127 if uiScale <= 0 {
128 uiScale = 1.0 // Fallback to default if invalid
129 }
130 titleFontSize := int(12 * uiScale) // Base: 12px
131 titlePaddingVertical := int(4 * uiScale) // Base: 4px
132 titlePaddingHorizontal := int(8 * uiScale) // Base: 8px
133 titleMinHeight := int(24 * uiScale) // Base: 24px
134 faviconMargin := int(4 * uiScale) // Base: 4px
135
136 css := fmt.Sprintf(`window {
137 background-color: %s;
138 padding: 0;
139 margin: 0;
140 }
141
142 /* Pane mode active - change window background to border color */
143 window.pane-mode-active {
144 background-color: %s;
145 }
146
147 /* Workspace root container */
148 paned, box {
149 background-color: %s;
150 transition: margin 150ms ease-in-out;
151 }
152
153 /* Base pane styling - configurable inactive borders */
154 .workspace-pane, .stacked-pane-container {
155 border-width: %dpx;
156 border-style: solid;
157 border-color: %s;
158 border-radius: %dpx;
159 margin: 0;
160 transition-property: border-color;
161 transition-duration: %dms;
162 transition-timing-function: ease-in-out;
163 }
164
165 /* Active pane border styling */
166 .workspace-pane-active {
167 border-width: %dpx;
168 border-color: %s;
169 }
170
171 /* Stacked panes styling */
172 .stacked-pane-container {
173 background-color: %s;
174 }
175
176 /* Stacked pane containers keep inactive border when active */
177 .stacked-pane-container.workspace-pane-active {
178 border-width: %dpx;
179 border-color: %s;
180 }
181
182 .stacked-pane-title {
183 background-color: %s;
184 border-bottom: %s;
185 padding: %dpx %dpx;
186 min-height: %dpx;
187 transition: background-color %dms ease-in-out;
188 }
189
190 .stacked-pane-title:hover {
191 background-color: %s;
192 }
193
194 .stacked-pane-title-text {
195 font-size: %dpx;
196 color: %s;
197 font-weight: 500;
198 }
199
200 .stacked-pane-favicon {
201 margin-right: %dpx;
202 }
203
204 .stacked-pane-active {
205 /* Active pane is fully visible */
206 }
207
208 .stacked-pane-collapsed {
209 /* Collapsed panes are hidden - handled in code via widget visibility */
210 }
211
212 /* Tab bar styling */
213 .tab-bar {
214 background-color: %s;
215 border-top: 2px solid %s;
216 padding: 0;
217 min-height: %dpx;
218 }
219
220 /* Tab mode active - window background changes to border color (like pane mode) */
221 window.tab-mode-active {
222 background-color: %s;
223 }
224
225 /* Tab button styling - matches stacked pane titles */
226 button.tab-button {
227 background-color: %s;
228 background-image: none;
229 border: none;
230 border-right: 1px solid %s;
231 border-radius: 0;
232 padding: %dpx %dpx;
233 transition: background-color %dms ease-in-out;
234 }
235
236 button.tab-button:hover {
237 background-color: %s;
238 background-image: none;
239 }
240
241 button.tab-button.tab-button-active {
242 background-color: %s;
243 background-image: none;
244 border: none;
245 border-right: 1px solid %s;
246 border-radius: 0;
247 font-weight: 600;
248 }
249
250 /* Tab title text */
251 .tab-title {
252 font-size: %dpx;
253 color: %s;
254 font-weight: 500;
255 }
256
257 /* Tab content area */
258 .tab-content-area {
259 background-color: %s;
260 }
261
262 /* Tab workspace containers */
263 .tab-workspace-container {
264 background-color: %s;
265 }
266
267 /* Pulse animation for tab mode */
268 @keyframes pulse-border {
269 0%%, 100%% { opacity: 1.0; }
270 50%% { opacity: 0.6; }
271 }`,
272 windowBackgroundColor, // window background
273 paneModeColor, // window.pane-mode-active background (border color)
274 windowBackgroundColor, // paned, box background
275 styling.InactiveBorderWidth, // base pane border-width (inactive)
276 inactiveBorderColor, // base pane border-color (inactive)
277 styling.BorderRadius, // base pane border radius
278 styling.TransitionDuration, // transition-duration
279 styling.BorderWidth, // workspace-pane-active border-width
280 activeBorderColor, // workspace-pane-active border color
281 windowBackgroundColor, // stacked-pane-container background
282 styling.InactiveBorderWidth, // stacked-pane-container.active border-width
283 inactiveBorderColor, // stacked-pane-container.active border-color
284 getStackTitleBg(isDark), // stacked-pane-title background
285 stackedTitleBorder, // stacked-pane-title border-bottom
286 titlePaddingVertical, // stacked-pane-title padding vertical
287 titlePaddingHorizontal, // stacked-pane-title padding horizontal
288 titleMinHeight, // stacked-pane-title min-height
289 styling.TransitionDuration, // stacked-pane-title transition
290 getStackTitleHoverBg(isDark), // stacked-pane-title:hover background
291 titleFontSize, // stacked-pane-title-text font-size
292 getStackTitleTextColor(isDark), // stacked-pane-title-text color
293 faviconMargin, // stacked-pane-favicon margin-right
294 // Tab bar styles
295 windowBackgroundColor, // tab-bar background
296 getStackTitleBorderColor(isDark), // tab-bar border-top
297 int(32 * uiScale), // tab-bar min-height
298 tabModeColor, // window.tab-mode-active .tab-bar border-top-color (orange, distinct from pane mode blue)
299 getStackTitleBg(isDark), // tab-button background (same as stacked title)
300 getStackTitleBorderColor(isDark), // tab-button border-right
301 titlePaddingVertical, // tab-button padding vertical
302 titlePaddingHorizontal, // tab-button padding horizontal
303 styling.TransitionDuration, // tab-button transition
304 getStackTitleHoverBg(isDark), // tab-button:hover background
305 getActiveTabBg(isDark), // tab-button-active background (much more visible)
306 getStackTitleBorderColor(isDark), // tab-button-active border-right
307 titleFontSize, // tab-title font-size
308 getStackTitleTextColor(isDark), // tab-title color
309 windowBackgroundColor, // tab-content-area background
310 windowBackgroundColor, // tab-workspace-container background
311 )
312
313 return css
314}
315
316// Global CSS provider tracking to prevent duplication
317var (
318 globalCSSInitialized bool
319 globalCSSContent string
320)
321
322// ensureWorkspaceStyles ensures that CSS styles are applied for workspace panes
323// Improved to prevent CSS provider duplication by using global CSS tracking
324func (wm *WorkspaceManager) ensureWorkspaceStyles() {
325 if wm == nil {
326 return
327 }
328
329 // Generate CSS content
330 workspaceCSS := wm.generateWorkspaceCSS()
331
332 // Only apply CSS if it hasn't been applied globally or if content changed
333 if !globalCSSInitialized || globalCSSContent != workspaceCSS {
334 if err := webkit.AddCSSProvider(workspaceCSS); err != nil {
335 log.Printf("[workspace] WARNING: Failed to add CSS provider: %v", err)
336 }
337 globalCSSInitialized = true
338 globalCSSContent = workspaceCSS
339 log.Printf("[workspace] Applied workspace CSS styles (%d bytes)", len(workspaceCSS))
340 log.Printf("[workspace] CSS preview: %s...", workspaceCSS[:min(200, len(workspaceCSS))])
341 }
342
343 wm.cssInitialized = true
344}