diff --git a/sdk/replay/BlackBox.zig b/sdk/replay/BlackBox.zig index eec1711e5..5c3890c7e 100644 --- a/sdk/replay/BlackBox.zig +++ b/sdk/replay/BlackBox.zig @@ -6,9 +6,9 @@ //! person's input and any tape's. Each dvui event becomes a fixed record in `tape.events`' rings: //! pointer motion and buttons where they were, in the window's pixels, wheel ticks, keys down, //! repeated and up with their modifiers, text as it arrived. Focus and position events are left -//! out: dvui makes them from the others, and a replay's dvui will again. A frame costs a copy of its -//! events (ReleaseFast, measured: 0.5 ns a frame with none, 5 ns with four); the rings are 1.3 MB, -//! made the first time it records. +//! out: dvui makes them from the others, and a replay's dvui will again. A frame costs a copy of +//! its events (ReleaseFast, measured: 0.5 ns a frame with none, 5 ns with four); the rings are 1.3 +//! MB, made the first time it records. //! //! Any dvui app can keep one: dvui and `tape` only. Fizzy writes its stretches as captures //! (`fizzy.capture`) and replays them (plans/RECORDER_PLAN.md, step 3). A stream that is not dvui's @@ -68,9 +68,27 @@ pub fn record(self: *BlackBox, gpa: std.mem.Allocator) void { fn toRecord(e: *const dvui.Event) ?events.Event { return switch (e.evt) { .mouse => |me| switch (me.action) { - .motion => .{ .kind = .motion, .button = @backingInt(me.button), .mod = @backingInt(me.mod), .x = me.p.x, .y = me.p.y }, - .press => .{ .kind = .press, .button = @backingInt(me.button), .mod = @backingInt(me.mod), .x = me.p.x, .y = me.p.y }, - .release => .{ .kind = .release, .button = @backingInt(me.button), .mod = @backingInt(me.mod), .x = me.p.x, .y = me.p.y }, + .motion => .{ + .kind = .motion, + .button = @backingInt(me.button), + .mod = @backingInt(me.mod), + .x = me.p.x, + .y = me.p.y, + }, + .press => .{ + .kind = .press, + .button = @backingInt(me.button), + .mod = @backingInt(me.mod), + .x = me.p.x, + .y = me.p.y, + }, + .release => .{ + .kind = .release, + .button = @backingInt(me.button), + .mod = @backingInt(me.mod), + .x = me.p.x, + .y = me.p.y, + }, .wheel_x => |ticks| .{ .kind = .wheel_x, .mod = @backingInt(me.mod), .x = ticks }, .wheel_y => |ticks| .{ .kind = .wheel_y, .mod = @backingInt(me.mod), .x = ticks }, .focus, .position => null, diff --git a/sdk/replay/Capture.zig b/sdk/replay/Capture.zig index 762c015cc..ad1b12f8c 100644 --- a/sdk/replay/Capture.zig +++ b/sdk/replay/Capture.zig @@ -12,8 +12,8 @@ //! and every window at once, laid out as they lie on the desktop round the main one, by `all`. //! //! Asked for like a `Snapshot` (`request`, then `get` once taken): the next frame the app draws -//! is read back at its end (`endFrame`) and kept, as the raw premultiplied pixels it drew, until the -//! next is taken. Nothing is encoded here: the frame pays for one read back and one copy, and +//! is read back at its end (`endFrame`) and kept, as the raw premultiplied pixels it drew, until +//! the next is taken. Nothing is encoded here: the frame pays for one read back and one copy, and //! whoever wants an image file (the agent's bridge, in its own process) scales and encodes it. const Capture = @This(); @@ -148,7 +148,12 @@ const max_side: f32 = 8192; /// Every window, each where it lies round the main window, a later one over an earlier. fn everyWindow(arena: std.mem.Allocator, sources: []const Source) !Pixels { var span: dvui.Rect.Physical = .{ .w = @floatFromInt(sources[0].w), .h = @floatFromInt(sources[0].h) }; - for (sources[1..]) |s| span = span.unionWith(.{ .x = s.at.x, .y = s.at.y, .w = @floatFromInt(s.w), .h = @floatFromInt(s.h) }); + for (sources[1..]) |s| span = span.unionWith(.{ + .x = s.at.x, + .y = s.at.y, + .w = @floatFromInt(s.w), + .h = @floatFromInt(s.h), + }); const w: u32 = @intFromFloat(@min(@round(span.w), max_side)); const h: u32 = @intFromFloat(@min(@round(span.h), max_side)); const out = try arena.alloc(u8, @as(usize, w) * h * 4); diff --git a/sdk/replay/Input.zig b/sdk/replay/Input.zig index 611a8cc64..5720c22d3 100644 --- a/sdk/replay/Input.zig +++ b/sdk/replay/Input.zig @@ -192,7 +192,14 @@ fn pressChord(c: chord.Chord) void { // dvui keeps the last key event's modifiers as the window's, and every pointer event after // carries them: let go of the modifier, as a hand does, or the next click is a Ctrl-click. if (mod != .none) { - const modifier: dvui.enums.Key = if (c.mods.command) .left_command else if (c.mods.ctrl) .left_control else if (c.mods.alt) .left_alt else .left_shift; + const modifier: dvui.enums.Key = if (c.mods.command) + .left_command + else if (c.mods.ctrl) + .left_control + else if (c.mods.alt) + .left_alt + else + .left_shift; addKey(cw, .{ .code = modifier, .mod = .none, .action = .up }); } } diff --git a/sdk/replay/LiveDriver.zig b/sdk/replay/LiveDriver.zig index fbc58c1f2..4ae34ec03 100644 --- a/sdk/replay/LiveDriver.zig +++ b/sdk/replay/LiveDriver.zig @@ -2,12 +2,12 @@ //! landed. The other way to play a tape besides a demo, and a driver of its own rather than a //! mode of the `Player` (`plans/AGENTS_PLAN.md`, "Driving a live tape"): //! -//! | | a demo (`Player`) | a live tape (this) | -//! |-----------------------|-------------------------------------|-----------------------------------| -//! | starts from | a keyframe; the session set aside | the app as it is (`Check.live`) | -//! | pacing | the tape's authored times | each op once the app has settled | -//! | a person steps in | pause, and replay on resume | stop, and say before which op | -//! | going back | seek: snapshots, keyframes | none — undo, through commands | +//! | | a demo (`Player`) | a live tape (this) | +//! | --- | --- | --- | +//! | starts from | a keyframe; the session set aside | the app as it is (`Check.live`) | +//! | pacing | the tape's authored times | each op once the app has settled | +//! | a person steps in | pause, and replay on resume | stop, and say before which op | +//! | going back | seek: snapshots, keyframes | none — undo, through commands | //! //! Consumers: a plugin's tests, a macro, a scripted tutorial, anything driving fizzy the way a //! person does. Plugins reach it through the `automation` service (`Service`). @@ -18,13 +18,13 @@ //! live tape while a demo is loaded, and fizzy's `Demo` a demo while a live tape plays). //! //! **Pacing.** Authored times are ignored: each frame the driver asks the sequencer for the next -//! moment anything happens (`Sequencer.nextAt`) and applies it, and goes on only while the stage -//! is idle — nothing it was asked to load still loading. Except a drag: a glide with a button -//! held plays at its authored pace, a frame at a time, as a hand moves (`drag_step_ms`). The sequencer still yields after -//! everything the app has to draw before the next op can land (a press, a key, the end of a -//! glide), so a click on a menu the last click opened finds it open. A tape's own waits hold as -//! they do in a demo, and one that gives up stops the tape: a live tape that has lost its place -//! is better stopped and reported than carried on blind. +//! moment anything happens (`Sequencer.nextAt`) and applies it, and goes on only while the stage is +//! idle — nothing it was asked to load still loading. Except a drag: a glide with a button held +//! plays at its authored pace, a frame at a time, as a hand moves (`drag_step_ms`). The sequencer +//! still yields after everything the app has to draw before the next op can land (a press, a key, +//! the end of a glide), so a click on a menu the last click opened finds it open. A tape's own +//! waits hold as they do in a demo, and one that gives up stops the tape: a live tape that has lost +//! its place is better stopped and reported than carried on blind. //! //! **Interruption.** A real click, tap, scroll, key or typed text stops it, and the person's //! input goes on to do what they meant. Real pointer motion is held off while it plays, as a diff --git a/sdk/replay/Player.zig b/sdk/replay/Player.zig index 769208328..d7f96c0e7 100644 --- a/sdk/replay/Player.zig +++ b/sdk/replay/Player.zig @@ -42,14 +42,15 @@ //! and the backend's clock is moved on by as much (`frames`' `clock`) so dvui's stays monotonic //! and continuous after. The player's own chrome (the transport bar) runs on the wall (`wallNs`). //! -//! **Closing** always leaves the app as it is (`Stage.keep`), and "Revert to before" (`revert`) is the one way back to the session -//! the demo set aside. `play` after a person changed things carries on from there as a new branch -//! (`branched`) rather than replaying over their work; seeking back is the history. +//! **Closing** always leaves the app as it is (`Stage.keep`), and "Revert to before" (`revert`) is +//! the one way back to the session the demo set aside. `play` after a person changed things carries +//! on from there as a new branch (`branched`) rather than replaying over their work; seeking back +//! is the history. //! //! **Interruption**: while playing, a real click, tap, scroll or key pauses the demo — a pointer //! event goes on to do what the person meant, the first key is only taken as "stop". Real pointer -//! motion is held off (the tape owns the pointer while it plays). Anything real that reaches the app -//! while paused marks the app `diverged` from the tape. +//! motion is held off (the tape owns the pointer while it plays). Anything real that reaches the +//! app while paused marks the app `diverged` from the tape. const Player = @This(); const std = @import("std"); @@ -227,8 +228,8 @@ pub const Transport = struct { /// physical, as far round it as the pointer is on it: what the bar hides into, and a press /// there brings it back. Null when it was not drawn. rest: ?dvui.Rect.Physical = null, - /// Sent into where it rests (its tab): the bar stays there, playing or not, until that brings it - /// back (a press here, the pointer held on it in the overlay). + /// Sent into where it rests (its tab): the bar stays there, playing or not, until that brings + /// it back (a press here, the pointer held on it in the overlay). tucked: bool = false, /// The speed slider, label and line (`Sequencer.speed`), whose notches are `Sequencer.speeds`. speed: dvui.Rect.Physical = .{}, @@ -373,7 +374,8 @@ pub fn revert(self: *Player) void { self.dismiss(.revert); } -/// Stop at once and keep the app as it is, as `close` does: the command's way, with no bar to run back. +/// Stop at once and keep the app as it is, as `close` does: the command's way, with no bar to run +/// back. pub fn stop(self: *Player) void { self.release(.keep); } @@ -717,7 +719,10 @@ fn verify(self: *Player, s: Snapshot) void { if (got == want) return; self.mismatches += 1; const name = if (self.tape()) |t| t.name else "?"; - log.warn("demo '{s}': replaying to {d:.0} ms did not reach what playing did — the tape does not replay exactly", .{ name, s.at }); + log.warn("demo '{s}': replaying to {d:.0} ms did not reach what playing did — the tape does not replay exactly", .{ + name, + s.at, + }); } fn dropSnapshots(self: *Player) void { @@ -733,7 +738,10 @@ fn dropSnapshots(self: *Player) void { pub fn frame(self: *Player) void { // Whether this run is seen, once the seek it asks for (or lands) is known: a run while a seek // catches up draws nothing, but the one `frames` ends it with. - defer self.run_unseen = if (self.set_unseen) |set| set((self.state == .seeking or self.catching_up) and !self.showing) else false; + defer self.run_unseen = if (self.set_unseen) |set| + set((self.state == .seeking or self.catching_up) and !self.showing) + else + false; if (self.owned == null) return; // Widgets name themselves for the tape while one is loaded (`anchor`). anchor.want(); @@ -814,7 +822,12 @@ pub fn frame(self: *Player) void { /// the runs took the app's clock past the wall — and, a seek still in flight, to the moment it /// goes on from — so the next frame carries on from there. Without one a silent run steps the /// clock the least dvui accepts, a microsecond, and timers wait for the wall. -pub fn frames(self: *Player, win: *dvui.Window, frame_fn: *const fn () anyerror!dvui.App.Result, clock: ?*i128) anyerror!dvui.App.Result { +pub fn frames( + self: *Player, + win: *dvui.Window, + frame_fn: *const fn () anyerror!dvui.App.Result, + clock: ?*i128, +) anyerror!dvui.App.Result { // Counted before the run, which may land the seek, and after it, which may have asked for one. const was_seeking = self.state == .seeking; if (was_seeking) self.seek_stats.shown += 1; @@ -869,7 +882,10 @@ fn runAt(self: *const Player, win: *dvui.Window, clock: ?*i128, base_ns: i128, b // Waiting on a glide's target to stop moving (`targetStill`), a run is a frame of motion where // the backend's clock can be moved on: on a microsecond's clock, a place sliding open would // never move between runs, and look still. - const soonest = win.frame_time_ns + if (self.aiming != null and clock != null) msToNs(Sequencer.path_step_ms) else std.time.ns_per_us; + const soonest = win.frame_time_ns + if (self.aiming != null and clock != null) + msToNs(Sequencer.path_step_ms) + else + std.time.ns_per_us; if (clock == null or self.state != .seeking) return soonest; return @max(soonest, base_ns + msToNs(self.nextMoment() - base_ms)); } @@ -1057,7 +1073,10 @@ fn transportTakes(self: *Player, e: *dvui.Event, me: dvui.Event.Mouse) bool { self.seq.setSpeedIndex(tr.speedAt(me.p.x)); } else if (tr.track.contains(me.p)) { tr.scrub = tr.timeAt(me.p.x, self.duration()); - self.scrub_after = if (self.state == .playing or (self.state == .seeking and self.after_seek == .play)) .play else .pause; + self.scrub_after = if (self.state == .playing or (self.state == .seeking and self.after_seek == .play)) + .play + else + .pause; self.scrubbed_to = null; } else if (tr.prev.contains(me.p)) { self.stepChapter(-1); @@ -1204,11 +1223,27 @@ fn failed(ctx: *anyopaque, failure: Sequencer.Failure) void { const at = if (self.tape()) |t| t.ops[failure.op].at else 0; switch (failure.what) { .wait => |until| switch (until) { - .idle => dvui.log.warn("demo '{s}': gave up waiting for the app to settle (op {d}, {d} ms); skipping {d} ops to the next chapter", .{ name, failure.op, at, failure.skipped }), - .shown => |tag| dvui.log.warn("demo '{s}': gave up waiting for '{s}' to be drawn (op {d}, {d} ms); skipping {d} ops to the next chapter", .{ name, tag, failure.op, at, failure.skipped }), - .gone => |tag| dvui.log.warn("demo '{s}': gave up waiting for '{s}' to go (op {d}, {d} ms); skipping {d} ops to the next chapter", .{ name, tag, failure.op, at, failure.skipped }), + .idle => dvui.log.warn( + "demo '{s}': gave up waiting for the app to settle (op {d}, {d} ms); " ++ + "skipping {d} ops to the next chapter", + .{ name, failure.op, at, failure.skipped }, + ), + .shown => |tag| dvui.log.warn( + "demo '{s}': gave up waiting for '{s}' to be drawn (op {d}, {d} ms); " ++ + "skipping {d} ops to the next chapter", + .{ name, tag, failure.op, at, failure.skipped }, + ), + .gone => |tag| dvui.log.warn( + "demo '{s}': gave up waiting for '{s}' to go (op {d}, {d} ms); " ++ + "skipping {d} ops to the next chapter", + .{ name, tag, failure.op, at, failure.skipped }, + ), }, - .target => |t| dvui.log.warn("demo '{s}': '{s}' was never drawn to move to (op {d}, {d} ms); skipping {d} ops to the next chapter", .{ name, t.tag, failure.op, at, failure.skipped }), + .target => |t| dvui.log.warn( + "demo '{s}': '{s}' was never drawn to move to (op {d}, {d} ms); " ++ + "skipping {d} ops to the next chapter", + .{ name, t.tag, failure.op, at, failure.skipped }, + ), } } diff --git a/sdk/replay/Snapshot.zig b/sdk/replay/Snapshot.zig index b5992b422..fb26f25e1 100644 --- a/sdk/replay/Snapshot.zig +++ b/sdk/replay/Snapshot.zig @@ -7,7 +7,10 @@ //! //! ``` //! .{ -//! .{ .role = "tab", .name = "Files", .tag = "fizzy.rail:files", .rect = .{ .x = 8, .y = 52, .w = 40, .h = 40 } }, +//! .{ +//! .role = "tab", .name = "Files", .tag = "fizzy.rail:files", +//! .rect = .{ .x = 8, .y = 52, .w = 40, .h = 40 }, +//! }, //! .{ .role = "button", .name = "Close Tab", .rect = .{ … }, .parent = 3 }, //! } //! ``` @@ -271,7 +274,23 @@ pub const Node = struct { /// The roles a person acts on: a widget with one is a node even with no tag. pub fn acted(role: dvui.AccessKit.Role) bool { return switch (role) { - .button, .default_button, .tab, .check_box, .radio_button, .link, .menu_item, .list_box_option, .tree_item, .text_input, .multiline_text_input, .search_input, .slider, .combo_box, .dialog, .alert_dialog => true, + .button, + .default_button, + .tab, + .check_box, + .radio_button, + .link, + .menu_item, + .list_box_option, + .tree_item, + .text_input, + .multiline_text_input, + .search_input, + .slider, + .combo_box, + .dialog, + .alert_dialog, + => true, else => false, }; } @@ -344,7 +363,12 @@ pub fn build(arena: std.mem.Allocator, widgets: []const CapturedWidget, windows: const owner = index_of.get(p.data.id); const within = if (owner) |i| node_of[i] else null; const subwindow = if (owner) |i| widgets[i].subwindow_id else null; - try nodes.append(arena, .{ .tag = p.name, .rect = round(p.data.rect), .parent = within, .window = windowOf(windows, p.data.id, subwindow, p.data.rect.center()) }); + try nodes.append(arena, .{ + .tag = p.name, + .rect = round(p.data.rect), + .parent = within, + .window = windowOf(windows, p.data.id, subwindow, p.data.rect.center()), + }); } return nodes.items; } @@ -361,7 +385,12 @@ pub fn write(nodes: []const Node, w: *std.Io.Writer) !void { } fn round(r: dvui.Rect.Physical) Node.Rect { - return .{ .x = @intFromFloat(@round(r.x)), .y = @intFromFloat(@round(r.y)), .w = @intFromFloat(@round(r.w)), .h = @intFromFloat(@round(r.h)) }; + return .{ + .x = @intFromFloat(@round(r.x)), + .y = @intFromFloat(@round(r.y)), + .w = @intFromFloat(@round(r.w)), + .h = @intFromFloat(@round(r.h)), + }; } /// The name widget `i` goes by, or null when it has none: its `label` text, the label widget it @@ -413,7 +442,11 @@ fn testFrame() !dvui.App.Result { defer box.deinit(); _ = dvui.button(@src(), "Save", .{}, .{ .tag = "demo.save" }); // An icon-only button: named by its label, not by anything it shows. - var close = dvui.box(@src(), .{}, .{ .role = .button, .label = .{ .text = "Close" }, .min_size_content = .{ .w = 10, .h = 10 } }); + var close = dvui.box(@src(), .{}, .{ + .role = .button, + .label = .{ .text = "Close" }, + .min_size_content = .{ .w = 10, .h = 10 }, + }); close.deinit(); // A mark, named from data, only while someone asked. var area = dvui.box(@src(), .{}, .{ .min_size_content = .{ .w = 20, .h = 20 } }); @@ -445,7 +478,12 @@ test "a snapshot names what a person acts on, and what a tape aims at" { var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); var diag: std.zon.parse.Diagnostics = undefined; - const nodes = try std.zon.parse.fromSlice([]const Node, .{ .gpa = std.testing.allocator, .arena = arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &diag }); + const nodes = try std.zon.parse.fromSlice([]const Node, .{ + .gpa = std.testing.allocator, + .arena = arena.allocator(), + .source = try arena.allocator().dupeSentinel(u8, text, 0), + .diagnostics = &diag, + }); var buttons: usize = 0; var save = false; var close = false; @@ -476,7 +514,10 @@ fn testDialog(id: dvui.Id) anyerror!void { const began = scopeBegin(); defer scopeEnd(began); // lint-allow(raw-transient-surface): snapshot test fixture, not a shown surface - var win = dvui.floatingWindow(@src(), .{}, .{ .id_extra = id.asUsize(), .rect = .{ .x = 10, .y = 10, .w = 80, .h = 40 } }); + var win = dvui.floatingWindow(@src(), .{}, .{ + .id_extra = id.asUsize(), + .rect = .{ .x = 10, .y = 10, .w = 80, .h = 40 }, + }); defer win.deinit(); test_dialog_window = win.data().id; _ = dvui.button(@src(), "Go ahead", .{}, .{}); @@ -506,7 +547,12 @@ fn testWindows(_: *anyopaque, arena: std.mem.Allocator) []const Window { const list = arena.dupe(Window, &.{ .{ .tag = "app.window:main", .title = "App", .rect = dvui.windowRectPixels() }, .{ .tag = "app.window:band", .rect = test_band, .area = true }, - .{ .tag = "app.window:dialog", .title = "Ask", .rect = .{ .x = 0, .y = 0, .w = 100, .h = 100 }, .subwindow = test_dialog_window }, + .{ + .tag = "app.window:dialog", + .title = "Ask", + .rect = .{ .x = 0, .y = 0, .w = 100, .h = 100 }, + .subwindow = test_dialog_window, + }, }) catch return &.{}; return list; } @@ -532,7 +578,12 @@ test "a snapshot has the dialogs dvui shows after its capture, and says which wi var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); var diag: std.zon.parse.Diagnostics = undefined; - const nodes = try std.zon.parse.fromSlice([]const Node, .{ .gpa = std.testing.allocator, .arena = arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &diag }); + const nodes = try std.zon.parse.fromSlice([]const Node, .{ + .gpa = std.testing.allocator, + .arena = arena.allocator(), + .source = try arena.allocator().dupeSentinel(u8, text, 0), + .diagnostics = &diag, + }); // The windows first, each a node. try std.testing.expect(nodes.len > 3); for (nodes[0..3], [_][]const u8{ "app.window:main", "app.window:band", "app.window:dialog" }) |node, tag| { diff --git a/sdk/replay/Stage.zig b/sdk/replay/Stage.zig index 9c423298c..44618b870 100644 --- a/sdk/replay/Stage.zig +++ b/sdk/replay/Stage.zig @@ -49,9 +49,9 @@ pub const VTable = struct { chordFor: ?*const fn (ctx: *anyopaque, id: []const u8) ?chord.Stroke = null, /// What a person would call a command ("Format Document"), for the keystroke display. commandTitle: ?*const fn (ctx: *anyopaque, id: []const u8) ?[]const u8 = null, - /// The live name of the place `role` stands for (`documents`, `explorer`, `panel`), written into - /// `buf`, for a tag like `region:role:documents`: a demo aims at what a place is, not at the - /// name the person's layout gave it. Null when the app has no such place now. Null hook: no + /// The live name of the place `role` stands for (`documents`, `explorer`, `panel`), written + /// into `buf`, for a tag like `region:role:documents`: a demo aims at what a place is, not at + /// the name the person's layout gave it. Null when the app has no such place now. Null hook: no /// roles. resolveRole: ?*const fn (ctx: *anyopaque, role: []const u8, buf: []u8) ?[]const u8 = null, /// Replaying fast (a seek): turn animation off so every replayed frame lands where it is diff --git a/sdk/replay/overlay.zig b/sdk/replay/overlay.zig index 775266f80..138055832 100644 --- a/sdk/replay/overlay.zig +++ b/sdk/replay/overlay.zig @@ -36,7 +36,12 @@ pub fn drawLive(driver: *const LiveDriver, opts: Options) void { // A cursor the app draws itself where the pointer is (a paint tool's pencil) is the pointer // there: two at one place would be one too many, so this one gives way (`anchor.own_cursor`). const own = dvui.currentWindow().tags.containsUsed(anchor.own_cursor) orelse false; - paintPointer(driver.seq.pointer, ripple, if (own) 0 else if (hand.typing) 1 - in else in, driver.input.held.count() > 0); + paintPointer( + driver.seq.pointer, + ripple, + if (own) 0 else if (hand.typing) 1 - in else in, + driver.input.held.count() > 0, + ); } /// A demo's pointer, while it drives, timed in demo time: a seek shows what playing showed. @@ -44,10 +49,16 @@ pub fn drawPlayer(player: *Player, opts: Options) void { if (!player.driving()) return; const fw = layer(@src(), .{ .rect = pointerRect(player.seq.pointer), .name = "DemoPointer" }, .{}); defer fw.deinit(); - const ripple: ?Ripple = if (player.last_press) |press| .{ .pt = press.pt, .age_ms = player.seq.now - press.at } else null; + const ripple: ?Ripple = if (player.last_press) |press| .{ + .pt = press.pt, + .age_ms = player.seq.now - press.at, + } else null; // Out of the way of the words while the tape types, as a desktop's pointer is, and of a // cursor the app draws itself (`drawLive`). - const shown = if (dvui.currentWindow().tags.containsUsed(anchor.own_cursor) orelse false) 0 else player.pointerShown(opts.fade_ms); + const shown = if (dvui.currentWindow().tags.containsUsed(anchor.own_cursor) orelse false) + 0 + else + player.pointerShown(opts.fade_ms); paintPointer(player.seq.pointer, ripple, shown, player.input.held.count() > 0); } @@ -63,7 +74,11 @@ fn pointerRect(p: Sequencer.Point) dvui.Rect { /// A floating widget that takes no input, raised to the top. A floating widget otherwise stays /// just above the window it was made in, under anything opened after it; re-added as a subwindow /// of its own it can be raised, and each call puts the new one above the last. -pub fn layer(src: std.lang.SourceLocation, opts: dvui.Options, init: dvui.FloatingWidget.InitOptions) *dvui.FloatingWidget { +pub fn layer( + src: std.lang.SourceLocation, + opts: dvui.Options, + init: dvui.FloatingWidget.InitOptions, +) *dvui.FloatingWidget { var init_opts = init; init_opts.mouse_events = false; const fw = dvui.widgetAlloc(dvui.FloatingWidget); @@ -118,7 +133,11 @@ pub fn paintPointer(p: Sequencer.Point, ripple: ?Ripple, shown: f32, pressed: bo dvui.Path.fill(&.{built}, .{ .color = .{ .color = dvui.Color.black.opacity(0.28) }, .fade = 2.5 * s }); } else { dvui.Path.fill(&.{built}, .{ .color = .{ .color = .white }, .fade = 1 }); - built.stroke(.{ .thickness = 1.1 * s, .color = .{ .color = dvui.Color.black.opacity(0.85) }, .closed = true }); + built.stroke(.{ + .thickness = 1.1 * s, + .color = .{ .color = dvui.Color.black.opacity(0.85) }, + .closed = true, + }); } } }