summaryrefslogtreecommitdiff
path: root/pane_ui.zig
diff options
context:
space:
mode:
Diffstat (limited to 'pane_ui.zig')
-rw-r--r--pane_ui.zig496
1 files changed, 0 insertions, 496 deletions
diff --git a/pane_ui.zig b/pane_ui.zig
deleted file mode 100644
index 629992b8..00000000
--- a/pane_ui.zig
+++ /dev/null
@@ -1,496 +0,0 @@
-const std = @import("std");
-
-pub const TOPBAR_H: u16 = 1;
-pub const GUTTER: u16 = 2;
-pub const BOX_H: u16 = 1;
-pub const MINW: u16 = 10;
-pub const MINH: u16 = 3;
-pub const PREFIX_W: u16 = 5;
-
-pub const Rect = struct { x: u16, y: u16, w: u16, h: u16 };
-
-pub const MousePos = struct { col: u16, row: u16 };
-
-pub const ScrollbarButton = enum { left, middle, right };
-
-pub const Scrollbar = struct {
- total: usize,
- offset: usize,
- len: usize,
-};
-
-pub fn Layout(comptime max_cols: usize, comptime max_panes: usize) type {
- return struct {
- ncol: usize,
- col_weight: [max_cols]f32,
- col_terms: [max_cols][max_panes]usize,
- col_n: [max_cols]usize,
- };
-}
-
-pub fn layoutFindTerm(l: anytype, id: usize) ?struct { col: usize, idx: usize } {
- var c: usize = 0;
- while (c < l.ncol) : (c += 1) {
- var k: usize = 0;
- while (k < l.col_n[c]) : (k += 1) {
- if (l.col_terms[c][k] == id) return .{ .col = c, .idx = k };
- }
- }
- return null;
-}
-
-pub fn layoutRemove(l: anytype, id: usize) void {
- const f = layoutFindTerm(l, id) orelse return;
- const c = f.col;
- var k = f.idx;
- while (k + 1 < l.col_n[c]) : (k += 1) l.col_terms[c][k] = l.col_terms[c][k + 1];
- l.col_n[c] -= 1;
- if (l.col_n[c] == 0) {
- if (l.ncol > 1) l.col_weight[if (c > 0) c - 1 else c + 1] += l.col_weight[c];
- var j = c;
- while (j + 1 < l.ncol) : (j += 1) {
- l.col_terms[j] = l.col_terms[j + 1];
- l.col_n[j] = l.col_n[j + 1];
- l.col_weight[j] = l.col_weight[j + 1];
- }
- l.ncol -= 1;
- }
-}
-
-pub fn layoutInsert(l: anytype, c: usize, idx: usize, id: usize) void {
- var k = l.col_n[c];
- while (k > idx) : (k -= 1) l.col_terms[c][k] = l.col_terms[c][k - 1];
- l.col_terms[c][idx] = id;
- l.col_n[c] += 1;
-}
-
-pub fn layoutAppendColumn(l: anytype, id: usize) void {
- if (l.ncol >= l.col_weight.len) return;
- const c = l.ncol;
- l.col_weight[c] = 1;
- l.col_terms[c][0] = id;
- l.col_n[c] = 1;
- l.ncol += 1;
-}
-
-pub const MovePlacement = struct {
- preview_col: usize,
- above_id: usize,
- row: u16,
- above_y: u16,
- above_h: u16,
-};
-
-pub fn computeGeom(
- l: anytype,
- items: anytype,
- sw: u16,
- sh: u16,
- rects: anytype,
- col_x: anytype,
- col_w: anytype,
- comptime vweightFn: anytype,
-) void {
- if (l.ncol == 0) return;
- var wsum: f32 = 0;
- for (0..l.ncol) |c| wsum += l.col_weight[c];
- if (wsum <= 0) wsum = 1;
-
- var x: u16 = 0;
- for (0..l.ncol) |c| {
- const last = c + 1 == l.ncol;
- const cw: u16 = if (last) (sw -| x) else @max(1, @as(u16, @intFromFloat(@round(@as(f32, @floatFromInt(sw)) * l.col_weight[c] / wsum))));
- col_x[c] = x;
- col_w[c] = cw;
-
- var vsum: f32 = 0;
- for (0..l.col_n[c]) |k| {
- if (vweightFn(items, l.col_terms[c][k])) |vweight| vsum += vweight;
- }
- if (vsum <= 0) vsum = 1;
-
- var y: u16 = TOPBAR_H;
- const avail_h = sh -| TOPBAR_H;
- for (0..l.col_n[c]) |k| {
- const id = l.col_terms[c][k];
- const vweight = vweightFn(items, id) orelse continue;
- const lastk = k + 1 == l.col_n[c];
- const ch: u16 = if (lastk) (sh -| y) else @max(1, @as(u16, @intFromFloat(@round(@as(f32, @floatFromInt(avail_h)) * vweight / vsum))));
- rects[id] = .{ .x = x, .y = y, .w = cw, .h = ch };
- y +|= ch;
- }
- x +|= cw;
- }
-}
-
-fn targetColumn(l: anytype, col_x: anytype, col_w: anytype, cur_x: u16) usize {
- var tc: usize = if (l.ncol > 0) l.ncol - 1 else 0;
- for (0..l.ncol) |c| {
- if (cur_x >= col_x[c] and cur_x < col_x[c] + col_w[c]) {
- tc = c;
- break;
- }
- }
- return tc;
-}
-
-fn splitRowForExtent(y: u16, h: u16, cur_y: u16) ?u16 {
- if (h < 2) return null;
- const min_each: u16 = if (h >= MINH * 2) MINH else 1;
- const lo = y +| min_each;
- const hi = y + h - min_each;
- if (lo > hi) return y + h / 2;
- return std.math.clamp(cur_y, lo, hi);
-}
-
-pub fn movePlacement(
- l: anytype,
- rects: anytype,
- col_x: anytype,
- col_w: anytype,
- id: usize,
- cur_x: u16,
- cur_y: u16,
-) ?MovePlacement {
- const src = layoutFindTerm(l, id) orelse return null;
- const tc = targetColumn(l, col_x, col_w, cur_x);
- if (tc == src.col and l.col_n[src.col] == 1) return null;
- if (tc == src.col) {
- const sr = rects[id];
- if (cur_y >= sr.y and cur_y < sr.y + sr.h) return null;
- }
-
- const max_panes = l.col_terms[0].len;
- var heights: [max_panes]u16 = @splat(0);
- for (0..l.ncol) |c| {
- var k: usize = 0;
- while (k < l.col_n[c]) : (k += 1) {
- const pid = l.col_terms[c][k];
- heights[pid] = rects[pid].h;
- }
- }
-
- if (l.col_n[src.col] > 1) {
- const sib = if (src.idx > 0) l.col_terms[src.col][src.idx - 1] else l.col_terms[src.col][src.idx + 1];
- heights[sib] +|= rects[id].h;
- }
-
- var y: u16 = TOPBAR_H;
- var last: ?MovePlacement = null;
- var k: usize = 0;
- while (k < l.col_n[tc]) : (k += 1) {
- const pid = l.col_terms[tc][k];
- if (pid == id) continue;
- const h = heights[pid];
- const row = splitRowForExtent(y, h, cur_y) orelse {
- y +|= h;
- continue;
- };
- const placement: MovePlacement = .{
- .preview_col = tc,
- .above_id = pid,
- .row = row,
- .above_y = y,
- .above_h = h,
- };
- last = placement;
- if (cur_y < y + h) return placement;
- y +|= h;
- }
- return last;
-}
-
-fn setColumnWeights(
- l: anytype,
- items: anytype,
- col: usize,
- heights: anytype,
- comptime setVWeight: anytype,
-) void {
- if (col >= l.ncol) return;
- var k: usize = 0;
- while (k < l.col_n[col]) : (k += 1) {
- const pid = l.col_terms[col][k];
- setVWeight(items, pid, @as(f32, @floatFromInt(@max(1, heights[pid]))));
- }
-}
-
-pub fn moveTerm(
- l: anytype,
- items: anytype,
- rects: anytype,
- col_x: anytype,
- col_w: anytype,
- id: usize,
- cur_x: u16,
- cur_y: u16,
- comptime setVWeight: anytype,
-) void {
- const placement = movePlacement(l, rects, col_x, col_w, id, cur_x, cur_y) orelse return;
- const src = layoutFindTerm(l, id) orelse return;
- const source_multi = l.col_n[src.col] > 1;
-
- const max_panes = l.col_terms[0].len;
- var heights: [max_panes]u16 = @splat(0);
- for (0..l.ncol) |c| {
- var k: usize = 0;
- while (k < l.col_n[c]) : (k += 1) {
- const pid = l.col_terms[c][k];
- heights[pid] = rects[pid].h;
- }
- }
-
- layoutRemove(l, id);
- if (source_multi and src.col < l.ncol and l.col_n[src.col] > 0) {
- const sib = if (src.idx > 0) l.col_terms[src.col][src.idx - 1] else l.col_terms[src.col][src.idx];
- heights[sib] +|= rects[id].h;
- }
-
- const af = layoutFindTerm(l, placement.above_id) orelse return;
- const upper_h = @max(1, placement.row -| placement.above_y);
- const lower_h = @max(1, placement.above_h -| upper_h);
- heights[placement.above_id] = upper_h;
- heights[id] = lower_h;
- layoutInsert(l, af.col, af.idx + 1, id);
-
- setColumnWeights(l, items, af.col, &heights, setVWeight);
- if (source_multi and src.col < l.ncol and src.col != af.col) setColumnWeights(l, items, src.col, &heights, setVWeight);
-}
-
-pub fn paneAt(rects: anytype, pane_count: usize, col: u16, row: u16) ?usize {
- for (0..pane_count) |i| {
- const r = rects[i];
- if (col >= r.x and col < r.x + r.w and row >= r.y and row < r.y + r.h) return i;
- }
- return null;
-}
-
-pub fn paneAtSlots(rects: anytype, slots: anytype, col: u16, row: u16) ?usize {
- for (slots.*, 0..) |slot, i| {
- if (slot == null) continue;
- const r = rects[i];
- if (col >= r.x and col < r.x + r.w and row >= r.y and row < r.y + r.h) return i;
- }
- return null;
-}
-
-pub fn clampMousePos(screen_w: u16, screen_h: u16, mouse: anytype) MousePos {
- return .{
- .col = if (mouse.col < 0) 0 else @min(@as(u16, @intCast(mouse.col)), screen_w -| 1),
- .row = if (mouse.row < 0) 0 else @min(@as(u16, @intCast(mouse.row)), screen_h -| 1),
- };
-}
-
-pub fn inMoveBox(r: Rect, col: u16, row: u16) bool {
- return col < r.x + GUTTER and row < r.y + BOX_H;
-}
-
-pub fn scrollbarClickDelta(r: Rect, button: ScrollbarButton, col: u16, row: u16) ?i32 {
- if (col >= r.x + GUTTER or row < r.y + BOX_H) return null;
- const local: i32 = @as(i32, row) - @as(i32, r.y + BOX_H);
- return switch (button) {
- .left, .middle => -local,
- .right => local,
- };
-}
-
-pub fn drawGutter(
- root: anytype,
- r: Rect,
- active: bool,
- active_box: [3]u8,
- inactive_box: [3]u8,
- track: [3]u8,
- thumb: [3]u8,
- scrollbar: ?Scrollbar,
-) void {
- const gut = root.child(.{ .x_off = r.x, .y_off = r.y, .width = GUTTER, .height = r.h });
- const box_bg = if (active) active_box else inactive_box;
- var gy: u16 = 0;
- while (gy < r.h) : (gy += 1) {
- const bg = if (gy < BOX_H) box_bg else track;
- var gx: u16 = 0;
- while (gx < GUTTER) : (gx += 1) {
- gut.writeCell(gx, gy, .{ .char = .{ .grapheme = " " }, .style = .{ .bg = .{ .rgb = bg } } });
- }
- }
-
- const sb = scrollbar orelse return;
- const track_h: usize = if (r.h > BOX_H) r.h - BOX_H else 0;
- if (track_h == 0) return;
- const total = if (sb.total == 0) 1 else sb.total;
- const len = @max(1, (track_h * @max(1, sb.len)) / total);
- const pos = (track_h * sb.offset) / total;
- var sy: usize = 0;
- while (sy < track_h) : (sy += 1) {
- if (sy < pos or sy >= pos + len) continue;
- var gx: u16 = 0;
- while (gx < GUTTER) : (gx += 1) {
- gut.writeCell(gx, @intCast(BOX_H + sy), .{ .char = .{ .grapheme = " " }, .style = .{ .bg = .{ .rgb = thumb } } });
- }
- }
-}
-
-pub fn overlayDash(win: anytype, col: u16, row: u16, glyph: []const u8) void {
- var cell = win.readCell(col, row) orelse return;
- cell.char.grapheme = glyph;
- cell.default = false;
- win.writeCell(col, row, cell);
-}
-
-pub fn drawBorderV(win: anytype, col: u16, screen_h: u16) void {
- var row: u16 = TOPBAR_H;
- while (row < screen_h) : (row += 1) overlayDash(win, col, row, "╎");
-}
-
-pub fn drawBorderH(win: anytype, x: u16, width: u16, row: u16) void {
- var col = x;
- while (col < x + width) : (col += 1) overlayDash(win, col, row, "╌");
-}
-
-pub fn drawMovePreview(win: anytype, l: anytype, rects: anytype, col_x: anytype, col_w: anytype, id: usize, cur_x: u16, cur_y: u16, color: [3]u8) void {
- const placement = movePlacement(l, rects, col_x, col_w, id, cur_x, cur_y) orelse return;
- var col: u16 = col_x[placement.preview_col];
- while (col < col_x[placement.preview_col] + col_w[placement.preview_col]) : (col += 1) {
- win.writeCell(col, placement.row, .{ .char = .{ .grapheme = "╌" }, .style = .{ .fg = .{ .rgb = color } } });
- }
- win.writeCell(cur_x, placement.row, .{ .char = .{ .grapheme = "▌" }, .style = .{ .fg = .{ .rgb = color } } });
-}
-
-pub fn drawHoverResizeHints(
- win: anytype,
- l: anytype,
- rects: anytype,
- col_x: anytype,
- col_w: anytype,
- screen_h: u16,
- hover_col: u16,
- hover_row: u16,
-) void {
- var c: usize = 0;
- while (c + 1 < l.ncol) : (c += 1) {
- if (hover_col == col_x[c] + col_w[c] -| 1) drawBorderV(win, hover_col, screen_h);
- }
- var cc: usize = 0;
- while (cc < l.ncol) : (cc += 1) {
- if (hover_col < col_x[cc] or hover_col >= col_x[cc] + col_w[cc]) continue;
- var k: usize = 0;
- while (k + 1 < l.col_n[cc]) : (k += 1) {
- const r = rects[l.col_terms[cc][k]];
- if (hover_row == r.y + r.h -| 1) drawBorderH(win, col_x[cc], col_w[cc], hover_row);
- }
- }
-}
-
-pub fn drawInteractionPreview(
- win: anytype,
- l: anytype,
- rects: anytype,
- col_x: anytype,
- col_w: anytype,
- screen_h: u16,
- drag: anytype,
- hover_col: u16,
- hover_row: u16,
- move_color: [3]u8,
-) void {
- switch (drag) {
- .border_v => |d| drawBorderV(win, d.cur_x, screen_h),
- .border_h => |d| drawBorderH(win, col_x[d.col], col_w[d.col], d.cur_y),
- .move => |d| drawMovePreview(win, l, rects, col_x, col_w, d.id, d.cur_x, d.cur_y, move_color),
- else => drawHoverResizeHints(win, l, rects, col_x, col_w, screen_h, hover_col, hover_row),
- }
-}
-
-const TestLayout = Layout(4, 8);
-const TestItem = struct { vweight: f32 = 1 };
-
-fn testVWeight(items: []const TestItem, id: usize) ?f32 {
- return items[id].vweight;
-}
-
-fn setTestVWeight(items: []TestItem, id: usize, weight: f32) void {
- items[id].vweight = weight;
-}
-
-fn expectYH(r: Rect, y: u16, h: u16) !void {
- try std.testing.expectEqual(y, r.y);
- try std.testing.expectEqual(h, r.h);
-}
-
-test "move into one-pane column appends at marker row" {
- var layout: TestLayout = .{
- .ncol = 2,
- .col_weight = @splat(1),
- .col_terms = undefined,
- .col_n = @splat(0),
- };
- layout.col_n[0] = 2;
- layout.col_terms[0][0] = 0;
- layout.col_terms[0][1] = 1;
- layout.col_n[1] = 1;
- layout.col_terms[1][0] = 2;
-
- var items = [_]TestItem{ .{ .vweight = 10 }, .{ .vweight = 20 }, .{ .vweight = 30 }, .{} };
- var rects: [8]Rect = undefined;
- var col_x: [4]u16 = undefined;
- var col_w: [4]u16 = undefined;
- computeGeom(&layout, items[0..], 100, 31, &rects, &col_x, &col_w, testVWeight);
-
- const placement = movePlacement(&layout, &rects, &col_x, &col_w, 0, 75, 18) orelse return error.NoMovePlacement;
- try std.testing.expectEqual(@as(usize, 2), placement.above_id);
- try std.testing.expectEqual(@as(u16, 18), placement.row);
-
- moveTerm(&layout, items[0..], &rects, &col_x, &col_w, 0, 75, 18, setTestVWeight);
- computeGeom(&layout, items[0..], 100, 31, &rects, &col_x, &col_w, testVWeight);
-
- try std.testing.expectEqual(@as(usize, 2), layout.ncol);
- try std.testing.expectEqual(@as(usize, 1), layout.col_n[0]);
- try std.testing.expectEqual(@as(usize, 2), layout.col_n[1]);
- try std.testing.expectEqual(@as(usize, 2), layout.col_terms[1][0]);
- try std.testing.expectEqual(@as(usize, 0), layout.col_terms[1][1]);
- try expectYH(rects[1], 1, 30);
- try expectYH(rects[2], 1, 17);
- try expectYH(rects[0], 18, 13);
-}
-
-test "move into multi-pane column only splits the pane above" {
- var layout: TestLayout = .{
- .ncol = 2,
- .col_weight = @splat(1),
- .col_terms = undefined,
- .col_n = @splat(0),
- };
- layout.col_n[0] = 3;
- layout.col_terms[0][0] = 0;
- layout.col_terms[0][1] = 1;
- layout.col_terms[0][2] = 2;
- layout.col_n[1] = 1;
- layout.col_terms[1][0] = 3;
-
- var items = [_]TestItem{ .{ .vweight = 10 }, .{ .vweight = 10 }, .{ .vweight = 10 }, .{ .vweight = 30 } };
- var rects: [8]Rect = undefined;
- var col_x: [4]u16 = undefined;
- var col_w: [4]u16 = undefined;
- computeGeom(&layout, items[0..], 100, 31, &rects, &col_x, &col_w, testVWeight);
- const before_top = rects[0];
- const before_bottom = rects[2];
-
- const placement = movePlacement(&layout, &rects, &col_x, &col_w, 3, 20, 15) orelse return error.NoMovePlacement;
- try std.testing.expectEqual(@as(usize, 1), placement.above_id);
- try std.testing.expectEqual(@as(u16, 15), placement.row);
-
- moveTerm(&layout, items[0..], &rects, &col_x, &col_w, 3, 20, 15, setTestVWeight);
- computeGeom(&layout, items[0..], 100, 31, &rects, &col_x, &col_w, testVWeight);
-
- try std.testing.expectEqual(@as(usize, 1), layout.ncol);
- try std.testing.expectEqual(@as(usize, 4), layout.col_n[0]);
- try std.testing.expectEqual(@as(usize, 0), layout.col_terms[0][0]);
- try std.testing.expectEqual(@as(usize, 1), layout.col_terms[0][1]);
- try std.testing.expectEqual(@as(usize, 3), layout.col_terms[0][2]);
- try std.testing.expectEqual(@as(usize, 2), layout.col_terms[0][3]);
- try expectYH(rects[0], before_top.y, before_top.h);
- try expectYH(rects[1], 11, 4);
- try expectYH(rects[3], 15, 6);
- try expectYH(rects[2], before_bottom.y, before_bottom.h);
-}