summaryrefslogtreecommitdiff
path: root/pane_ui.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-03 14:43:30 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commitd1ed5bd8f55935e6470c4a7559216119571a084f (patch)
tree434478ce8a5dcd9aed367334816f54da9953e56d /pane_ui.zig
parent153f0a88862507cf1f5dda71f71db3d6649dfeaa (diff)
downloadpardes-d1ed5bd8f55935e6470c4a7559216119571a084f.tar.gz
pardes-d1ed5bd8f55935e6470c4a7559216119571a084f.zip
fixed scaling and fitting on web ui
Diffstat (limited to 'pane_ui.zig')
-rw-r--r--pane_ui.zig273
1 files changed, 273 insertions, 0 deletions
diff --git a/pane_ui.zig b/pane_ui.zig
new file mode 100644
index 00000000..f53fa269
--- /dev/null
+++ b/pane_ui.zig
@@ -0,0 +1,273 @@
+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 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 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;
+ }
+}
+
+pub fn moveTerm(
+ l: anytype,
+ rects: anytype,
+ col_x: anytype,
+ col_w: anytype,
+ id: usize,
+ cur_x: u16,
+ cur_y: u16,
+) void {
+ const src = layoutFindTerm(l, id) orelse return;
+ 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;
+ }
+ }
+
+ var ti: usize = 0;
+ var k: usize = 0;
+ while (k < l.col_n[tc]) : (k += 1) {
+ const cid = l.col_terms[tc][k];
+ if (cid == id) continue;
+ const r = rects[cid];
+ if (cur_y < r.y + r.h / 2) break;
+ ti += 1;
+ }
+
+ const src_solo = l.col_n[src.col] == 1;
+ if (tc == src.col and src_solo) return;
+ layoutRemove(l, id);
+ if (src_solo and src.col < tc) tc -= 1;
+ if (tc >= l.ncol) tc = l.ncol - 1;
+ if (ti > l.col_n[tc]) ti = l.col_n[tc];
+ layoutInsert(l, tc, ti, id);
+}
+
+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 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, col_x: anytype, col_w: anytype, cur_x: u16, cur_y: u16, color: [3]u8) void {
+ 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;
+ }
+ }
+ var col: u16 = col_x[tc];
+ while (col < col_x[tc] + col_w[tc]) : (col += 1) {
+ win.writeCell(col, cur_y, .{ .char = .{ .grapheme = "╌" }, .style = .{ .fg = .{ .rgb = color } } });
+ }
+ win.writeCell(cur_x, cur_y, .{ .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);
+ }
+ }
+}