detached b850e4a

fix(render): light only the focused half of a two-tile separator

commit
b850e4a7c3de7f82ed754ef43040946a797fd49e
author
brice · · 2026-09-26 14:52 UTC
committer
brice · · 2026-09-26 15:07 UTC
parent
5facfaf
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +42 -9
M internal/adapters/vulkan/renderer.go +38 -5
@@ -458,13 +458,30 @@ func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.Surfac
458458 }
459459 // The focused window lights the lines along its own sides only: its
460460 // inset border, and its neighbors' border line where they own it.
461+ // With exactly two tiles one line splits them: like tmux, the focused
462+ // window lights only its half of it (left/top window: first half).
461463 if focused != nil && s.Border.Active != "" && s.Border.Width > 0 {
462- f := focused
463- for _, strip := range borderStrips(f.Rect, f.Inset, 0, s.Border.Width) {
464- add(physRect(strip.X, strip.Y, strip.W, strip.H), parseColor(s.Border.Active))
464+ f, tiles := focused, 0
465+ for _, w := range s.Windows {
466+ if !w.Hidden && !w.Popup && w.Neighbors != 0 {
467+ tiles++
468+ }
465469 }
466- for _, strip := range borderStrips(f.Rect, f.Neighbors&^f.Inset, s.Border.Width, s.Border.Width) {
467- add(physRect(strip.X, strip.Y, strip.W, strip.H), parseColor(s.Border.Active))
470+ for _, side := range []ports.Sides{ports.SideLeft, ports.SideRight, ports.SideTop, ports.SideBottom} {
471+ out := 0
472+ switch {
473+ case f.Inset&side != 0:
474+ case f.Neighbors&side != 0:
475+ out = s.Border.Width
476+ default:
477+ continue
478+ }
479+ for _, strip := range borderStrips(f.Rect, side, out, s.Border.Width) {
480+ if tiles == 2 {
481+ strip = halfStrip(strip, side)
482+ }
483+ add(physRect(strip.X, strip.Y, strip.W, strip.H), parseColor(s.Border.Active))
484+ }
468485 }
469486 }
470487 // Popups draw only what the client drew, shadows clipped. Window popups
@@ -621,3 +638,19 @@ func borderStrips(r ports.Rect, sides ports.Sides, out, b int) []ports.Rect {
621638 }
622639 return strips
623640 }
641+
642+// halfStrip keeps the half of a separator strip owned by the window on
643+// side of it: the first half for the left or top window, else the second.
644+func halfStrip(r ports.Rect, side ports.Sides) ports.Rect {
645+ switch side {
646+ case ports.SideRight:
647+ r.H /= 2
648+ case ports.SideLeft:
649+ r.Y, r.H = r.Y+r.H/2, r.H-r.H/2
650+ case ports.SideBottom:
651+ r.W /= 2
652+ case ports.SideTop:
653+ r.X, r.W = r.X+r.W/2, r.W-r.W/2
654+ }
655+ return r
656+}
@@ -458,13 +458,30 @@ func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.Surfac
458 }458 }
459 // The focused window lights the lines along its own sides only: its459 // The focused window lights the lines along its own sides only: its
460 // inset border, and its neighbors' border line where they own it.460 // inset border, and its neighbors' border line where they own it.
461+ // With exactly two tiles one line splits them: like tmux, the focused
462+ // window lights only its half of it (left/top window: first half).
461 if focused != nil && s.Border.Active != "" && s.Border.Width > 0 {463 if focused != nil && s.Border.Active != "" && s.Border.Width > 0 {
462- f := focused464+ f, tiles := focused, 0
463- for _, strip := range borderStrips(f.Rect, f.Inset, 0, s.Border.Width) {465+ for _, w := range s.Windows {
464- add(physRect(strip.X, strip.Y, strip.W, strip.H), parseColor(s.Border.Active))466+ if !w.Hidden && !w.Popup && w.Neighbors != 0 {
467+ tiles++
468+ }
465 }469 }
466- for _, strip := range borderStrips(f.Rect, f.Neighbors&^f.Inset, s.Border.Width, s.Border.Width) {470+ for _, side := range []ports.Sides{ports.SideLeft, ports.SideRight, ports.SideTop, ports.SideBottom} {
467- add(physRect(strip.X, strip.Y, strip.W, strip.H), parseColor(s.Border.Active))471+ out := 0
472+ switch {
473+ case f.Inset&side != 0:
474+ case f.Neighbors&side != 0:
475+ out = s.Border.Width
476+ default:
477+ continue
478+ }
479+ for _, strip := range borderStrips(f.Rect, side, out, s.Border.Width) {
480+ if tiles == 2 {
481+ strip = halfStrip(strip, side)
482+ }
483+ add(physRect(strip.X, strip.Y, strip.W, strip.H), parseColor(s.Border.Active))
484+ }
468 }485 }
469 }486 }
470 // Popups draw only what the client drew, shadows clipped. Window popups487 // Popups draw only what the client drew, shadows clipped. Window popups
@@ -621,3 +638,19 @@ func borderStrips(r ports.Rect, sides ports.Sides, out, b int) []ports.Rect {
621 }638 }
622 return strips639 return strips
623 }640 }
641+
642+// halfStrip keeps the half of a separator strip owned by the window on
643+// side of it: the first half for the left or top window, else the second.
644+func halfStrip(r ports.Rect, side ports.Sides) ports.Rect {
645+ switch side {
646+ case ports.SideRight:
647+ r.H /= 2
648+ case ports.SideLeft:
649+ r.Y, r.H = r.Y+r.H/2, r.H-r.H/2
650+ case ports.SideBottom:
651+ r.W /= 2
652+ case ports.SideTop:
653+ r.X, r.W = r.X+r.W/2, r.W-r.W/2
654+ }
655+ return r
656+}
M internal/adapters/vulkan/renderer_test.go +4 -4
@@ -64,17 +64,17 @@ func TestRendererRender(t *testing.T) {
6464 check(s, map[image.Point]color.RGBA{{0, 0}: bg, {9, 9}: {0x31, 0x32, 0x44, 255}, {10, 10}: wc, {16, 16}: wc, {21, 21}: wc, {22, 21}: {0x31, 0x32, 0x44, 255}, {21, 22}: {0x31, 0x32, 0x44, 255}, {23, 23}: {0x31, 0x32, 0x44, 255}, {24, 24}: bg})
6565 s.Windows[0].Focused = true
6666 check(s, map[image.Point]color.RGBA{{9, 9}: {0xb4, 0xbe, 0xfe, 255}, {10, 10}: wc, {16, 16}: wc})
67- // tmux style: the left window owns the shared line; focusing the right
68- // window lights that line too.
67+ // tmux style: the left window owns the shared line. With two tiles the
68+ // focused one lights its half: top for the left window, bottom for the right.
6969 gray, lit := color.RGBA{0x31, 0x32, 0x44, 255}, color.RGBA{0xb4, 0xbe, 0xfe, 255}
7070 pair := s
7171 pair.Windows = []ports.SceneWindow{
7272 {ID: 1, Rect: ports.Rect{X: 0, Y: 0, W: 16, H: 16}, Inset: ports.SideRight, Neighbors: ports.SideRight},
7373 {ID: 2, Rect: ports.Rect{X: 16, Y: 0, W: 16, H: 16}, Neighbors: ports.SideLeft, Focused: true},
7474 }
75- check(pair, map[image.Point]color.RGBA{{14, 5}: lit, {15, 5}: lit, {13, 5}: wc, {0, 0}: wc})
75+ check(pair, map[image.Point]color.RGBA{{14, 12}: lit, {15, 12}: lit, {14, 3}: gray, {13, 5}: wc, {0, 0}: wc})
7676 pair.Windows[1].Focused, pair.Windows[0].Focused = false, true
77- check(pair, map[image.Point]color.RGBA{{14, 5}: lit})
77+ check(pair, map[image.Point]color.RGBA{{14, 3}: lit, {14, 12}: gray})
7878 pair.Windows[0].Focused = false
7979 check(pair, map[image.Point]color.RGBA{{14, 5}: gray, {15, 5}: gray})
8080 s.Windows[0].Fullscreen = true
@@ -64,17 +64,17 @@ func TestRendererRender(t *testing.T) {
64 check(s, map[image.Point]color.RGBA{{0, 0}: bg, {9, 9}: {0x31, 0x32, 0x44, 255}, {10, 10}: wc, {16, 16}: wc, {21, 21}: wc, {22, 21}: {0x31, 0x32, 0x44, 255}, {21, 22}: {0x31, 0x32, 0x44, 255}, {23, 23}: {0x31, 0x32, 0x44, 255}, {24, 24}: bg})64 check(s, map[image.Point]color.RGBA{{0, 0}: bg, {9, 9}: {0x31, 0x32, 0x44, 255}, {10, 10}: wc, {16, 16}: wc, {21, 21}: wc, {22, 21}: {0x31, 0x32, 0x44, 255}, {21, 22}: {0x31, 0x32, 0x44, 255}, {23, 23}: {0x31, 0x32, 0x44, 255}, {24, 24}: bg})
65 s.Windows[0].Focused = true65 s.Windows[0].Focused = true
66 check(s, map[image.Point]color.RGBA{{9, 9}: {0xb4, 0xbe, 0xfe, 255}, {10, 10}: wc, {16, 16}: wc})66 check(s, map[image.Point]color.RGBA{{9, 9}: {0xb4, 0xbe, 0xfe, 255}, {10, 10}: wc, {16, 16}: wc})
67- // tmux style: the left window owns the shared line; focusing the right67+ // tmux style: the left window owns the shared line. With two tiles the
68- // window lights that line too.68+ // focused one lights its half: top for the left window, bottom for the right.
69 gray, lit := color.RGBA{0x31, 0x32, 0x44, 255}, color.RGBA{0xb4, 0xbe, 0xfe, 255}69 gray, lit := color.RGBA{0x31, 0x32, 0x44, 255}, color.RGBA{0xb4, 0xbe, 0xfe, 255}
70 pair := s70 pair := s
71 pair.Windows = []ports.SceneWindow{71 pair.Windows = []ports.SceneWindow{
72 {ID: 1, Rect: ports.Rect{X: 0, Y: 0, W: 16, H: 16}, Inset: ports.SideRight, Neighbors: ports.SideRight},72 {ID: 1, Rect: ports.Rect{X: 0, Y: 0, W: 16, H: 16}, Inset: ports.SideRight, Neighbors: ports.SideRight},
73 {ID: 2, Rect: ports.Rect{X: 16, Y: 0, W: 16, H: 16}, Neighbors: ports.SideLeft, Focused: true},73 {ID: 2, Rect: ports.Rect{X: 16, Y: 0, W: 16, H: 16}, Neighbors: ports.SideLeft, Focused: true},
74 }74 }
75- check(pair, map[image.Point]color.RGBA{{14, 5}: lit, {15, 5}: lit, {13, 5}: wc, {0, 0}: wc})75+ check(pair, map[image.Point]color.RGBA{{14, 12}: lit, {15, 12}: lit, {14, 3}: gray, {13, 5}: wc, {0, 0}: wc})
76 pair.Windows[1].Focused, pair.Windows[0].Focused = false, true76 pair.Windows[1].Focused, pair.Windows[0].Focused = false, true
77- check(pair, map[image.Point]color.RGBA{{14, 5}: lit})77+ check(pair, map[image.Point]color.RGBA{{14, 3}: lit, {14, 12}: gray})
78 pair.Windows[0].Focused = false78 pair.Windows[0].Focused = false
79 check(pair, map[image.Point]color.RGBA{{14, 5}: gray, {15, 5}: gray})79 check(pair, map[image.Point]color.RGBA{{14, 5}: gray, {15, 5}: gray})
80 s.Windows[0].Fullscreen = true80 s.Windows[0].Fullscreen = true

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 25

Tags 10

↑↓ moveenter openesc close