summaryrefslogtreecommitdiff
path: root/src/tag_layer.zig
blob: ac7c4ac2bdcc75744e9fca5aa7babb6f33c778b6 (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
//! Tag text has its own horizontal pitch. Its viewport stays in physical
//! body-grid coordinates so compact text never changes pane hit targets.
const std = @import("std");
const pardes = @import("pardes.zig");

/// `notice` is a pane's transient line -- a builtin's name, a pending leader
/// chord -- carried as a tagline-shaped band so it gets the tagline's height,
/// small-font metrics, band offset and border instead of being a body-grid row
/// wearing a tagline font.
pub const Kind = enum(u8) { workspace, column, pane, notice };
pub const Hit = struct {
    kind: Kind,
    id: u16,
    serial: u32,
    col: u16,
    /// Which line of a taller tag the layer draws.
    line: u16 = 0,
    pixel_x: f32,
    pixel_y: f32,
    metrics: pardes.RowMetrics,
};
pub const Layer = struct {
    kind: Kind = .workspace,
    id: u16 = 0,
    serial: u32 = 0,
    /// A pane's tag taller than a line is drawn a layer to a line; this is
    /// which one, counted from its first.
    line: u16 = 0,
    viewport: pardes.Rect = .{ .x = 0, .y = 0, .w = 0, .h = 0 },
    cols: u16 = 0,
    bg: [3]u8 = .{ 0, 0, 0 },
    cells: []pardes.Cell = &.{},
    cursor: ?struct { col: u16, bar: bool = false } = null,
    /// Vertical offset in rows (negative: above its viewport), for a band
    /// that is moving into place. A pixel shell draws it offset and clipped
    /// to the rows below the viewport's top; the grid has no such thing.
    slide: f32 = 0,
    /// How present a notice band is (1 = fully) while it fades up or
    /// dissolves. `bg` and the cells are already blended by it; a pixel
    /// shell uses it for what it draws around the band, like its rule.
    fade: f32 = 1,

    pub fn capacity(layer: *const Layer, metrics: pardes.RowMetrics) u16 {
        return @intCast(@min(std.math.maxInt(u16), @max(1, @as(u32, layer.viewport.w) * @max(1, metrics.body_w) / @max(1, metrics.tagline_w))));
    }

    pub fn hitAt(layer: *const Layer, x: f32, y: f32, body_w: f32, body_h: f32, tagline_w: f32, tagline_h: f32) ?Hit {
        if (layer.cols == 0 or !std.math.isFinite(x) or !std.math.isFinite(y)) return null;
        for ([_]f32{ body_w, body_h, tagline_w, tagline_h }) |metric|
            if (!std.math.isFinite(metric) or metric < 1 or metric > std.math.maxInt(u16)) 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;
        return .{
            .kind = layer.kind,
            .id = layer.id,
            .serial = layer.serial,
            .line = layer.line,
            .col = @intFromFloat(@min(@as(f32, @floatFromInt(layer.cols - 1)), @floor(dx / tw))),
            .pixel_x = x,
            .pixel_y = y,
            .metrics = .{ .body_w = @intFromFloat(bw), .body_h = @intFromFloat(bh), .tagline_w = @intFromFloat(tw), .tagline_h = @intFromFloat(th) },
        };
    }
};

test "tag text capacity uses its own pitch and excludes the physical grip" {
    const layer: Layer = .{ .kind = .pane, .id = 2, .serial = 7, .viewport = .{ .x = 12, .y = 4, .w = 38, .h = 1 }, .cols = 90 };
    try std.testing.expectEqual(@as(u16, 63), layer.capacity(.{ .body_w = 10, .tagline_w = 6 }));
    try std.testing.expect(layer.hitAt(119.99, 85, 10, 20, 6, 12) == null);
    try std.testing.expectEqual(@as(u16, 0), layer.hitAt(120, 85, 10, 20, 6, 12).?.col);
    try std.testing.expectEqual(@as(u16, 50), layer.hitAt(423, 85, 10, 20, 6, 12).?.col);
    try std.testing.expect(layer.hitAt(500, 85, 10, 20, 6, 12) == null);
    try std.testing.expect(layer.hitAt(120, 85, std.math.nan(f32), 20, 6, 12) == null);
    try std.testing.expect(layer.hitAt(120, 85, 10, 20, 0, 12) == null);
}