detached f25b0c0

fix(selection): leave enough height for resolution labels

commit
f25b0c03dba8f37a9562dec5bc427c58aec1b412
author
brice · · 2026-09-30 12:07 UTC
committer
brice · · 2026-09-30 14:48 UTC
parent
d3fc2d3
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +40 -3
M internal/adapters/selection/scene.go +2 -1
@@ -7,7 +7,8 @@ import (
77
88 // Fixed overlay element sizes in logical pixels.
99 const (
10- labelW, labelH = 120.0, 20.0
10+ // 4px tag padding leaves 18px for the default 13px monospace line.
11+ labelW, labelH = 120.0, 26.0
1112 footerW, footerH = 400.0, 28.0
1213 headerH = 28.0
1314 edge = 1.0 // thin selection lines
@@ -7,7 +7,8 @@ import (
7 7
8 // Fixed overlay element sizes in logical pixels.8 // Fixed overlay element sizes in logical pixels.
9 const (9 const (
10- labelW, labelH = 120.0, 20.010+ // 4px tag padding leaves 18px for the default 13px monospace line.
11+ labelW, labelH = 120.0, 26.0
11 footerW, footerH = 400.0, 28.012 footerW, footerH = 400.0, 28.0
12 headerH = 28.013 headerH = 28.0
13 edge = 1.0 // thin selection lines14 edge = 1.0 // thin selection lines
M internal/adapters/selection/selection_test.go +38 -2
@@ -4,6 +4,7 @@ import (
44 "context"
55 "errors"
66 "fmt"
7+ "strings"
78 "testing"
89
910 "github.com/bnema/nefergui"
@@ -404,7 +405,7 @@ func TestSceneGeometry(t *testing.T) {
404405 assert.Equal(t, frect{100, 249, 200, 1}, s.edges[1])
405406 assert.Equal(t, frect{100, 50, 1, 200}, s.edges[2])
406407 assert.Equal(t, frect{299, 50, 1, 200}, s.edges[3])
407- assert.Equal(t, frect{100, 26, labelW, labelH}, s.label)
408+ assert.Equal(t, frect{100, 50 - labelH - 4, labelW, labelH}, s.label)
408409 // Dim strips and region tile the output exactly.
409410 area := 200.0 * 200.0
410411 for _, d := range s.dim {
@@ -425,8 +426,43 @@ func TestSceneMonitorOutlineAndGrid(t *testing.T) {
425426 }
426427
427428 func TestLabelPlacementStaysOnSurface(t *testing.T) {
428- assert.Equal(t, frect{1800, 986, labelW, labelH}, labelRect(frect{1800, 1010, 100, 50}, 1920, 1080))
429+ // Above the region, with a 4px gap.
430+ assert.Equal(t, frect{1800, 1010 - labelH - 4, labelW, labelH}, labelRect(frect{1800, 1010, 100, 50}, 1920, 1080))
431+ // No room above: just inside the region's top-left corner.
429432 assert.Equal(t, frect{4, 4, labelW, labelH}, labelRect(frect{0, 0, 500, 500}, 1920, 1080))
433+ // Exactly enough room above: the label touches the gap, not the edge.
434+ assert.Equal(t, frect{10, 0, labelW, labelH}, labelRect(frect{10, labelH + 4, 300, 100}, 1920, 1080))
435+ // One pixel short falls back to inside the region.
436+ assert.Equal(t, frect{14, labelH + 3 + 4, labelW, labelH}, labelRect(frect{10, labelH + 3, 300, 100}, 1920, 1080))
437+ // Right edge: shifted left to stay on the surface.
438+ assert.Equal(t, frect{1920 - labelW, 100, labelW, labelH}, labelRect(frect{1900, 100 + labelH + 4, 20, 20}, 1920, 1080))
439+ // Inside fallback near the bottom is clamped onto the surface.
440+ assert.Equal(t, frect{4, 40 - labelH, labelW, labelH}, labelRect(frect{0, 20, 500, 20}, 1920, 40))
441+ // A surface smaller than the label pins it to the origin.
442+ assert.Equal(t, frect{0, 0, labelW, labelH}, labelRect(frect{5, 5, 10, 10}, labelW-1, labelH-1))
443+}
444+
445+func TestLabelStaysOnSurfaceWhenItFits(t *testing.T) {
446+ const w, h = 300.0, 100.0
447+ for y := 0.0; y <= h; y += 7 {
448+ for x := 0.0; x <= w; x += 13 {
449+ l := labelRect(frect{x, y, w - x, h - y}, w, h)
450+ assert.GreaterOrEqual(t, l.x, 0.0)
451+ assert.GreaterOrEqual(t, l.y, 0.0)
452+ assert.LessOrEqual(t, l.x+l.w, w, "region %v,%v", x, y)
453+ assert.LessOrEqual(t, l.y+l.h, h, "region %v,%v", x, y)
454+ }
455+ }
456+}
457+
458+// The tallest default 13px monospace line is 17.71px (Noto Sans Mono).
459+// Keep the sizing assumptions explicit rather than partially parsing CSS.
460+func TestLabelContentHoldsMonospaceLine(t *testing.T) {
461+ const line, padY = 17.71, 4.0
462+ require.True(t, strings.Contains(styleSheet, "box-sizing: border-box; padding: 4px 10px; overflow: hidden;"), "update label sizing when tag box sizing changes")
463+ content := labelH - 2*padY
464+ assert.GreaterOrEqual(t, content, line, "label clips its text line")
465+ assert.GreaterOrEqual(t, content-line+padY, 1.0, "less than 1px below the text line")
430466 }
431467
432468 func TestFooterAndHeaderText(t *testing.T) {
@@ -4,6 +4,7 @@ import (
4 "context"4 "context"
5 "errors"5 "errors"
6 "fmt"6 "fmt"
7+ "strings"
7 "testing"8 "testing"
8 9
9 "github.com/bnema/nefergui"10 "github.com/bnema/nefergui"
@@ -404,7 +405,7 @@ func TestSceneGeometry(t *testing.T) {
404 assert.Equal(t, frect{100, 249, 200, 1}, s.edges[1])405 assert.Equal(t, frect{100, 249, 200, 1}, s.edges[1])
405 assert.Equal(t, frect{100, 50, 1, 200}, s.edges[2])406 assert.Equal(t, frect{100, 50, 1, 200}, s.edges[2])
406 assert.Equal(t, frect{299, 50, 1, 200}, s.edges[3])407 assert.Equal(t, frect{299, 50, 1, 200}, s.edges[3])
407- assert.Equal(t, frect{100, 26, labelW, labelH}, s.label)408+ assert.Equal(t, frect{100, 50 - labelH - 4, labelW, labelH}, s.label)
408 // Dim strips and region tile the output exactly.409 // Dim strips and region tile the output exactly.
409 area := 200.0 * 200.0410 area := 200.0 * 200.0
410 for _, d := range s.dim {411 for _, d := range s.dim {
@@ -425,8 +426,43 @@ func TestSceneMonitorOutlineAndGrid(t *testing.T) {
425 }426 }
426 427
427 func TestLabelPlacementStaysOnSurface(t *testing.T) {428 func TestLabelPlacementStaysOnSurface(t *testing.T) {
428- assert.Equal(t, frect{1800, 986, labelW, labelH}, labelRect(frect{1800, 1010, 100, 50}, 1920, 1080))429+ // Above the region, with a 4px gap.
430+ assert.Equal(t, frect{1800, 1010 - labelH - 4, labelW, labelH}, labelRect(frect{1800, 1010, 100, 50}, 1920, 1080))
431+ // No room above: just inside the region's top-left corner.
429 assert.Equal(t, frect{4, 4, labelW, labelH}, labelRect(frect{0, 0, 500, 500}, 1920, 1080))432 assert.Equal(t, frect{4, 4, labelW, labelH}, labelRect(frect{0, 0, 500, 500}, 1920, 1080))
433+ // Exactly enough room above: the label touches the gap, not the edge.
434+ assert.Equal(t, frect{10, 0, labelW, labelH}, labelRect(frect{10, labelH + 4, 300, 100}, 1920, 1080))
435+ // One pixel short falls back to inside the region.
436+ assert.Equal(t, frect{14, labelH + 3 + 4, labelW, labelH}, labelRect(frect{10, labelH + 3, 300, 100}, 1920, 1080))
437+ // Right edge: shifted left to stay on the surface.
438+ assert.Equal(t, frect{1920 - labelW, 100, labelW, labelH}, labelRect(frect{1900, 100 + labelH + 4, 20, 20}, 1920, 1080))
439+ // Inside fallback near the bottom is clamped onto the surface.
440+ assert.Equal(t, frect{4, 40 - labelH, labelW, labelH}, labelRect(frect{0, 20, 500, 20}, 1920, 40))
441+ // A surface smaller than the label pins it to the origin.
442+ assert.Equal(t, frect{0, 0, labelW, labelH}, labelRect(frect{5, 5, 10, 10}, labelW-1, labelH-1))
443+}
444+
445+func TestLabelStaysOnSurfaceWhenItFits(t *testing.T) {
446+ const w, h = 300.0, 100.0
447+ for y := 0.0; y <= h; y += 7 {
448+ for x := 0.0; x <= w; x += 13 {
449+ l := labelRect(frect{x, y, w - x, h - y}, w, h)
450+ assert.GreaterOrEqual(t, l.x, 0.0)
451+ assert.GreaterOrEqual(t, l.y, 0.0)
452+ assert.LessOrEqual(t, l.x+l.w, w, "region %v,%v", x, y)
453+ assert.LessOrEqual(t, l.y+l.h, h, "region %v,%v", x, y)
454+ }
455+ }
456+}
457+
458+// The tallest default 13px monospace line is 17.71px (Noto Sans Mono).
459+// Keep the sizing assumptions explicit rather than partially parsing CSS.
460+func TestLabelContentHoldsMonospaceLine(t *testing.T) {
461+ const line, padY = 17.71, 4.0
462+ require.True(t, strings.Contains(styleSheet, "box-sizing: border-box; padding: 4px 10px; overflow: hidden;"), "update label sizing when tag box sizing changes")
463+ content := labelH - 2*padY
464+ assert.GreaterOrEqual(t, content, line, "label clips its text line")
465+ assert.GreaterOrEqual(t, content-line+padY, 1.0, "less than 1px below the text line")
430 }466 }
431 467
432 func TestFooterAndHeaderText(t *testing.T) {468 func TestFooterAndHeaderText(t *testing.T) {

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 2

Tags 9

↑↓ moveenter openesc close