From faa5ee1d81e2b9c4be6925b7178db2d2dc4577f8 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 23:34:20 -0300 Subject: G3: the focused cursor glides a jump on the Motion flavour's springs, and blinks The core owns the motion, as Lift: animation.CursorGlide keeps the focused cursor's four corners on springs (leading corners at the flavour's pace x2.4, trailing ones x follow, so a jump smears and collapses; a long jump bows by the flavour's arc), sampled at each frame's own time. A step of a cell or less, insert mode, a focus change and any scroll (a file's view, a terminal's output) land at once; a snap lands a glide under way too. The quad is clamped to the focused pane when drawn (never its springs). Chrome carries it on the wire (cursor_glide: bit 1 a cursor, bit 0 gliding; cursor_alpha; cursor_quad, grid cells; cursor_idle_ms): the GUI draws the quad as an overlay in the cursor's colour, at 85% (70% on the long flavours) rising to solid as it lands, and hides the cells' cursors while it does: one or the other every frame. Only the focused cursor glides. Blink (CursorBlink, on by default in pixel shells) is the shell's: from cursor_idle_ms and when the frame arrived, solid while typing and 500 ms after, 530 ms halves with 80 ms eased edges, solid after 10 s idle, and never while the window is unfocused or minimized or on a virtual clock. An edge redraws the last frame with no core render (level B, blink_redraws); an attached GUI blinks from the same field without a frame per edge. Tests: snaps a cell, glides a jump with smear and bow, arrives under a pixel by 150 ms on smooth and lands exactly, shifts with a scroll; blink holds, eases, stops; the core glides a jump (ge) frame by frame (gliding exactly while it moves, alpha rising, exact at rest), snaps a step (j) and a switch to insert mid-glide; a terminal's scrolling output lands its cursor; wire round-trip of the cursor record. Shared files: src/animation.zig, src/pardes.zig, src/config.zig (toggle CursorBlink), src/surface.zig, src/detached/wire.zig. --- src/detached/wire.zig | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'src/detached/wire.zig') diff --git a/src/detached/wire.zig b/src/detached/wire.zig index c0e17054..53a1a70d 100644 --- a/src/detached/wire.zig +++ b/src/detached/wire.zig @@ -13,7 +13,7 @@ pub const version: u16 = 8; /// The Chrome palette is carried field by field as its declaration lists /// them, so any change to it changes the wire: bump `version`, then this. -const chrome_layout: u64 = 0x7e3d0952317bfee; +const chrome_layout: u64 = 0xc91196fcdef144e2; comptime { @setEvalBranchQuota(10_000); var hash = std.hash.Fnv1a_64.init(); @@ -286,6 +286,16 @@ pub const Frame = struct { } } else if (field.type == u8) { rgb.* = try r.getByte(); + } else if (field.type == u32) { + rgb.* = try r.getU32(); + } else if (field.type == f32) { + rgb.* = @bitCast(try r.getU32()); + if (!std.math.isFinite(rgb.*)) return error.BadValue; + } else if (field.type == [8]f32) { + for (rgb) |*value| { + value.* = @bitCast(try r.getU32()); + if (!std.math.isFinite(value.*)) return error.BadValue; + } } else for (rgb) |*channel| channel.* = try r.getByte(); } try r.end(); @@ -747,7 +757,7 @@ pub fn layersBound(layers: []const pardes.Layer, tags: []const pardes.Layer, reg const region_len = 1 + 1 + 4 + 8 + 1 + 2 + 2 + 1 + 2 + 4; /// The palette: each colour three bytes, a theme's optional ones a flag /// before them. -const chrome_len = @typeInfo(pardes.Chrome).@"struct".fields.len * 4; +const chrome_len = @typeInfo(pardes.Chrome).@"struct".fields.len * 4 + 8 * 4; pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, pointer_shape: pardes.Surface.PointerShape, cells: []const pardes.Cell, prev: []const pardes.Cell, layers: []const pardes.Layer, tags: []const pardes.Layer, regions: []const pardes.Region, chrome: pardes.Chrome) Error![]const u8 { // The protocol's ceiling, enforced by the ENCODER too, and BEFORE the @@ -837,6 +847,12 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point if (rgb) |value| for (value) |channel| try w.putByte(channel); } else if (field.type == u8) { try w.putByte(rgb); + } else if (field.type == u32) { + try w.putU32(rgb); + } else if (field.type == f32) { + try w.putU32(@bitCast(rgb)); + } else if (field.type == [8]f32) { + for (rgb) |value| try w.putU32(@bitCast(value)); } else for (rgb) |channel| try w.putByte(channel); } } @@ -1917,7 +1933,7 @@ test "detached wire carries compact body rows and exact pointer metrics" { const placed = [_]pardes.Region{.{ .kind = .rail, .owner = 3, .serial = 42, .rect = .{ .x = 0, .y = 1, .w = 2, .h = 3 }, .active = true, .dirty = true, .thumb_y = 1, .thumb_h = 2, .clip = 1, .lift = 0.5 }}; const out = try gpa.alloc(u8, frameBound(2, 4) + layersBound(&.{layer}, &.{tag}, &placed)); defer gpa.free(out); - const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 }, .rule_px = 3, .tag_rule = .{ 13, 14, 15 }, .rail_px = 12, .grip_border = .{ 16, 17, 18 }, .grip_focus_ring = .{ 19, 20, 21 }, .grip_dirty = .{ 22, 23, 24 }, .lift_style = 2 }; + const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 }, .rule_px = 3, .tag_rule = .{ 13, 14, 15 }, .rail_px = 12, .grip_border = .{ 16, 17, 18 }, .grip_focus_ring = .{ 19, 20, 21 }, .grip_dirty = .{ 22, 23, 24 }, .lift_style = 2, .cursor_glide = 3, .cursor_alpha = 0.5, .cursor_idle_ms = 1234, .cursor_quad = .{ 1, 2, 3.5, 2, 3.5, 3, 1, 3 } }; const encoded = try encodeFrameLayers(out, 2, 4, null, .link, &cells, &cells, &.{layer}, &.{tag}, &placed, palette); const packet = (try framed(encoded)).?; const frame = (try decodeServer(packet.tag, packet.payload)).frame; -- cgit v1.3