perf(core): advance only the flipped output's springs and keep the pulse value between its flips
- commit
- c0fdb38d8b6640a992570c785d5b646882df3e84
- author
- brice · · 2026-10-04 15:44 UTC
- committer
- brice · · 2026-10-04 19:08 UTC
- parent
- d8dfa1d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +171 -18
M internal/core/core.go +19 -9
| @@ -130,6 +130,9 @@ type Core struct { | |||
| 130 | 130 | swipe *swipeGesture | |
| 131 | 131 | frameC <-chan time.Time | |
| 132 | 132 | frameStop func() bool | |
| 133 | + | // stepping is the screen a page flip advances (nil: all of them); it is | |
| 134 | + | // set while step publishes. | |
| 135 | + | stepping *screen | |
| 133 | 136 | // motionTime is the time of the last pointer motion; pointerAt is the | |
| 134 | 137 | // last position sent in the pointer's window. | |
| 135 | 138 | motionTime time.Duration | |
| @@ -530,10 +533,14 @@ func (c *Core) publish(ctx context.Context) error { | |||
| 530 | 533 | focus := c.keyboardFocus() | |
| 531 | 534 | window, _ := c.cur().mon.Focused() | |
| 532 | 535 | c.pulseFocus(window, focus == window) | |
| 533 | - | var pulse float64 | |
| 534 | - | if c.pulse.id != 0 { | |
| 535 | - | pulse = c.advancePulse(c.now()) | |
| 536 | - | } | |
| 536 | + | // The pulse runs on the focused output: a flip of another output reuses | |
| 537 | + | // its value so the focused scene keeps its Seq. | |
| 538 | + | if c.pulse.id == 0 { | |
| 539 | + | c.pulse.value = 0 | |
| 540 | + | } else if c.stepping == nil || c.stepping == c.cur() { | |
| 541 | + | c.pulse.value = c.advancePulse(c.now()) | |
| 542 | + | } | |
| 543 | + | pulse := c.pulse.value | |
| 537 | 544 | // drawable: the scene can show a pulse on the focused window. | |
| 538 | 545 | drawable := false | |
| 539 | 546 | scenes := make([]ports.Scene, 0, len(c.screens)) | |
| @@ -679,10 +686,13 @@ func (c *Core) publish(ctx context.Context) error { | |||
| 679 | 686 | return nil | |
| 680 | 687 | } | |
| 681 | 688 | ||
| 682 | - | // step moves the running slides to now and publishes the frame. | |
| 683 | - | func (c *Core) step(ctx context.Context) error { | |
| 689 | + | // step moves the running slides of only (nil: every screen) to now and | |
| 690 | + | // publishes the frame. | |
| 691 | + | func (c *Core) step(ctx context.Context, only *screen) error { | |
| 684 | 692 | c.stopFrame() | |
| 685 | - | c.animate(c.now()) | |
| 693 | + | c.animate(c.now(), only) | |
| 694 | + | c.stepping = only | |
| 695 | + | defer func() { c.stepping = nil }() | |
| 686 | 696 | return c.slid(ctx, false) | |
| 687 | 697 | } | |
| 688 | 698 | ||
| @@ -1156,7 +1166,7 @@ func (c *Core) Run(ctx context.Context) error { | |||
| 1156 | 1166 | c.ch.Frames = nil | |
| 1157 | 1167 | continue | |
| 1158 | 1168 | } | |
| 1159 | - | if c.sliding(f.Output) && c.step(ctx) != nil { | |
| 1169 | + | if i := c.screenIndex(f.Output); i >= 0 && c.sliding(f.Output) && c.step(ctx, c.screens[i]) != nil { | |
| 1160 | 1170 | return nil | |
| 1161 | 1171 | } | |
| 1162 | 1172 | continue | |
| @@ -1165,7 +1175,7 @@ func (c *Core) Run(ctx context.Context) error { | |||
| 1165 | 1175 | return nil | |
| 1166 | 1176 | } | |
| 1167 | 1177 | c.frameC, c.frameStop = nil, nil | |
| 1168 | - | if c.step(ctx) != nil { | |
| 1178 | + | if c.step(ctx, nil) != nil { | |
| 1169 | 1179 | return nil | |
| 1170 | 1180 | } | |
| 1171 | 1181 | continue | |
| @@ -130,6 +130,9 @@ type Core struct { | |||||
| 130 | swipe *swipeGesture | 130 | swipe *swipeGesture | ||
| 131 | frameC <-chan time.Time | 131 | frameC <-chan time.Time | ||
| 132 | frameStop func() bool | 132 | frameStop func() bool | ||
| 133 | + | // stepping is the screen a page flip advances (nil: all of them); it is | |||
| 134 | + | // set while step publishes. | |||
| 135 | + | stepping *screen | |||
| 133 | // motionTime is the time of the last pointer motion; pointerAt is the | 136 | // motionTime is the time of the last pointer motion; pointerAt is the | ||
| 134 | // last position sent in the pointer's window. | 137 | // last position sent in the pointer's window. | ||
| 135 | motionTime time.Duration | 138 | motionTime time.Duration | ||
| @@ -530,10 +533,14 @@ func (c *Core) publish(ctx context.Context) error { | |||||
| 530 | focus := c.keyboardFocus() | 533 | focus := c.keyboardFocus() | ||
| 531 | window, _ := c.cur().mon.Focused() | 534 | window, _ := c.cur().mon.Focused() | ||
| 532 | c.pulseFocus(window, focus == window) | 535 | c.pulseFocus(window, focus == window) | ||
| 533 | - | var pulse float64 | 536 | + | // The pulse runs on the focused output: a flip of another output reuses |
| 534 | - | if c.pulse.id != 0 { | 537 | + | // its value so the focused scene keeps its Seq. |
| 535 | - | pulse = c.advancePulse(c.now()) | 538 | + | if c.pulse.id == 0 { |
| 536 | - | } | 539 | + | c.pulse.value = 0 |
| 540 | + | } else if c.stepping == nil || c.stepping == c.cur() { | |||
| 541 | + | c.pulse.value = c.advancePulse(c.now()) | |||
| 542 | + | } | |||
| 543 | + | pulse := c.pulse.value | |||
| 537 | // drawable: the scene can show a pulse on the focused window. | 544 | // drawable: the scene can show a pulse on the focused window. | ||
| 538 | drawable := false | 545 | drawable := false | ||
| 539 | scenes := make([]ports.Scene, 0, len(c.screens)) | 546 | scenes := make([]ports.Scene, 0, len(c.screens)) | ||
| @@ -679,10 +686,13 @@ func (c *Core) publish(ctx context.Context) error { | |||||
| 679 | return nil | 686 | return nil | ||
| 680 | } | 687 | } | ||
| 681 | 688 | ||||
| 682 | - | // step moves the running slides to now and publishes the frame. | 689 | + | // step moves the running slides of only (nil: every screen) to now and |
| 683 | - | func (c *Core) step(ctx context.Context) error { | 690 | + | // publishes the frame. |
| 691 | + | func (c *Core) step(ctx context.Context, only *screen) error { | |||
| 684 | c.stopFrame() | 692 | c.stopFrame() | ||
| 685 | - | c.animate(c.now()) | 693 | + | c.animate(c.now(), only) |
| 694 | + | c.stepping = only | |||
| 695 | + | defer func() { c.stepping = nil }() | |||
| 686 | return c.slid(ctx, false) | 696 | return c.slid(ctx, false) | ||
| 687 | } | 697 | } | ||
| 688 | 698 | ||||
| @@ -1156,7 +1166,7 @@ func (c *Core) Run(ctx context.Context) error { | |||||
| 1156 | c.ch.Frames = nil | 1166 | c.ch.Frames = nil | ||
| 1157 | continue | 1167 | continue | ||
| 1158 | } | 1168 | } | ||
| 1159 | - | if c.sliding(f.Output) && c.step(ctx) != nil { | 1169 | + | if i := c.screenIndex(f.Output); i >= 0 && c.sliding(f.Output) && c.step(ctx, c.screens[i]) != nil { |
| 1160 | return nil | 1170 | return nil | ||
| 1161 | } | 1171 | } | ||
| 1162 | continue | 1172 | continue | ||
| @@ -1165,7 +1175,7 @@ func (c *Core) Run(ctx context.Context) error { | |||||
| 1165 | return nil | 1175 | return nil | ||
| 1166 | } | 1176 | } | ||
| 1167 | c.frameC, c.frameStop = nil, nil | 1177 | c.frameC, c.frameStop = nil, nil | ||
| 1168 | - | if c.step(ctx) != nil { | 1178 | + | if c.step(ctx, nil) != nil { |
| 1169 | return nil | 1179 | return nil | ||
| 1170 | } | 1180 | } | ||
| 1171 | continue | 1181 | continue | ||
A internal/core/frame_output_test.go +67 -0
| @@ -0,0 +1,67 @@ | |||
| 1 | + | package core_test | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | "time" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/neferwl/internal/ports" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func sceneFor(set []ports.Scene, output string) ports.Scene { | |
| 11 | + | for _, s := range set { | |
| 12 | + | if s.Output == output { | |
| 13 | + | return s | |
| 14 | + | } | |
| 15 | + | } | |
| 16 | + | return ports.Scene{} | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | // A page flip of one output advances only that output's springs: the other | |
| 20 | + | // output keeps its scene (and Seq) until its own flip. | |
| 21 | + | func TestFlipAdvancesOnlyItsOutput(t *testing.T) { | |
| 22 | + | r := startSwipe(t, nil) | |
| 23 | + | threeColumns(t, r) | |
| 24 | + | second := ports.OutputInfo{Name: "DP-2", Width: 800, Height: 600, RefreshMilli: 60000} | |
| 25 | + | r.plug(t, second) | |
| 26 | + | r.input <- ports.PointerMotion{X: 1000, Y: 300} | |
| 27 | + | scene(t, r.scenes) | |
| 28 | + | for id := ports.WindowID(4); id <= 6; id++ { | |
| 29 | + | r.mapWindow(t, id) | |
| 30 | + | } | |
| 31 | + | // A running landing on each output. | |
| 32 | + | r.input <- ports.PointerMotion{X: 100, Y: 300} | |
| 33 | + | scene(t, r.scenes) | |
| 34 | + | r.flick(t, 10, -40, 0) | |
| 35 | + | r.input <- ports.PointerMotion{X: 1000, Y: 300} | |
| 36 | + | scene(t, r.scenes) | |
| 37 | + | r.flick(t, 10, -40, 0) | |
| 38 | + | r.drain() | |
| 39 | + | ||
| 40 | + | // The first flip of each output moves its columns. | |
| 41 | + | r.advance(32 * time.Millisecond) | |
| 42 | + | r.frames <- ports.OutputFrame{Output: wide.Name} | |
| 43 | + | set := receive(t, r.scenes) | |
| 44 | + | a, b := sceneFor(set, wide.Name), sceneFor(set, second.Name) | |
| 45 | + | r.advance(32 * time.Millisecond) | |
| 46 | + | r.frames <- ports.OutputFrame{Output: second.Name} | |
| 47 | + | set = receive(t, r.scenes) | |
| 48 | + | a2, b2 := sceneFor(set, wide.Name), sceneFor(set, second.Name) | |
| 49 | + | if a2.Seq != a.Seq || !a2.SameAs(a) { | |
| 50 | + | t.Fatalf("DP-1 changed on a flip of DP-2: Seq %d, was %d", a2.Seq, a.Seq) | |
| 51 | + | } | |
| 52 | + | if b2.Seq <= b.Seq || b2.SameAs(b) { | |
| 53 | + | t.Fatalf("DP-2 did not move on its own flip: Seq %d, was %d", b2.Seq, b.Seq) | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | // And the other way round. | |
| 57 | + | r.advance(32 * time.Millisecond) | |
| 58 | + | r.frames <- ports.OutputFrame{Output: wide.Name} | |
| 59 | + | set = receive(t, r.scenes) | |
| 60 | + | a3, b3 := sceneFor(set, wide.Name), sceneFor(set, second.Name) | |
| 61 | + | if a3.Seq <= a2.Seq || a3.SameAs(a2) { | |
| 62 | + | t.Fatalf("DP-1 did not move on its own flip: Seq %d, was %d", a3.Seq, a2.Seq) | |
| 63 | + | } | |
| 64 | + | if b3.Seq != b2.Seq || !b3.SameAs(b2) { | |
| 65 | + | t.Fatalf("DP-2 changed on a flip of DP-1: Seq %d, was %d", b3.Seq, b2.Seq) | |
| 66 | + | } | |
| 67 | + | } | |
| @@ -0,0 +1,67 @@ | |||||
| 1 | + | package core_test | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | "time" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/neferwl/internal/ports" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func sceneFor(set []ports.Scene, output string) ports.Scene { | |||
| 11 | + | for _, s := range set { | |||
| 12 | + | if s.Output == output { | |||
| 13 | + | return s | |||
| 14 | + | } | |||
| 15 | + | } | |||
| 16 | + | return ports.Scene{} | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | // A page flip of one output advances only that output's springs: the other | |||
| 20 | + | // output keeps its scene (and Seq) until its own flip. | |||
| 21 | + | func TestFlipAdvancesOnlyItsOutput(t *testing.T) { | |||
| 22 | + | r := startSwipe(t, nil) | |||
| 23 | + | threeColumns(t, r) | |||
| 24 | + | second := ports.OutputInfo{Name: "DP-2", Width: 800, Height: 600, RefreshMilli: 60000} | |||
| 25 | + | r.plug(t, second) | |||
| 26 | + | r.input <- ports.PointerMotion{X: 1000, Y: 300} | |||
| 27 | + | scene(t, r.scenes) | |||
| 28 | + | for id := ports.WindowID(4); id <= 6; id++ { | |||
| 29 | + | r.mapWindow(t, id) | |||
| 30 | + | } | |||
| 31 | + | // A running landing on each output. | |||
| 32 | + | r.input <- ports.PointerMotion{X: 100, Y: 300} | |||
| 33 | + | scene(t, r.scenes) | |||
| 34 | + | r.flick(t, 10, -40, 0) | |||
| 35 | + | r.input <- ports.PointerMotion{X: 1000, Y: 300} | |||
| 36 | + | scene(t, r.scenes) | |||
| 37 | + | r.flick(t, 10, -40, 0) | |||
| 38 | + | r.drain() | |||
| 39 | + | ||||
| 40 | + | // The first flip of each output moves its columns. | |||
| 41 | + | r.advance(32 * time.Millisecond) | |||
| 42 | + | r.frames <- ports.OutputFrame{Output: wide.Name} | |||
| 43 | + | set := receive(t, r.scenes) | |||
| 44 | + | a, b := sceneFor(set, wide.Name), sceneFor(set, second.Name) | |||
| 45 | + | r.advance(32 * time.Millisecond) | |||
| 46 | + | r.frames <- ports.OutputFrame{Output: second.Name} | |||
| 47 | + | set = receive(t, r.scenes) | |||
| 48 | + | a2, b2 := sceneFor(set, wide.Name), sceneFor(set, second.Name) | |||
| 49 | + | if a2.Seq != a.Seq || !a2.SameAs(a) { | |||
| 50 | + | t.Fatalf("DP-1 changed on a flip of DP-2: Seq %d, was %d", a2.Seq, a.Seq) | |||
| 51 | + | } | |||
| 52 | + | if b2.Seq <= b.Seq || b2.SameAs(b) { | |||
| 53 | + | t.Fatalf("DP-2 did not move on its own flip: Seq %d, was %d", b2.Seq, b.Seq) | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | // And the other way round. | |||
| 57 | + | r.advance(32 * time.Millisecond) | |||
| 58 | + | r.frames <- ports.OutputFrame{Output: wide.Name} | |||
| 59 | + | set = receive(t, r.scenes) | |||
| 60 | + | a3, b3 := sceneFor(set, wide.Name), sceneFor(set, second.Name) | |||
| 61 | + | if a3.Seq <= a2.Seq || a3.SameAs(a2) { | |||
| 62 | + | t.Fatalf("DP-1 did not move on its own flip: Seq %d, was %d", a3.Seq, a2.Seq) | |||
| 63 | + | } | |||
| 64 | + | if b3.Seq != b2.Seq || !b3.SameAs(b2) { | |||
| 65 | + | t.Fatalf("DP-2 changed on a flip of DP-1: Seq %d, was %d", b3.Seq, b2.Seq) | |||
| 66 | + | } | |||
| 67 | + | } | |||
A internal/core/frame_pulse_test.go +69 -0
| @@ -0,0 +1,69 @@ | |||
| 1 | + | package core | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "testing" | |
| 6 | + | "time" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/neferwl/internal/ports" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // stepScenes steps only the screen and returns the scene of each output. | |
| 12 | + | func stepScenes(t *testing.T, c *Core, only *screen) map[string]ports.Scene { | |
| 13 | + | t.Helper() | |
| 14 | + | if err := c.step(context.Background(), only); err != nil { | |
| 15 | + | t.Fatal(err) | |
| 16 | + | } | |
| 17 | + | out := map[string]ports.Scene{} | |
| 18 | + | for _, s := range <-c.ch.Scenes { | |
| 19 | + | out[s.Output] = s | |
| 20 | + | } | |
| 21 | + | return out | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | // While the focus pulse runs on the focused output, a flip of another output | |
| 25 | + | // keeps the focused output's Seq; its own flip samples the pulse. | |
| 26 | + | func TestFlipOfOtherOutputKeepsPulseSeq(t *testing.T) { | |
| 27 | + | c, ic := pulseCore(t) | |
| 28 | + | a := c.cur() | |
| 29 | + | c.addScreen(ports.OutputInfo{Name: "B", Width: 300, Height: 200}) | |
| 30 | + | b := c.screens[1] | |
| 31 | + | for id := WindowID(3); id <= 5; id++ { | |
| 32 | + | b.mon.AddWindow(id) | |
| 33 | + | } | |
| 34 | + | indicatorScene(t, c) | |
| 35 | + | ||
| 36 | + | // A pulse on window 1 of A. | |
| 37 | + | a.mon.Current().FocusID(1) | |
| 38 | + | indicatorScene(t, c) | |
| 39 | + | ic.now = ic.now.Add(pulseSettle) | |
| 40 | + | if !c.pulseTick() { | |
| 41 | + | t.Fatal("no pulse after settle") | |
| 42 | + | } | |
| 43 | + | // A camera transition runs on B. | |
| 44 | + | w := b.mon.Current() | |
| 45 | + | w.shift = 100 | |
| 46 | + | w.motion = newMotion(viewSpring(w.shift, 0), ic.now, 1) | |
| 47 | + | ||
| 48 | + | ic.now = ic.now.Add(pulseRise / 2) | |
| 49 | + | first := stepScenes(t, c, a) | |
| 50 | + | if pulseOf(first["A"], 1) <= 0 { | |
| 51 | + | t.Fatalf("no pulse on A: %v", pulseOf(first["A"], 1)) | |
| 52 | + | } | |
| 53 | + | ic.now = ic.now.Add(10 * time.Millisecond) | |
| 54 | + | second := stepScenes(t, c, b) | |
| 55 | + | if second["A"].Seq != first["A"].Seq || !second["A"].SameAs(first["A"]) { | |
| 56 | + | t.Fatalf("A changed on a flip of B: Seq %d, was %d", second["A"].Seq, first["A"].Seq) | |
| 57 | + | } | |
| 58 | + | if second["B"].SameAs(first["B"]) { | |
| 59 | + | t.Fatal("B did not move on its own flip") | |
| 60 | + | } | |
| 61 | + | ic.now = ic.now.Add(10 * time.Millisecond) | |
| 62 | + | third := stepScenes(t, c, a) | |
| 63 | + | if third["A"].Seq <= second["A"].Seq || pulseOf(third["A"], 1) == pulseOf(second["A"], 1) { | |
| 64 | + | t.Fatalf("A did not sample the pulse on its own flip: Seq %d, was %d", third["A"].Seq, second["A"].Seq) | |
| 65 | + | } | |
| 66 | + | if third["B"].Seq != second["B"].Seq { | |
| 67 | + | t.Fatalf("B changed on a flip of A: Seq %d, was %d", third["B"].Seq, second["B"].Seq) | |
| 68 | + | } | |
| 69 | + | } | |
| @@ -0,0 +1,69 @@ | |||||
| 1 | + | package core | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "testing" | |||
| 6 | + | "time" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/neferwl/internal/ports" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // stepScenes steps only the screen and returns the scene of each output. | |||
| 12 | + | func stepScenes(t *testing.T, c *Core, only *screen) map[string]ports.Scene { | |||
| 13 | + | t.Helper() | |||
| 14 | + | if err := c.step(context.Background(), only); err != nil { | |||
| 15 | + | t.Fatal(err) | |||
| 16 | + | } | |||
| 17 | + | out := map[string]ports.Scene{} | |||
| 18 | + | for _, s := range <-c.ch.Scenes { | |||
| 19 | + | out[s.Output] = s | |||
| 20 | + | } | |||
| 21 | + | return out | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | // While the focus pulse runs on the focused output, a flip of another output | |||
| 25 | + | // keeps the focused output's Seq; its own flip samples the pulse. | |||
| 26 | + | func TestFlipOfOtherOutputKeepsPulseSeq(t *testing.T) { | |||
| 27 | + | c, ic := pulseCore(t) | |||
| 28 | + | a := c.cur() | |||
| 29 | + | c.addScreen(ports.OutputInfo{Name: "B", Width: 300, Height: 200}) | |||
| 30 | + | b := c.screens[1] | |||
| 31 | + | for id := WindowID(3); id <= 5; id++ { | |||
| 32 | + | b.mon.AddWindow(id) | |||
| 33 | + | } | |||
| 34 | + | indicatorScene(t, c) | |||
| 35 | + | ||||
| 36 | + | // A pulse on window 1 of A. | |||
| 37 | + | a.mon.Current().FocusID(1) | |||
| 38 | + | indicatorScene(t, c) | |||
| 39 | + | ic.now = ic.now.Add(pulseSettle) | |||
| 40 | + | if !c.pulseTick() { | |||
| 41 | + | t.Fatal("no pulse after settle") | |||
| 42 | + | } | |||
| 43 | + | // A camera transition runs on B. | |||
| 44 | + | w := b.mon.Current() | |||
| 45 | + | w.shift = 100 | |||
| 46 | + | w.motion = newMotion(viewSpring(w.shift, 0), ic.now, 1) | |||
| 47 | + | ||||
| 48 | + | ic.now = ic.now.Add(pulseRise / 2) | |||
| 49 | + | first := stepScenes(t, c, a) | |||
| 50 | + | if pulseOf(first["A"], 1) <= 0 { | |||
| 51 | + | t.Fatalf("no pulse on A: %v", pulseOf(first["A"], 1)) | |||
| 52 | + | } | |||
| 53 | + | ic.now = ic.now.Add(10 * time.Millisecond) | |||
| 54 | + | second := stepScenes(t, c, b) | |||
| 55 | + | if second["A"].Seq != first["A"].Seq || !second["A"].SameAs(first["A"]) { | |||
| 56 | + | t.Fatalf("A changed on a flip of B: Seq %d, was %d", second["A"].Seq, first["A"].Seq) | |||
| 57 | + | } | |||
| 58 | + | if second["B"].SameAs(first["B"]) { | |||
| 59 | + | t.Fatal("B did not move on its own flip") | |||
| 60 | + | } | |||
| 61 | + | ic.now = ic.now.Add(10 * time.Millisecond) | |||
| 62 | + | third := stepScenes(t, c, a) | |||
| 63 | + | if third["A"].Seq <= second["A"].Seq || pulseOf(third["A"], 1) == pulseOf(second["A"], 1) { | |||
| 64 | + | t.Fatalf("A did not sample the pulse on its own flip: Seq %d, was %d", third["A"].Seq, second["A"].Seq) | |||
| 65 | + | } | |||
| 66 | + | if third["B"].Seq != second["B"].Seq { | |||
| 67 | + | t.Fatalf("B changed on a flip of A: Seq %d, was %d", third["B"].Seq, second["B"].Seq) | |||
| 68 | + | } | |||
| 69 | + | } | |||
M internal/core/gesture.go +6 -2
| @@ -353,9 +353,13 @@ func (c *Core) stopAnimations() { | |||
| 353 | 353 | c.stopFrame() | |
| 354 | 354 | } | |
| 355 | 355 | ||
| 356 | - | // animate moves the running springs to now; settled ones stop. | |
| 357 | - | func (c *Core) animate(now time.Time) { | |
| 356 | + | // animate moves the running springs to now; settled ones stop. A non-nil only | |
| 357 | + | // limits it to that screen (its own page flip); nil moves every screen. | |
| 358 | + | func (c *Core) animate(now time.Time, only *screen) { | |
| 358 | 359 | for _, sc := range c.screens { | |
| 360 | + | if only != nil && sc != only { | |
| 361 | + | continue | |
| 362 | + | } | |
| 359 | 363 | m := sc.mon | |
| 360 | 364 | if m.switchMotion.on { | |
| 361 | 365 | v, done := m.switchMotion.at(now) | |
| @@ -353,9 +353,13 @@ func (c *Core) stopAnimations() { | |||||
| 353 | c.stopFrame() | 353 | c.stopFrame() | ||
| 354 | } | 354 | } | ||
| 355 | 355 | ||||
| 356 | - | // animate moves the running springs to now; settled ones stop. | 356 | + | // animate moves the running springs to now; settled ones stop. A non-nil only |
| 357 | - | func (c *Core) animate(now time.Time) { | 357 | + | // limits it to that screen (its own page flip); nil moves every screen. |
| 358 | + | func (c *Core) animate(now time.Time, only *screen) { | |||
| 358 | for _, sc := range c.screens { | 359 | for _, sc := range c.screens { | ||
| 360 | + | if only != nil && sc != only { | |||
| 361 | + | continue | |||
| 362 | + | } | |||
| 359 | m := sc.mon | 363 | m := sc.mon | ||
| 360 | if m.switchMotion.on { | 364 | if m.switchMotion.on { | ||
| 361 | v, done := m.switchMotion.at(now) | 365 | v, done := m.switchMotion.at(now) | ||
M internal/core/pulse.go +10 -7
| @@ -32,13 +32,16 @@ const ( | |||
| 32 | 32 | // are the previous pulse, for the cooldown. drawable is whether the last | |
| 33 | 33 | // scene could show a pulse on target. | |
| 34 | 34 | type focusPulse struct { | |
| 35 | - | target WindowID | |
| 36 | - | since time.Time | |
| 37 | - | id WindowID | |
| 38 | - | start time.Time | |
| 39 | - | last WindowID | |
| 40 | - | lastAt time.Time | |
| 41 | - | drawable bool | |
| 35 | + | target WindowID | |
| 36 | + | since time.Time | |
| 37 | + | id WindowID | |
| 38 | + | start time.Time | |
| 39 | + | last WindowID | |
| 40 | + | lastAt time.Time | |
| 41 | + | drawable bool | |
| 42 | + | // value is the effect of the last sample, kept for scenes published | |
| 43 | + | // between the focused output's own flips. | |
| 44 | + | value float64 | |
| 42 | 45 | timerC <-chan time.Time | |
| 43 | 46 | timerStop func() bool | |
| 44 | 47 | } | |
| @@ -32,13 +32,16 @@ const ( | |||||
| 32 | // are the previous pulse, for the cooldown. drawable is whether the last | 32 | // are the previous pulse, for the cooldown. drawable is whether the last | ||
| 33 | // scene could show a pulse on target. | 33 | // scene could show a pulse on target. | ||
| 34 | type focusPulse struct { | 34 | type focusPulse struct { | ||
| 35 | - | target WindowID | 35 | + | target WindowID |
| 36 | - | since time.Time | 36 | + | since time.Time |
| 37 | - | id WindowID | 37 | + | id WindowID |
| 38 | - | start time.Time | 38 | + | start time.Time |
| 39 | - | last WindowID | 39 | + | last WindowID |
| 40 | - | lastAt time.Time | 40 | + | lastAt time.Time |
| 41 | - | drawable bool | 41 | + | drawable bool |
| 42 | + | // value is the effect of the last sample, kept for scenes published | |||
| 43 | + | // between the focused output's own flips. | |||
| 44 | + | value float64 | |||
| 42 | timerC <-chan time.Time | 45 | timerC <-chan time.Time | ||
| 43 | timerStop func() bool | 46 | timerStop func() bool | ||
| 44 | } | 47 | } | ||