summaryrefslogtreecommitdiff
path: root/src/draw.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/draw.zig')
-rw-r--r--src/draw.zig73
1 files changed, 72 insertions, 1 deletions
diff --git a/src/draw.zig b/src/draw.zig
index a2005e59..6c0cc077 100644
--- a/src/draw.zig
+++ b/src/draw.zig
@@ -197,8 +197,11 @@ fn focusedCursorBox(p: *const Pardes, s: *const Surface) ?animation.Box {
const caret = layer.cursor orelse continue;
if (layer.rows == 0 or layer.viewport.y + caret.y != cursor.y) continue;
if (cursor.x < layer.viewport.x or cursor.x >= layer.viewport.x + layer.viewport.w) continue;
- const x = @as(f32, @floatFromInt(layer.viewport.x)) + @as(f32, @floatFromInt(caret.x)) * tw / bw;
const w = if (caret.bar) tw / bw / 8 else tw / bw;
+ // A caret past a full row's end sits at the tag's right edge, where
+ // the grid's is clamped too.
+ const right = @as(f32, @floatFromInt(layer.viewport.x + layer.viewport.w));
+ const x = @min(@as(f32, @floatFromInt(layer.viewport.x)) + @as(f32, @floatFromInt(caret.x)) * tw / bw, right - w);
return .{ .x = x, .y = @as(f32, @floatFromInt(cursor.y)) + (bh - th) / 2 / bh, .w = w, .h = th / bh };
}
return .{ .x = @floatFromInt(cursor.x), .y = @floatFromInt(cursor.y), .w = if (cursor.bar) 1.0 / 8.0 else 1, .h = 1 };
@@ -467,7 +470,25 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface {
.grip_dirty = boxDirty(p),
.tag_rule = p.theme().tag_rule,
.rail_px = @intCast(@min(255, @as(u32, p.theme().rail_px orelse 12) * p.settings.grip_width / 100)),
+ .name_ink = chrome.tag_name_fg,
+ .active_name_ink = if (p.settings.focus_tint) chrome.tag_active_name_fg else chrome.tag_name_fg,
+ .selection_glow = @intFromBool(p.settings.selection_glow),
};
+ if (p.theme().decor) |decor| {
+ s.chrome.decor_dots = decor.page_dots;
+ s.chrome.decor_dot_alpha = decor.page_dot_alpha;
+ s.chrome.decor_dot_px = decor.page_dot_px;
+ s.chrome.decor_checker = decor.rail_checker;
+ s.chrome.decor_checker_px = decor.rail_checker_px;
+ s.chrome.decor_box_border = decor.tag_border;
+ s.chrome.decor_box_border_px = decor.tag_border_px;
+ s.chrome.decor_box_shadow = decor.tag_shadow;
+ s.chrome.decor_box_shadow_px = decor.tag_shadow_px;
+ s.chrome.decor_box_stripe = decor.tag_stripe;
+ s.chrome.decor_box_stripe_px = decor.tag_stripe_px;
+ s.chrome.decor_title = decor.title_shadow;
+ s.chrome.decor_title_px = decor.title_shadow_px;
+ }
place(p, s);
// Separators have their own role; a scrollbar need not frame the page.
const whole = s.regions[0].rect;
@@ -1787,6 +1808,56 @@ test "the focused cursor glides a jump, lands a step at once, and is exact at re
try std.testing.expectEqual(@as(u8, 2), s.chrome.cursor_glide);
}
+test "a tag caret past its row's end glides to the tag's right edge, where the layer's is" {
+ if (!builtins.capabilities.lift) return error.SkipZigTest;
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 60, .rows = 16 });
+ defer p.deinit();
+ p.update(.{ .resize = .{ .cols = 60, .rows = 16, .row_metrics = .{ .body_w = 10, .body_h = 20, .tagline_w = 6, .tagline_h = 12 } } });
+ const pane = try p.setTestFile("text\n");
+ var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
+ defer arena.deinit();
+ // The tag's text grown a letter at a time, the caret after its last one
+ // in insert mode, until a row is full and the caret is past its end.
+ var found = false;
+ var n: usize = 1;
+ while (n < 200 and !found) : (n += 1) {
+ _ = arena.reset(.retain_capacity);
+ if (pane.tag.own) |own| p.gpa.free(own);
+ const own = try p.gpa.alloc(u8, n);
+ @memset(own, 'x');
+ own[0] = ' ';
+ pane.tag.own = own;
+ tagline.enterTag(p, pane);
+ const whole = try tagline.tagText(p, arena.allocator(), pane);
+ pane.tag.mode = .insert;
+ pane.tag.cur_row = 0;
+ pane.tag.cur_col = @intCast(modal.lineSlice(whole, 0).len);
+ p.sync();
+ const s = try p.render(arena.allocator());
+ const layer = for (s.tagLayers()) |*layer| {
+ if (layer.rows != 0 and layer.kind == .pane and layer.cursor != null) break layer;
+ } else continue;
+ const caret = layer.cursor.?;
+ const right: f32 = @floatFromInt(layer.viewport.x + layer.viewport.w);
+ const box = focusedCursorBox(p, s).?;
+ // Never past the tag's right edge, and the pane's.
+ try std.testing.expect(box.x + box.w <= right + 1e-4);
+ try std.testing.expect(box.x + box.w <= @as(f32, @floatFromInt(p.rects[p.active].x + p.rects[p.active].w)));
+ const at = @as(f32, @floatFromInt(layer.viewport.x)) + @as(f32, @floatFromInt(caret.x)) * 6 / 10;
+ if (at + box.w <= right) {
+ // Inside its row: exactly where the layer's caret is.
+ try std.testing.expectApproxEqAbs(at, box.x, 1e-4);
+ } else {
+ // Past it: on the tag's right edge, as the pixel shell's caret
+ // is (gui.zig cursorBoxes).
+ try std.testing.expectApproxEqAbs(right, box.x + box.w, 1e-4);
+ try std.testing.expectApproxEqAbs(@as(f32, 6.0 / 10.0 / 8.0), box.w, 1e-4);
+ found = true;
+ }
+ }
+ try std.testing.expect(found);
+}
+
test "a terminal's cursor lands with scrolling output, never gliding against it" {
if (!builtins.capabilities.lift or !pardes.terminal_panes) return error.SkipZigTest;
const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 12 });