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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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 | |||
| 458 | 458 | } | |
| 459 | 459 | // The focused window lights the lines along its own sides only: its | |
| 460 | 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 | 463 | 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 | + | } | |
| 465 | 469 | } | |
| 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 | + | } | |
| 468 | 485 | } | |
| 469 | 486 | } | |
| 470 | 487 | // 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 | 639 | 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 | + | } | |
| @@ -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: its | 459 | // 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 := focused | 464 | + | 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 popups | 487 | // 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 strips | 639 | 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) { | |||
| 64 | 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 | 65 | s.Windows[0].Focused = true | |
| 66 | 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 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. | |
| 69 | 69 | gray, lit := color.RGBA{0x31, 0x32, 0x44, 255}, color.RGBA{0xb4, 0xbe, 0xfe, 255} | |
| 70 | 70 | pair := s | |
| 71 | 71 | pair.Windows = []ports.SceneWindow{ | |
| 72 | 72 | {ID: 1, Rect: ports.Rect{X: 0, Y: 0, W: 16, H: 16}, Inset: ports.SideRight, Neighbors: ports.SideRight}, | |
| 73 | 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 | 76 | 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 | 78 | pair.Windows[0].Focused = false | |
| 79 | 79 | check(pair, map[image.Point]color.RGBA{{14, 5}: gray, {15, 5}: gray}) | |
| 80 | 80 | 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 = true | 65 | 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 right | 67 | + | // 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 := s | 70 | 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, true | 76 | 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 = false | 78 | 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 = true | 80 | s.Windows[0].Fullscreen = true | ||