summaryrefslogtreecommitdiff
path: root/src/body_layer.zig
blob: 3b5ee795c4e51bd86ebf7429ee2a94c07a7ad58d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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);
}