v0.15.0 9006957

workspace_css.go RawHistoryFolder

1// workspace_css.go - CSS generation and styling for workspace panes
2package browser
3
4import (
5	"fmt"
6	"strings"
7
8	"github.com/bnema/dumber/internal/config"
9	"github.com/bnema/dumber/internal/logging"
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// generateWorkspaceCSS generates the complete CSS for workspace panes and stacked panes
99func (wm *WorkspaceManager) generateWorkspaceCSS() string {
100	cfg := config.Get()
101	styling := cfg.Workspace.Styling
102
103	// Get appropriate colors based on GTK theme preference
104	var windowBackgroundColor string
105	isDark := webkit.PrefersDarkTheme()
106	if isDark {
107		windowBackgroundColor = "#2b2b2b" // Dark window background
108	} else {
109		windowBackgroundColor = "#ffffff" // Light window background
110	}
111
112	activeBorderColor := getActivePaneBorderColor(styling, isDark)
113
114	// Pane mode border color - use configured value or fallback to blue
115	paneModeColor := styling.PaneModeBorderColor
116	if paneModeColor == "" {
117		paneModeColor = "#4A90E2" // Fallback to blue if not configured
118	}
119
120	// Tab mode border color - use configured value or fallback to orange
121	tabModeColor := styling.TabModeBorderColor
122	if tabModeColor == "" {
123		tabModeColor = "#FFA500" // Fallback to orange if not configured
124	}
125
126	// Stacked title border - subtle separator between titles
127	stackedTitleBorder := fmt.Sprintf("1px solid %s", getStackTitleBorderColor(isDark))
128
129	// UI Scale calculations for title bar elements (base values at 1.0 scale)
130	uiScale := styling.UIScale
131	if uiScale <= 0 {
132		uiScale = 1.0 // Fallback to default if invalid
133	}
134	titleFontSize := int(12 * uiScale)         // Base: 12px
135	titlePaddingVertical := int(4 * uiScale)   // Base: 4px
136	titlePaddingHorizontal := int(8 * uiScale) // Base: 8px
137	titleMinHeight := int(24 * uiScale)        // Base: 24px
138	faviconMargin := int(4 * uiScale)          // Base: 4px
139
140	css := fmt.Sprintf(`window {
141	  background-color: %s;
142	  padding: 0;
143	  margin: 0;
144	}
145
146	/* Pane mode border overlay - floats over content without layout shift */
147	.pane-mode-border {
148	  border: %dpx solid %s;
149	  border-radius: 0;
150	  background-color: transparent;
151	  pointer-events: none;
152	}
153
154	/* Tab mode border overlay - floats over content without layout shift */
155	.tab-mode-border {
156	  border: %dpx solid %s;
157	  border-radius: 0;
158	  background-color: transparent;
159	  pointer-events: none;
160	}
161
162	/* Pane border overlay - active pane indicator without layout shift */
163	.pane-border-overlay {
164	  border: %dpx solid %s;
165	  border-radius: 0;
166	  background-color: transparent;
167	  pointer-events: none;
168	  transition-property: opacity;
169	  transition-duration: %dms;
170	  transition-timing-function: ease-in-out;
171	}
172
173	/* Workspace root container */
174	paned, box {
175	  background-color: %s;
176	}
177
178	/* Base pane styling - no borders (using overlays instead) */
179	.workspace-pane, .stacked-pane-container {
180	  border: none;
181	  margin: 0;
182	}
183
184	/* Stacked panes styling */
185	.stacked-pane-container {
186	  background-color: %s;
187	}
188
189	.stacked-pane-title {
190	  background-color: %s;
191	  border-bottom: %s;
192	  padding: %dpx %dpx;
193	  min-height: %dpx;
194	  transition: background-color %dms ease-in-out;
195	}
196
197	.stacked-pane-title:hover {
198	  background-color: %s;
199	}
200
201	.stacked-pane-title-text {
202	  font-size: %dpx;
203	  color: %s;
204	  font-weight: 500;
205	}
206
207	.stacked-pane-favicon {
208	  margin-right: %dpx;
209	}
210
211	.stacked-pane-active {
212	  /* Active pane is fully visible */
213	}
214
215	.stacked-pane-collapsed {
216	  /* Collapsed panes are hidden - handled in code via widget visibility */
217	}
218
219	/* Tab bar styling */
220	.tab-bar {
221	  background-color: %s;
222	  border-top: 2px solid %s;
223	  padding: 0;
224	  min-height: %dpx;
225	}
226
227	/* Tab progress bar */
228	progressbar.tab-progress-bar {
229	  min-height: 4px;
230	  padding: 0;
231	  margin: 0;
232	  border: none;
233	  transition: opacity 180ms ease-in-out;
234	}
235
236	progressbar.tab-progress-bar trough {
237	  min-height: 4px;
238	  background: transparent;
239	  border: none;
240	  padding: 0;
241	}
242
243	progressbar.tab-progress-bar progress {
244	  min-height: 4px;
245	  border-radius: 0;
246	  background-image: linear-gradient(90deg, %s 0%%, %s 50%%, %s 100%%);
247	  background-size: 240%% 100%%;
248	  background-position: 0%% 0%%;
249	  transition: width 180ms ease-in-out, background-position 420ms linear;
250	  animation: tab-progress-stripe 1.3s linear infinite;
251	}
252
253	/* Tab button styling - matches stacked pane titles */
254	button.tab-button {
255	  background-color: %s;
256	  background-image: none;
257	  border: none;
258	  border-right: 1px solid %s;
259	  border-radius: 0;
260	  padding: %dpx %dpx;
261	  transition: background-color %dms ease-in-out;
262	}
263
264	button.tab-button:hover {
265	  background-color: %s;
266	  background-image: none;
267	}
268
269	button.tab-button.tab-button-active {
270	  background-color: %s;
271	  background-image: none;
272	  border: none;
273	  border-right: 1px solid %s;
274	  border-radius: 0;
275	  font-weight: 600;
276	}
277
278	/* Tab title text */
279	.tab-title {
280	  font-size: %dpx;
281	  color: %s;
282	  font-weight: 500;
283	}
284
285	/* Tab rename entry */
286	entry.tab-rename-entry {
287	  background-color: %s;
288	  color: %s;
289	  font-size: %dpx;
290	  padding: 2px %dpx;
291	  border: 1px solid %s;
292	  border-radius: 3px;
293	  box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.4);
294	  min-width: 80px;
295	  min-height: 0;
296	}
297
298	entry.tab-rename-entry:focus {
299	  background-color: %s;
300	  border-color: %s;
301	  box-shadow: inset 1px 1px 4px rgba(0, 0, 0, 0.5);
302	  outline: none;
303	}
304
305	/* Tab content area */
306	.tab-content-area {
307	  background-color: %s;
308	}
309
310	/* Tab workspace containers */
311	.tab-workspace-container {
312	  background-color: %s;
313	}
314
315	/* Pulse animation for tab mode */
316	@keyframes pulse-border {
317	  0%%, 100%% { opacity: 1.0; }
318	  50%% { opacity: 0.6; }
319	}
320
321	@keyframes tab-progress-stripe {
322	  0%% { background-position: 0%% 0%%; }
323	  100%% { background-position: -100%% 0%%; }
324	}`,
325		windowBackgroundColor,          // window background
326		styling.PaneModeBorderWidth,    // .pane-mode-border border-width
327		paneModeColor,                  // .pane-mode-border border-color
328		styling.TabModeBorderWidth,     // .tab-mode-border border-width
329		tabModeColor,                   // .tab-mode-border border-color
330		styling.BorderWidth,            // .pane-border-overlay border-width
331		activeBorderColor,              // .pane-border-overlay border-color
332		styling.TransitionDuration,     // .pane-border-overlay transition-duration
333		windowBackgroundColor,          // paned, box background
334		windowBackgroundColor,          // stacked-pane-container background
335		getStackTitleBg(isDark),        // stacked-pane-title background
336		stackedTitleBorder,             // stacked-pane-title border-bottom
337		titlePaddingVertical,           // stacked-pane-title padding vertical
338		titlePaddingHorizontal,         // stacked-pane-title padding horizontal
339		titleMinHeight,                 // stacked-pane-title min-height
340		styling.TransitionDuration,     // stacked-pane-title transition
341		getStackTitleHoverBg(isDark),   // stacked-pane-title:hover background
342		titleFontSize,                  // stacked-pane-title-text font-size
343		getStackTitleTextColor(isDark), // stacked-pane-title-text color
344		faviconMargin,                  // stacked-pane-favicon margin-right
345		// Tab bar styles
346		windowBackgroundColor,            // tab-bar background
347		getStackTitleBorderColor(isDark), // tab-bar border-top
348		int(32*uiScale),                  // tab-bar min-height
349		"#606060",                        // tab progress gradient start (neutral gray)
350		"#747474",                        // tab progress gradient middle (neutral gray)
351		"#8a8a8a",                        // tab progress gradient end (neutral gray)
352		getStackTitleBg(isDark),          // tab-button background (same as stacked title)
353		getStackTitleBorderColor(isDark), // tab-button border-right
354		titlePaddingVertical,             // tab-button padding vertical
355		titlePaddingHorizontal,           // tab-button padding horizontal
356		styling.TransitionDuration,       // tab-button transition
357		getStackTitleHoverBg(isDark),     // tab-button:hover background
358		getActiveTabBg(isDark),           // tab-button-active background (much more visible)
359		getStackTitleBorderColor(isDark), // tab-button-active border-right
360		titleFontSize,                    // tab-title font-size
361		getStackTitleTextColor(isDark),   // tab-title color
362		// Tab rename entry styles
363		getEntryBg(isDark),               // entry.tab-rename-entry background
364		getStackTitleTextColor(isDark),   // entry.tab-rename-entry color (text)
365		titleFontSize,                    // entry.tab-rename-entry font-size
366		titlePaddingHorizontal,           // entry.tab-rename-entry padding horizontal (vertical is hardcoded 2px)
367		getEntryBorderColor(isDark),      // entry.tab-rename-entry border-color
368		getEntryFocusBg(isDark),          // entry.tab-rename-entry:focus background
369		activeBorderColor,                // entry.tab-rename-entry:focus border-color (use active pane color)
370		windowBackgroundColor,            // tab-content-area background
371		windowBackgroundColor,            // tab-workspace-container background
372	)
373
374	return css
375}
376
377// Global CSS provider tracking to prevent duplication
378var (
379	globalCSSInitialized bool
380	globalCSSContent     string
381)
382
383// ensureWorkspaceStyles ensures that CSS styles are applied for workspace panes
384// Improved to prevent CSS provider duplication by using global CSS tracking
385func (wm *WorkspaceManager) ensureWorkspaceStyles() {
386	if wm == nil {
387		return
388	}
389
390	// Generate CSS content
391	workspaceCSS := wm.generateWorkspaceCSS()
392
393	// Only apply CSS if it hasn't been applied globally or if content changed
394	if !globalCSSInitialized || globalCSSContent != workspaceCSS {
395		if err := webkit.AddCSSProvider(workspaceCSS); err != nil {
396			logging.Error(fmt.Sprintf("[workspace] WARNING: Failed to add CSS provider: %v", err))
397		}
398		globalCSSInitialized = true
399		globalCSSContent = workspaceCSS
400		logging.Info(fmt.Sprintf("[workspace] Applied workspace CSS styles (%d bytes)", len(workspaceCSS)))
401
402		previewLen := 200
403		if len(workspaceCSS) < previewLen {
404			previewLen = len(workspaceCSS)
405		}
406		logging.Info(fmt.Sprintf("[workspace] CSS preview: %s...", workspaceCSS[:previewLen]))
407	}
408
409	wm.cssInitialized = true
410}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close