v0.14.1 0da30ba

workspace_css.go RawHistoryFolder

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}

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