From 9277df11272ad77dc36d60a7cf5947ba55ac2522 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 5 Mar 2024 14:35:05 -0800 Subject: [PATCH] terminal2: delete kitty by intersecting cursor --- src/terminal2/PageList.zig | 45 ++++++++- src/terminal2/kitty/graphics_image.zig | 13 +-- src/terminal2/kitty/graphics_storage.zig | 116 +++++++++++++---------- 3 files changed, 111 insertions(+), 63 deletions(-) diff --git a/src/terminal2/PageList.zig b/src/terminal2/PageList.zig index 02bcd82da..3d634962f 100644 --- a/src/terminal2/PageList.zig +++ b/src/terminal2/PageList.zig @@ -1765,6 +1765,47 @@ pub const Pin = struct { return .{ .row = rac.row, .cell = rac.cell }; } + /// Returns true if this pin is between the top and bottom, inclusive. + // + // Note: this is primarily unit tested as part of the Kitty + // graphics deletion code. + pub fn isBetween(self: Pin, top: Pin, bottom: Pin) bool { + if (comptime std.debug.runtime_safety) { + if (top.page == bottom.page) { + // If top is bottom, must be ordered. + assert(top.y <= bottom.y); + if (top.y == bottom.y) { + assert(top.x <= bottom.x); + } + } else { + // If top is not bottom, top must be before bottom. + var page = top.page.next; + while (page) |p| : (page = p.next) { + if (p == bottom.page) break; + } else assert(false); + } + } + + if (self.page == top.page) { + if (self.y < top.y) return false; + if (self.y > top.y) return true; + return self.x >= top.x; + } + if (self.page == bottom.page) { + if (self.y > bottom.y) return false; + if (self.y < bottom.y) return true; + return self.x <= bottom.x; + } + + var page = top.page.next; + while (page) |p| : (page = p.next) { + if (p == bottom.page) break; + if (p == self.page) return true; + } + + return false; + } + /// Move the pin down a certain number of rows, or return null if /// the pin goes beyond the end of the screen. pub fn down(self: Pin, n: usize) ?Pin { @@ -1785,7 +1826,7 @@ pub const Pin = struct { /// Move the offset down n rows. If the offset goes beyond the /// end of the screen, return the overflow amount. - fn downOverflow(self: Pin, n: usize) union(enum) { + pub fn downOverflow(self: Pin, n: usize) union(enum) { offset: Pin, overflow: struct { end: Pin, @@ -1823,7 +1864,7 @@ pub const Pin = struct { /// Move the offset up n rows. If the offset goes beyond the /// start of the screen, return the overflow amount. - fn upOverflow(self: Pin, n: usize) union(enum) { + pub fn upOverflow(self: Pin, n: usize) union(enum) { offset: Pin, overflow: struct { end: Pin, diff --git a/src/terminal2/kitty/graphics_image.zig b/src/terminal2/kitty/graphics_image.zig index d84ea91d6..8f9a1b666 100644 --- a/src/terminal2/kitty/graphics_image.zig +++ b/src/terminal2/kitty/graphics_image.zig @@ -6,6 +6,7 @@ const ArenaAllocator = std.heap.ArenaAllocator; const command = @import("graphics_command.zig"); const point = @import("../point.zig"); +const PageList = @import("../PageList.zig"); const internal_os = @import("../../os/main.zig"); const stb = @import("../../stb/main.zig"); @@ -451,16 +452,8 @@ pub const Image = struct { /// be rounded up to the nearest grid cell since we can't place images /// in partial grid cells. pub const Rect = struct { - top_left: point.ScreenPoint = .{}, - bottom_right: point.ScreenPoint = .{}, - - /// True if the rect contains a given screen point. - pub fn contains(self: Rect, p: point.ScreenPoint) bool { - return p.y >= self.top_left.y and - p.y <= self.bottom_right.y and - p.x >= self.top_left.x and - p.x <= self.bottom_right.x; - } + top_left: PageList.Pin, + bottom_right: PageList.Pin, }; /// Easy base64 encoding function. diff --git a/src/terminal2/kitty/graphics_storage.zig b/src/terminal2/kitty/graphics_storage.zig index 230c1edc3..1501f4f23 100644 --- a/src/terminal2/kitty/graphics_storage.zig +++ b/src/terminal2/kitty/graphics_storage.zig @@ -242,12 +242,17 @@ pub const ImageStorage = struct { }, .intersect_cursor => |delete_images| { - if (true) @panic("TODO"); - const target = (point.Viewport{ - .x = t.screen.cursor.x, - .y = t.screen.cursor.y, - }).toScreen(&t.screen); - self.deleteIntersecting(alloc, t, target, delete_images, {}, null); + self.deleteIntersecting( + alloc, + t, + .{ .active = .{ + .x = t.screen.cursor.x, + .y = t.screen.cursor.y, + } }, + delete_images, + {}, + null, + ); }, .intersect_cell => |v| { @@ -378,18 +383,22 @@ pub const ImageStorage = struct { fn deleteIntersecting( self: *ImageStorage, alloc: Allocator, - t: *const terminal.Terminal, - p: point.ScreenPoint, + t: *terminal.Terminal, + p: point.Point, delete_unused: bool, filter_ctx: anytype, comptime filter: ?fn (@TypeOf(filter_ctx), Placement) bool, ) void { + // Convert our target point to a pin for comparison. + const target_pin = t.screen.pages.pin(p) orelse return; + var it = self.placements.iterator(); while (it.next()) |entry| { const img = self.imageById(entry.key_ptr.image_id) orelse continue; const rect = entry.value_ptr.rect(img, t); - if (rect.contains(p)) { + if (target_pin.isBetween(rect.top_left, rect.bottom_right)) { if (filter) |f| if (!f(filter_ctx, entry.value_ptr.*)) continue; + entry.value_ptr.deinit(t); self.placements.removeByPtr(entry.key_ptr); if (delete_unused) self.deleteIfUnused(alloc, img.id); } @@ -547,13 +556,13 @@ pub const ImageStorage = struct { ) Rect { // If we have columns/rows specified we can simplify this whole thing. if (self.columns > 0 and self.rows > 0) { - return .{ - .top_left = self.point, - .bottom_right = .{ - .x = @min(self.point.x + self.columns, t.cols - 1), - .y = self.point.y + self.rows, - }, + var br = switch (self.pin.downOverflow(self.rows)) { + .offset => |v| v, + .overflow => |v| v.end, }; + br.x = @min(self.pin.x + self.columns, t.cols - 1); + + return .{ .top_left = self.pin.*, .bottom_right = br }; } // Calculate our cell size. @@ -574,12 +583,16 @@ pub const ImageStorage = struct { const width_cells: u32 = @intFromFloat(@ceil(width_f64 / cell_width_f64)); const height_cells: u32 = @intFromFloat(@ceil(height_f64 / cell_height_f64)); + // TODO(paged-terminal): clean this logic up above + var br = switch (self.pin.downOverflow(height_cells)) { + .offset => |v| v, + .overflow => |v| v.end, + }; + br.x = @min(self.pin.x + width_cells, t.cols - 1); + return .{ - .top_left = self.point, - .bottom_right = .{ - .x = @min(self.point.x + width_cells, t.cols - 1), - .y = self.point.y + height_cells, - }, + .top_left = self.pin.*, + .bottom_right = br, }; } }; @@ -769,37 +782,38 @@ test "storage: delete placement by specific id" { try testing.expectEqual(tracked + 2, t.screen.pages.countTrackedPins()); } -// test "storage: delete intersecting cursor" { -// const testing = std.testing; -// const alloc = testing.allocator; -// var t = try terminal.Terminal.init(alloc, 100, 100); -// defer t.deinit(alloc); -// t.width_px = 100; -// t.height_px = 100; -// -// var s: ImageStorage = .{}; -// defer s.deinit(alloc); -// try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); -// try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); -// try s.addPlacement(alloc, 1, 1, .{ .point = .{ .x = 0, .y = 0 } }); -// try s.addPlacement(alloc, 1, 2, .{ .point = .{ .x = 25, .y = 25 } }); -// -// t.screen.cursor.x = 12; -// t.screen.cursor.y = 12; -// -// s.dirty = false; -// s.delete(alloc, &t, .{ .intersect_cursor = false }); -// try testing.expect(s.dirty); -// try testing.expectEqual(@as(usize, 1), s.placements.count()); -// try testing.expectEqual(@as(usize, 2), s.images.count()); -// -// // verify the placement is what we expect -// try testing.expect(s.placements.get(.{ -// .image_id = 1, -// .placement_id = .{ .tag = .external, .id = 2 }, -// }) != null); -// } -// +test "storage: delete intersecting cursor" { + const testing = std.testing; + const alloc = testing.allocator; + var t = try terminal.Terminal.init(alloc, 100, 100); + defer t.deinit(alloc); + t.width_px = 100; + t.height_px = 100; + const tracked = t.screen.pages.countTrackedPins(); + + var s: ImageStorage = .{}; + defer s.deinit(alloc, &t); + try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); + try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); + try s.addPlacement(alloc, 1, 1, .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) }); + try s.addPlacement(alloc, 1, 2, .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) }); + + t.screen.cursorAbsolute(12, 12); + + s.dirty = false; + s.delete(alloc, &t, .{ .intersect_cursor = false }); + try testing.expect(s.dirty); + try testing.expectEqual(@as(usize, 1), s.placements.count()); + try testing.expectEqual(@as(usize, 2), s.images.count()); + try testing.expectEqual(tracked + 1, t.screen.pages.countTrackedPins()); + + // verify the placement is what we expect + try testing.expect(s.placements.get(.{ + .image_id = 1, + .placement_id = .{ .tag = .external, .id = 2 }, + }) != null); +} + // test "storage: delete intersecting cursor plus unused" { // const testing = std.testing; // const alloc = testing.allocator;