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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +40 -3
M internal/adapters/selection/scene.go +2 -1
| @@ -7,7 +7,8 @@ import ( | |||
| 7 | 7 | ||
| 8 | 8 | // Fixed overlay element sizes in logical pixels. | |
| 9 | 9 | 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 | |
| 11 | 12 | footerW, footerH = 400.0, 28.0 | |
| 12 | 13 | headerH = 28.0 | |
| 13 | 14 | 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.0 | 10 | + | // 4px tag padding leaves 18px for the default 13px monospace line. |
| 11 | + | labelW, labelH = 120.0, 26.0 | |||
| 11 | footerW, footerH = 400.0, 28.0 | 12 | footerW, footerH = 400.0, 28.0 | ||
| 12 | headerH = 28.0 | 13 | headerH = 28.0 | ||
| 13 | edge = 1.0 // thin selection lines | 14 | edge = 1.0 // thin selection lines | ||
M internal/adapters/selection/selection_test.go +38 -2
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "context" | |
| 5 | 5 | "errors" | |
| 6 | 6 | "fmt" | |
| 7 | + | "strings" | |
| 7 | 8 | "testing" | |
| 8 | 9 | ||
| 9 | 10 | "github.com/bnema/nefergui" | |
| @@ -404,7 +405,7 @@ func TestSceneGeometry(t *testing.T) { | |||
| 404 | 405 | assert.Equal(t, frect{100, 249, 200, 1}, s.edges[1]) | |
| 405 | 406 | assert.Equal(t, frect{100, 50, 1, 200}, s.edges[2]) | |
| 406 | 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 | 409 | // Dim strips and region tile the output exactly. | |
| 409 | 410 | area := 200.0 * 200.0 | |
| 410 | 411 | for _, d := range s.dim { | |
| @@ -425,8 +426,43 @@ func TestSceneMonitorOutlineAndGrid(t *testing.T) { | |||
| 425 | 426 | } | |
| 426 | 427 | ||
| 427 | 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 | 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 | 468 | 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.0 | 410 | 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) { | ||