detached 365d0f4

feat(browser): add configurable active pane borders with smooth transitions

- Make border color configurable via workspace.styling.border_color config
- Support GTK theme variables (@theme_selected_bg_color) and custom colors
- Replace border-jump CSS with smooth outline-color transitions
- Add base transparent outline to prevent visual jumps
- Fallback to theme-appropriate colors (dark: #4A90E2, light: #87CEEB)
commit
365d0f44da28ff5d3b8d3d144b0fc6d2dfb8118b
author
Brice · · 2025-09-27 21:47 UTC
parent
7c100fb
signature
unsigned
1 file · +28 -14
M internal/app/browser/workspace_css.go +28 -14
@@ -3,6 +3,7 @@ package browser
33
44 import (
55 "fmt"
6+ "strings"
67
78 "github.com/bnema/dumber/internal/config"
89 "github.com/bnema/dumber/pkg/webkit"
@@ -34,8 +35,19 @@ func getStackTitleTextColor(isDark bool) string {
3435 return "#333333"
3536 }
3637
37-// getActivePaneBorderColor returns the border color for active panes based on theme
38-func getActivePaneBorderColor(isDark bool) string {
38+// getActivePaneBorderColor returns the border color for active panes based on config and theme
39+func getActivePaneBorderColor(styling config.WorkspaceStylingConfig, isDark bool) string {
40+ // Use configured border color if set
41+ if styling.BorderColor != "" {
42+ // Check if it's a GTK theme variable (starts with @)
43+ if strings.HasPrefix(styling.BorderColor, "@") {
44+ return styling.BorderColor
45+ }
46+ // Return the configured color as-is (could be hex, rgb, etc.)
47+ return styling.BorderColor
48+ }
49+
50+ // Fallback to hardcoded colors based on theme
3951 if isDark {
4052 return "#4A90E2" // Deeper blue pastel for dark theme
4153 }
@@ -58,30 +70,33 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string {
5870 windowBackgroundColor = "#ffffff" // Light window background
5971 }
6072
61- activeBorderColor := getActivePaneBorderColor(isDark)
73+ activeBorderColor := getActivePaneBorderColor(styling, isDark)
6274
6375 css := fmt.Sprintf(`window {
6476 background-color: %s;
6577 }
6678
67- /* Active pane border styling using outline (no layout impact) */
68- .workspace-pane-active {
69- outline: 2px solid %s;
79+ /* Base pane styling - transparent outline for smooth transitions */
80+ .workspace-pane, .stacked-pane-container {
81+ outline: 2px solid transparent;
7082 outline-offset: -2px;
7183 transition: outline-color %dms ease-in-out;
7284 }
7385
86+ /* Active pane border styling using outline (no layout impact) */
87+ .workspace-pane-active {
88+ outline-color: %s;
89+ }
90+
7491 /* Stacked panes styling */
7592 .stacked-pane-container {
7693 background-color: %s;
7794 border-radius: %dpx;
7895 }
7996
80- /* Active stacked pane container gets the outline */
97+ /* Active stacked pane container gets the outline color */
8198 .stacked-pane-container.workspace-pane-active {
82- outline: 2px solid %s;
83- outline-offset: -2px;
84- transition: outline-color %dms ease-in-out;
99+ outline-color: %s;
85100 }
86101
87102 .stacked-pane-title {
@@ -110,12 +125,11 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string {
110125 /* Collapsed panes are hidden - handled in code via widget visibility */
111126 }`,
112127 windowBackgroundColor, // window background
113- activeBorderColor, // workspace-pane-active border
114- styling.TransitionDuration, // workspace-pane-active transition
128+ styling.TransitionDuration, // base pane outline transition
129+ activeBorderColor, // workspace-pane-active outline-color
115130 windowBackgroundColor, // stacked-pane-container background
116131 styling.BorderRadius, // stacked-pane-container border-radius
117- activeBorderColor, // stacked-pane-container.workspace-pane-active border
118- styling.TransitionDuration, // stacked-pane-container.workspace-pane-active transition
132+ activeBorderColor, // stacked-pane-container.workspace-pane-active outline-color
119133 getStackTitleBg(isDark), // stacked-pane-title background
120134 inactiveBorderColor, // stacked-pane-title border-bottom
121135 styling.TransitionDuration, // stacked-pane-title transition
@@ -3,6 +3,7 @@ package browser
3 3
4 import (4 import (
5 "fmt"5 "fmt"
6+ "strings"
6 7
7 "github.com/bnema/dumber/internal/config"8 "github.com/bnema/dumber/internal/config"
8 "github.com/bnema/dumber/pkg/webkit"9 "github.com/bnema/dumber/pkg/webkit"
@@ -34,8 +35,19 @@ func getStackTitleTextColor(isDark bool) string {
34 return "#333333"35 return "#333333"
35 }36 }
36 37
37-// getActivePaneBorderColor returns the border color for active panes based on theme38+// getActivePaneBorderColor returns the border color for active panes based on config and theme
38-func getActivePaneBorderColor(isDark bool) string {39+func getActivePaneBorderColor(styling config.WorkspaceStylingConfig, isDark bool) string {
40+ // Use configured border color if set
41+ if styling.BorderColor != "" {
42+ // Check if it's a GTK theme variable (starts with @)
43+ if strings.HasPrefix(styling.BorderColor, "@") {
44+ return styling.BorderColor
45+ }
46+ // Return the configured color as-is (could be hex, rgb, etc.)
47+ return styling.BorderColor
48+ }
49+
50+ // Fallback to hardcoded colors based on theme
39 if isDark {51 if isDark {
40 return "#4A90E2" // Deeper blue pastel for dark theme52 return "#4A90E2" // Deeper blue pastel for dark theme
41 }53 }
@@ -58,30 +70,33 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string {
58 windowBackgroundColor = "#ffffff" // Light window background70 windowBackgroundColor = "#ffffff" // Light window background
59 }71 }
60 72
61- activeBorderColor := getActivePaneBorderColor(isDark)73+ activeBorderColor := getActivePaneBorderColor(styling, isDark)
62 74
63 css := fmt.Sprintf(`window {75 css := fmt.Sprintf(`window {
64 background-color: %s;76 background-color: %s;
65 }77 }
66 78
67- /* Active pane border styling using outline (no layout impact) */79+ /* Base pane styling - transparent outline for smooth transitions */
68- .workspace-pane-active {80+ .workspace-pane, .stacked-pane-container {
69- outline: 2px solid %s;81+ outline: 2px solid transparent;
70 outline-offset: -2px;82 outline-offset: -2px;
71 transition: outline-color %dms ease-in-out;83 transition: outline-color %dms ease-in-out;
72 }84 }
73 85
86+ /* Active pane border styling using outline (no layout impact) */
87+ .workspace-pane-active {
88+ outline-color: %s;
89+ }
90+
74 /* Stacked panes styling */91 /* Stacked panes styling */
75 .stacked-pane-container {92 .stacked-pane-container {
76 background-color: %s;93 background-color: %s;
77 border-radius: %dpx;94 border-radius: %dpx;
78 }95 }
79 96
80- /* Active stacked pane container gets the outline */97+ /* Active stacked pane container gets the outline color */
81 .stacked-pane-container.workspace-pane-active {98 .stacked-pane-container.workspace-pane-active {
82- outline: 2px solid %s;99+ outline-color: %s;
83- outline-offset: -2px;
84- transition: outline-color %dms ease-in-out;
85 }100 }
86 101
87 .stacked-pane-title {102 .stacked-pane-title {
@@ -110,12 +125,11 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string {
110 /* Collapsed panes are hidden - handled in code via widget visibility */125 /* Collapsed panes are hidden - handled in code via widget visibility */
111 }`,126 }`,
112 windowBackgroundColor, // window background127 windowBackgroundColor, // window background
113- activeBorderColor, // workspace-pane-active border128+ styling.TransitionDuration, // base pane outline transition
114- styling.TransitionDuration, // workspace-pane-active transition129+ activeBorderColor, // workspace-pane-active outline-color
115 windowBackgroundColor, // stacked-pane-container background130 windowBackgroundColor, // stacked-pane-container background
116 styling.BorderRadius, // stacked-pane-container border-radius131 styling.BorderRadius, // stacked-pane-container border-radius
117- activeBorderColor, // stacked-pane-container.workspace-pane-active border132+ activeBorderColor, // stacked-pane-container.workspace-pane-active outline-color
118- styling.TransitionDuration, // stacked-pane-container.workspace-pane-active transition
119 getStackTitleBg(isDark), // stacked-pane-title background133 getStackTitleBg(isDark), // stacked-pane-title background
120 inactiveBorderColor, // stacked-pane-title border-bottom134 inactiveBorderColor, // stacked-pane-title border-bottom
121 styling.TransitionDuration, // stacked-pane-title transition135 styling.TransitionDuration, // stacked-pane-title transition

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 9

Tags 42

↑↓ moveenter openesc close