fix(focus): flash the focused window on click focus
- commit
- 0c04aee60aa73b5c3ac719f4fb2075628f2ca2a3
- author
- brice · · 2026-10-21 14:33 UTC
- committer
- brice · · 2026-10-21 15:07 UTC
- parent
- ec21f32
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +46 -4
M internal/core/flash_test.go +42 -4
| @@ -389,11 +389,8 @@ func TestFlashNoImplicitFocusTriggers(t *testing.T) { | |||
| 389 | 389 | if err := c.pointerMotion(ctx, ports.PointerMotion{X: 50, Y: 50}); err != nil { | |
| 390 | 390 | t.Fatal(err) | |
| 391 | 391 | } | |
| 392 | - | if err := c.pointerButton(ctx, ports.PointerButton{Button: btnLeft, Pressed: true}); err != nil { | |
| 393 | - | t.Fatal(err) | |
| 394 | - | } | |
| 395 | 392 | if c.flash.pending != nil { | |
| 396 | - | t.Fatal("pointer focus flashed") | |
| 393 | + | t.Fatal("hover focus flashed") | |
| 397 | 394 | } | |
| 398 | 395 | } | |
| 399 | 396 | ||
| @@ -774,3 +771,44 @@ func TestFlashFollowsInnerBorderAndRadius(t *testing.T) { | |||
| 774 | 771 | }) | |
| 775 | 772 | } | |
| 776 | 773 | } | |
| 774 | + | ||
| 775 | + | func TestFlashClickFocus(t *testing.T) { | |
| 776 | + | for _, test := range []struct { | |
| 777 | + | name string | |
| 778 | + | target WindowID | |
| 779 | + | enabled bool | |
| 780 | + | want bool | |
| 781 | + | }{ | |
| 782 | + | {"different window", 1, true, true}, | |
| 783 | + | {"already focused", 3, true, false}, | |
| 784 | + | {"disabled", 1, false, false}, | |
| 785 | + | } { | |
| 786 | + | t.Run(test.name, func(t *testing.T) { | |
| 787 | + | c, clock := flashCore(t) | |
| 788 | + | c.cfg.Focus.Flash = test.enabled | |
| 789 | + | indicatorScene(t, c) | |
| 790 | + | if c.keyboardFocus() != 3 { | |
| 791 | + | t.Fatal("setup: expected window 3 focused") | |
| 792 | + | } | |
| 793 | + | c.pointer = test.target | |
| 794 | + | if err := c.pointerButton(context.Background(), ports.PointerButton{Button: btnLeft, Pressed: true}); err != nil { | |
| 795 | + | t.Fatal(err) | |
| 796 | + | } | |
| 797 | + | if c.keyboardFocus() != test.target { | |
| 798 | + | t.Fatalf("focus = %d, want %d", c.keyboardFocus(), test.target) | |
| 799 | + | } | |
| 800 | + | if (c.flash.pending != nil) != test.want { | |
| 801 | + | t.Fatalf("flash request = %+v, want flash %v", c.flash.pending, test.want) | |
| 802 | + | } | |
| 803 | + | // Let the click's viewport transition settle before the flash. | |
| 804 | + | clock.now = clock.now.Add(time.Second) | |
| 805 | + | stepScenes(t, c, c.cur()) | |
| 806 | + | if c.flashTick() != test.want { | |
| 807 | + | t.Fatalf("flash start: want %v", test.want) | |
| 808 | + | } | |
| 809 | + | if test.want && (c.flash.running.window != test.target || c.flash.running.level != flashWindow || c.flashFor(c.cur()) == nil) { | |
| 810 | + | t.Fatalf("wrong running flash: %+v", c.flash.running) | |
| 811 | + | } | |
| 812 | + | }) | |
| 813 | + | } | |
| 814 | + | } | |
| @@ -389,11 +389,8 @@ func TestFlashNoImplicitFocusTriggers(t *testing.T) { | |||||
| 389 | if err := c.pointerMotion(ctx, ports.PointerMotion{X: 50, Y: 50}); err != nil { | 389 | if err := c.pointerMotion(ctx, ports.PointerMotion{X: 50, Y: 50}); err != nil { | ||
| 390 | t.Fatal(err) | 390 | t.Fatal(err) | ||
| 391 | } | 391 | } | ||
| 392 | - | if err := c.pointerButton(ctx, ports.PointerButton{Button: btnLeft, Pressed: true}); err != nil { | |||
| 393 | - | t.Fatal(err) | |||
| 394 | - | } | |||
| 395 | if c.flash.pending != nil { | 392 | if c.flash.pending != nil { | ||
| 396 | - | t.Fatal("pointer focus flashed") | 393 | + | t.Fatal("hover focus flashed") |
| 397 | } | 394 | } | ||
| 398 | } | 395 | } | ||
| 399 | 396 | ||||
| @@ -774,3 +771,44 @@ func TestFlashFollowsInnerBorderAndRadius(t *testing.T) { | |||||
| 774 | }) | 771 | }) | ||
| 775 | } | 772 | } | ||
| 776 | } | 773 | } | ||
| 774 | + | ||||
| 775 | + | func TestFlashClickFocus(t *testing.T) { | |||
| 776 | + | for _, test := range []struct { | |||
| 777 | + | name string | |||
| 778 | + | target WindowID | |||
| 779 | + | enabled bool | |||
| 780 | + | want bool | |||
| 781 | + | }{ | |||
| 782 | + | {"different window", 1, true, true}, | |||
| 783 | + | {"already focused", 3, true, false}, | |||
| 784 | + | {"disabled", 1, false, false}, | |||
| 785 | + | } { | |||
| 786 | + | t.Run(test.name, func(t *testing.T) { | |||
| 787 | + | c, clock := flashCore(t) | |||
| 788 | + | c.cfg.Focus.Flash = test.enabled | |||
| 789 | + | indicatorScene(t, c) | |||
| 790 | + | if c.keyboardFocus() != 3 { | |||
| 791 | + | t.Fatal("setup: expected window 3 focused") | |||
| 792 | + | } | |||
| 793 | + | c.pointer = test.target | |||
| 794 | + | if err := c.pointerButton(context.Background(), ports.PointerButton{Button: btnLeft, Pressed: true}); err != nil { | |||
| 795 | + | t.Fatal(err) | |||
| 796 | + | } | |||
| 797 | + | if c.keyboardFocus() != test.target { | |||
| 798 | + | t.Fatalf("focus = %d, want %d", c.keyboardFocus(), test.target) | |||
| 799 | + | } | |||
| 800 | + | if (c.flash.pending != nil) != test.want { | |||
| 801 | + | t.Fatalf("flash request = %+v, want flash %v", c.flash.pending, test.want) | |||
| 802 | + | } | |||
| 803 | + | // Let the click's viewport transition settle before the flash. | |||
| 804 | + | clock.now = clock.now.Add(time.Second) | |||
| 805 | + | stepScenes(t, c, c.cur()) | |||
| 806 | + | if c.flashTick() != test.want { | |||
| 807 | + | t.Fatalf("flash start: want %v", test.want) | |||
| 808 | + | } | |||
| 809 | + | if test.want && (c.flash.running.window != test.target || c.flash.running.level != flashWindow || c.flashFor(c.cur()) == nil) { | |||
| 810 | + | t.Fatalf("wrong running flash: %+v", c.flash.running) | |||
| 811 | + | } | |||
| 812 | + | }) | |||
| 813 | + | } | |||
| 814 | + | } | |||
M internal/core/input.go +4 -0
| @@ -134,11 +134,15 @@ func (c *Core) clickFocus(ctx context.Context, id WindowID) error { | |||
| 134 | 134 | if s == nil || w != s.mon.Current() || (c.keyboard.sent == id && s == c.cur()) { | |
| 135 | 135 | return nil | |
| 136 | 136 | } | |
| 137 | + | focused, _ := c.cur().mon.Focused() | |
| 137 | 138 | now := c.now() | |
| 138 | 139 | before := c.snapshot(now) | |
| 139 | 140 | w.Click(id) | |
| 140 | 141 | c.focusScreen = c.screenIndex(s.name()) | |
| 141 | 142 | c.transition(before, now) | |
| 143 | + | if focused != id { | |
| 144 | + | c.requestFlash(flashWindow, id) | |
| 145 | + | } | |
| 142 | 146 | return c.publish(ctx) | |
| 143 | 147 | } | |
| 144 | 148 | ||
| @@ -134,11 +134,15 @@ func (c *Core) clickFocus(ctx context.Context, id WindowID) error { | |||||
| 134 | if s == nil || w != s.mon.Current() || (c.keyboard.sent == id && s == c.cur()) { | 134 | if s == nil || w != s.mon.Current() || (c.keyboard.sent == id && s == c.cur()) { | ||
| 135 | return nil | 135 | return nil | ||
| 136 | } | 136 | } | ||
| 137 | + | focused, _ := c.cur().mon.Focused() | |||
| 137 | now := c.now() | 138 | now := c.now() | ||
| 138 | before := c.snapshot(now) | 139 | before := c.snapshot(now) | ||
| 139 | w.Click(id) | 140 | w.Click(id) | ||
| 140 | c.focusScreen = c.screenIndex(s.name()) | 141 | c.focusScreen = c.screenIndex(s.name()) | ||
| 141 | c.transition(before, now) | 142 | c.transition(before, now) | ||
| 143 | + | if focused != id { | |||
| 144 | + | c.requestFlash(flashWindow, id) | |||
| 145 | + | } | |||
| 142 | return c.publish(ctx) | 146 | return c.publish(ctx) | ||
| 143 | } | 147 | } | ||
| 144 | 148 | ||||