summaryrefslogtreecommitdiff
path: root/src/body_layer.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/body_layer.zig')
-rw-r--r--src/body_layer.zig80
1 files changed, 80 insertions, 0 deletions
diff --git a/src/body_layer.zig b/src/body_layer.zig
new file mode 100644
index 00000000..3b5ee795
--- /dev/null
+++ b/src/body_layer.zig
@@ -0,0 +1,80 @@
+//! A file body's logical rows can outnumber its physical grid rows. Hosts
+//! place the compact declaration rows first and clip the remaining body rows.
+const std = @import("std");
+const pardes = @import("pardes.zig");
+const config = @import("config.zig");
+
+pub const Metrics = struct {
+ body_w: u16 = 1,
+ body_h: u16 = 1,
+ tagline_w: u16 = 1,
+ tagline_h: u16 = 1,
+};
+
+pub const Layer = struct {
+ pane: u16 = 0,
+ serial: u32 = 0,
+ viewport: pardes.Rect = .{ .x = 0, .y = 0, .w = 0, .h = 0 },
+ cols: u16 = 0,
+ rows: u16 = 0,
+ context_rows: u16 = 0,
+ cells: []pardes.Cell = &.{},
+ cursor: ?struct { x: u16, y: u16, bar: bool = false } = null,
+
+ pub fn rowTop(layer: *const Layer, row: u16, body_h: f32, tagline_h: f32) f32 {
+ const body = @max(1, body_h);
+ const tag = std.math.clamp(tagline_h, 1, body);
+ return @as(f32, @floatFromInt(layer.viewport.y)) * body +
+ @as(f32, @floatFromInt(@min(row, layer.context_rows))) * tag +
+ @as(f32, @floatFromInt(row -| layer.context_rows)) * body;
+ }
+
+ pub fn rowHeight(layer: *const Layer, row: u16, body_h: f32, tagline_h: f32) f32 {
+ return if (row < layer.context_rows) std.math.clamp(tagline_h, 1, @max(1, body_h)) else @max(1, body_h);
+ }
+
+ pub fn hitAt(layer: *const Layer, x: f32, y: f32, body_w: f32, body_h: f32, tagline_w: f32, tagline_h: f32) ?pardes.Mouse.BodyHit {
+ if (layer.rows == 0 or !std.math.isFinite(x) or !std.math.isFinite(y)) return null;
+ const bw = @max(1, body_w);
+ const bh = @max(1, body_h);
+ const tw = std.math.clamp(tagline_w, 1, bw);
+ const th = std.math.clamp(tagline_h, 1, bh);
+ const dx = x - @as(f32, @floatFromInt(layer.viewport.x)) * bw;
+ const dy = y - @as(f32, @floatFromInt(layer.viewport.y)) * bh;
+ if (dx < 0 or dy < 0 or dx >= @as(f32, @floatFromInt(layer.viewport.w)) * bw or dy >= @as(f32, @floatFromInt(layer.viewport.h)) * bh) return null;
+ const headers = @as(f32, @floatFromInt(layer.context_rows)) * th;
+ const row: u16 = @intFromFloat(if (dy < headers) @floor(dy / th) else @as(f32, @floatFromInt(layer.context_rows)) + @floor((dy - headers) / bh));
+ if (row >= layer.rows) return null;
+ const col: u16 = @intFromFloat(@min(@as(f32, @floatFromInt(layer.cols -| 1)), @floor(dx / (if (row < layer.context_rows) tw else bw))));
+ return .{
+ .serial = layer.serial,
+ .row = row,
+ .col = col + config.GUTTER,
+ .pixel_x = x,
+ .pixel_y = y,
+ .metrics = .{ .body_w = @intFromFloat(bw), .body_h = @intFromFloat(bh), .tagline_w = @intFromFloat(tw), .tagline_h = @intFromFloat(th) },
+ };
+ }
+};
+
+test "compact body layer rows fill reclaimed height and invert exact boundaries" {
+ const layer: Layer = .{ .serial = 7, .viewport = .{ .x = 4, .y = 3, .w = 40, .h = 10 }, .cols = 40, .rows = 15, .context_rows = 5 };
+ try std.testing.expectEqual(@as(f32, 120), layer.rowTop(5, 20, 12));
+ try std.testing.expectEqual(@as(f32, 240), layer.rowTop(11, 20, 12));
+ try std.testing.expectEqual(@as(f32, 12), layer.rowHeight(4, 20, 12));
+ try std.testing.expectEqual(@as(f32, 20), layer.rowHeight(5, 20, 12));
+ for (0..12) |index| {
+ const row: u16 = @intCast(index);
+ const y = layer.rowTop(row, 20, 12) + layer.rowHeight(row, 20, 12) / 2;
+ const width: f32 = if (row < 5) 6 else 10;
+ const hit = layer.hitAt(40 + 3.5 * width, y, 10, 20, 6, 12).?;
+ try std.testing.expectEqual(row, hit.row);
+ try std.testing.expectEqual(@as(u16, 3 + config.GUTTER), hit.col);
+ }
+ try std.testing.expectEqual(@as(u16, 4), layer.hitAt(45, 119.99, 10, 20, 6, 12).?.row);
+ try std.testing.expectEqual(@as(u16, 5), layer.hitAt(45, 120, 10, 20, 6, 12).?.row);
+ try std.testing.expectEqual(@as(u16, 11), layer.hitAt(45, 259.99, 10, 20, 6, 12).?.row);
+ try std.testing.expect(layer.hitAt(45, 260, 10, 20, 6, 12) == null);
+ try std.testing.expect(layer.hitAt(39.99, 80, 10, 20, 6, 12) == null);
+ try std.testing.expect(layer.hitAt(std.math.nan(f32), 80, 10, 20, 6, 12) == null);
+}