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// getEntryBg returns the background color for text entry widgets
56func getEntryBg(isDark bool) string {
57 if isDark {
58 return "#2a2a2a" // Darker background for dark theme
59 }
60 return "#e0e0e0" // Darker gray for light theme
61}
62
63// getEntryFocusBg returns the background color for focused entry widgets
64func getEntryFocusBg(isDark bool) string {
65 if isDark {
66 return "#323232" // Slightly lighter when focused
67 }
68 return "#f5f5f5" // Very light gray when focused
69}
70
71// getEntryBorderColor returns the border color for entry widgets
72func getEntryBorderColor(isDark bool) string {
73 if isDark {
74 return "#555555" // Subtle border for dark theme
75 }
76 return "#cccccc" // Subtle border for light theme
77}
78
79// getActivePaneBorderColor returns the border color for active panes based on config and theme
80func getActivePaneBorderColor(styling config.WorkspaceStylingConfig, isDark bool) string {
81 // Use configured border color if set
82 if styling.BorderColor != "" {
83 // Check if it's a GTK theme variable (starts with @)
84 if strings.HasPrefix(styling.BorderColor, "@") {
85 return styling.BorderColor
86 }
87 // Return the configured color as-is (could be hex, rgb, etc.)
88 return styling.BorderColor
89 }
90
91 // Fallback to hardcoded colors based on theme
92 if isDark {
93 return "#4A90E2" // Deeper blue pastel for dark theme
94 }
95 return "#87CEEB" // Sky blue pastel for light theme
96}
97
98// getInactivePaneBorderColor returns the border color for inactive panes based on config and theme
99func getInactivePaneBorderColor(styling config.WorkspaceStylingConfig, isDark bool) string {
100 // Use configured border color if set
101 if styling.InactiveBorderColor != "" {
102 // Check if it's a GTK theme variable (starts with @)
103 if strings.HasPrefix(styling.InactiveBorderColor, "@") {
104 return styling.InactiveBorderColor
105 }
106 // Return the configured color as-is (could be hex, rgb, etc.)
107 return styling.InactiveBorderColor
108 }
109
110 // Fallback to hardcoded colors based on theme
111 if isDark {
112 return "#333333" // Dark border for dark theme
113 }
114 return "#dddddd" // Light border for light theme
115}
116
117// generateWorkspaceCSS generates the complete CSS for workspace panes and stacked panes
118func (wm *WorkspaceManager) generateWorkspaceCSS() string {
119 cfg := config.Get()
120 styling := cfg.Workspace.Styling
121
122 // Get appropriate colors based on GTK theme preference
123 var windowBackgroundColor string
124 isDark := webkit.PrefersDarkTheme()
125 if isDark {
126 windowBackgroundColor = "#2b2b2b" // Dark window background
127 } else {
128 windowBackgroundColor = "#ffffff" // Light window background
129 }
130
131 activeBorderColor := getActivePaneBorderColor(styling, isDark)
132 inactiveBorderColor := getInactivePaneBorderColor(styling, isDark)
133
134 // Pane mode border color - use configured value or fallback to blue
135 paneModeColor := styling.PaneModeBorderColor
136 if paneModeColor == "" {
137 paneModeColor = "#4A90E2" // Fallback to blue if not configured
138 }
139
140 // Tab mode border color - use configured value or fallback to orange
141 tabModeColor := styling.TabModeBorderColor
142 if tabModeColor == "" {
143 tabModeColor = "#FFA500" // Fallback to orange if not configured
144 }
145
146 // Stacked title border - subtle separator between titles
147 stackedTitleBorder := fmt.Sprintf("1px solid %s", getStackTitleBorderColor(isDark))
148
149 // UI Scale calculations for title bar elements (base values at 1.0 scale)
150 uiScale := styling.UIScale
151 if uiScale <= 0 {
152 uiScale = 1.0 // Fallback to default if invalid
153 }
154 titleFontSize := int(12 * uiScale) // Base: 12px
155 titlePaddingVertical := int(4 * uiScale) // Base: 4px
156 titlePaddingHorizontal := int(8 * uiScale) // Base: 8px
157 titleMinHeight := int(24 * uiScale) // Base: 24px
158 faviconMargin := int(4 * uiScale) // Base: 4px
159
160 css := fmt.Sprintf(`window {
161 background-color: %s;
162 padding: 0;
163 margin: 0;
164 }
165
166 /* Pane mode active - change window background to border color */
167 window.pane-mode-active {
168 background-color: %s;
169 }
170
171 /* Workspace root container */
172 paned, box {
173 background-color: %s;
174 transition: margin 150ms ease-in-out;
175 }
176
177 /* Base pane styling - configurable inactive borders */
178 .workspace-pane, .stacked-pane-container {
179 border-width: %dpx;
180 border-style: solid;
181 border-color: %s;
182 border-radius: %dpx;
183 margin: 0;
184 transition-property: border-color;
185 transition-duration: %dms;
186 transition-timing-function: ease-in-out;
187 }
188
189 /* Active pane border styling */
190 .workspace-pane-active {
191 border-width: %dpx;
192 border-color: %s;
193 }
194
195 /* Stacked panes styling */
196 .stacked-pane-container {
197 background-color: %s;
198 }
199
200 /* Stacked pane containers keep inactive border when active */
201 .stacked-pane-container.workspace-pane-active {
202 border-width: %dpx;
203 border-color: %s;
204 }
205
206 .stacked-pane-title {
207 background-color: %s;
208 border-bottom: %s;
209 padding: %dpx %dpx;
210 min-height: %dpx;
211 transition: background-color %dms ease-in-out;
212 }
213
214 .stacked-pane-title:hover {
215 background-color: %s;
216 }
217
218 .stacked-pane-title-text {
219 font-size: %dpx;
220 color: %s;
221 font-weight: 500;
222 }
223
224 .stacked-pane-favicon {
225 margin-right: %dpx;
226 }
227
228 .stacked-pane-active {
229 /* Active pane is fully visible */
230 }
231
232 .stacked-pane-collapsed {
233 /* Collapsed panes are hidden - handled in code via widget visibility */
234 }
235
236 /* Tab bar styling */
237 .tab-bar {
238 background-color: %s;
239 border-top: 2px solid %s;
240 padding: 0;
241 min-height: %dpx;
242 }
243
244 /* Tab mode active - window background changes to border color (like pane mode) */
245 window.tab-mode-active {
246 background-color: %s;
247 }
248
249 /* Tab button styling - matches stacked pane titles */
250 button.tab-button {
251 background-color: %s;
252 background-image: none;
253 border: none;
254 border-right: 1px solid %s;
255 border-radius: 0;
256 padding: %dpx %dpx;
257 transition: background-color %dms ease-in-out;
258 }
259
260 button.tab-button:hover {
261 background-color: %s;
262 background-image: none;
263 }
264
265 button.tab-button.tab-button-active {
266 background-color: %s;
267 background-image: none;
268 border: none;
269 border-right: 1px solid %s;
270 border-radius: 0;
271 font-weight: 600;
272 }
273
274 /* Tab title text */
275 .tab-title {
276 font-size: %dpx;
277 color: %s;
278 font-weight: 500;
279 }
280
281 /* Tab rename entry */
282 entry.tab-rename-entry {
283 background-color: %s;
284 color: %s;
285 font-size: %dpx;
286 padding: 2px %dpx;
287 border: 1px solid %s;
288 border-radius: 3px;
289 box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.4);
290 min-width: 80px;
291 min-height: 0;
292 }
293
294 entry.tab-rename-entry:focus {
295 background-color: %s;
296 border-color: %s;
297 box-shadow: inset 1px 1px 4px rgba(0, 0, 0, 0.5);
298 outline: none;
299 }
300
301 /* Tab content area */
302 .tab-content-area {
303 background-color: %s;
304 }
305
306 /* Tab workspace containers */
307 .tab-workspace-container {
308 background-color: %s;
309 }
310
311 /* Pulse animation for tab mode */
312 @keyframes pulse-border {
313 0%%, 100%% { opacity: 1.0; }
314 50%% { opacity: 0.6; }
315 }`,
316 windowBackgroundColor, // window background
317 paneModeColor, // window.pane-mode-active background (border color)
318 windowBackgroundColor, // paned, box background
319 styling.InactiveBorderWidth, // base pane border-width (inactive)
320 inactiveBorderColor, // base pane border-color (inactive)
321 styling.BorderRadius, // base pane border radius
322 styling.TransitionDuration, // transition-duration
323 styling.BorderWidth, // workspace-pane-active border-width
324 activeBorderColor, // workspace-pane-active border color
325 windowBackgroundColor, // stacked-pane-container background
326 styling.InactiveBorderWidth, // stacked-pane-container.active border-width
327 inactiveBorderColor, // stacked-pane-container.active border-color
328 getStackTitleBg(isDark), // stacked-pane-title background
329 stackedTitleBorder, // stacked-pane-title border-bottom
330 titlePaddingVertical, // stacked-pane-title padding vertical
331 titlePaddingHorizontal, // stacked-pane-title padding horizontal
332 titleMinHeight, // stacked-pane-title min-height
333 styling.TransitionDuration, // stacked-pane-title transition
334 getStackTitleHoverBg(isDark), // stacked-pane-title:hover background
335 titleFontSize, // stacked-pane-title-text font-size
336 getStackTitleTextColor(isDark), // stacked-pane-title-text color
337 faviconMargin, // stacked-pane-favicon margin-right
338 // Tab bar styles
339 windowBackgroundColor, // tab-bar background
340 getStackTitleBorderColor(isDark), // tab-bar border-top
341 int(32*uiScale), // tab-bar min-height
342 tabModeColor, // window.tab-mode-active .tab-bar border-top-color (orange, distinct from pane mode blue)
343 getStackTitleBg(isDark), // tab-button background (same as stacked title)
344 getStackTitleBorderColor(isDark), // tab-button border-right
345 titlePaddingVertical, // tab-button padding vertical
346 titlePaddingHorizontal, // tab-button padding horizontal
347 styling.TransitionDuration, // tab-button transition
348 getStackTitleHoverBg(isDark), // tab-button:hover background
349 getActiveTabBg(isDark), // tab-button-active background (much more visible)
350 getStackTitleBorderColor(isDark), // tab-button-active border-right
351 titleFontSize, // tab-title font-size
352 getStackTitleTextColor(isDark), // tab-title color
353 // Tab rename entry styles
354 getEntryBg(isDark), // entry.tab-rename-entry background
355 getStackTitleTextColor(isDark), // entry.tab-rename-entry color (text)
356 titleFontSize, // entry.tab-rename-entry font-size
357 titlePaddingHorizontal, // entry.tab-rename-entry padding horizontal (vertical is hardcoded 2px)
358 getEntryBorderColor(isDark), // entry.tab-rename-entry border-color
359 getEntryFocusBg(isDark), // entry.tab-rename-entry:focus background
360 activeBorderColor, // entry.tab-rename-entry:focus border-color (use active pane color)
361 windowBackgroundColor, // tab-content-area background
362 windowBackgroundColor, // tab-workspace-container background
363 )
364
365 return css
366}
367
368// Global CSS provider tracking to prevent duplication
369var (
370 globalCSSInitialized bool
371 globalCSSContent string
372)
373
374// ensureWorkspaceStyles ensures that CSS styles are applied for workspace panes
375// Improved to prevent CSS provider duplication by using global CSS tracking
376func (wm *WorkspaceManager) ensureWorkspaceStyles() {
377 if wm == nil {
378 return
379 }
380
381 // Generate CSS content
382 workspaceCSS := wm.generateWorkspaceCSS()
383
384 // Only apply CSS if it hasn't been applied globally or if content changed
385 if !globalCSSInitialized || globalCSSContent != workspaceCSS {
386 if err := webkit.AddCSSProvider(workspaceCSS); err != nil {
387 log.Printf("[workspace] WARNING: Failed to add CSS provider: %v", err)
388 }
389 globalCSSInitialized = true
390 globalCSSContent = workspaceCSS
391 log.Printf("[workspace] Applied workspace CSS styles (%d bytes)", len(workspaceCSS))
392 log.Printf("[workspace] CSS preview: %s...", workspaceCSS[:min(200, len(workspaceCSS))])
393 }
394
395 wm.cssInitialized = true
396}