Something went wrong. Try again.
A history browser for git, a replacement for gitk, drawn with Vulkan on Wayland.
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! Where the panes go, as `gitk` has them: the graph and commit list across//! the top; below, the details and diff on the left and the file list on//! the right. Both dividers can be dragged, and are kept as fractions, so a//! resized window keeps its proportions.
const std = @import("std");const aerie = @import("aerie");const Rect = aerie.gpu.Canvas.Rect;
const Panes = @This();
/// How much of the height the commit list takes.split_y: f32 = 0.45,/// How much of the bottom's width the details and diff take.split_x: f32 = 0.75,
pub const Divider = enum { horizontal, vertical };
pub const Layout = struct { list: Rect, diff: Rect, files: Rect, /// The dividers themselves: across, between the list and the bottom, /// and up and down, between the diff and the file list. horizontal: Rect, vertical: Rect,};
/// The panes in `area`, with dividers `thickness` wide.pub fn layout(p: *const Panes, area: Rect, thickness: f32) Layout { const usable_h = @max(0, area.height - thickness); const top_h = @round(usable_h * p.split_y); const bottom_y = area.y + top_h + thickness; const bottom_h = @max(0, area.y + area.height - bottom_y); const usable_w = @max(0, area.width - thickness); const left_w = @round(usable_w * p.split_x); return .{ .list = .{ .x = area.x, .y = area.y, .width = area.width, .height = top_h }, .horizontal = .{ .x = area.x, .y = area.y + top_h, .width = area.width, .height = thickness }, .diff = .{ .x = area.x, .y = bottom_y, .width = left_w, .height = bottom_h }, .vertical = .{ .x = area.x + left_w, .y = bottom_y, .width = thickness, .height = bottom_h }, .files = .{ .x = area.x + left_w + thickness, .y = bottom_y, .width = @max(0, usable_w - left_w), .height = bottom_h }, };}
/// The divider at `x`, `y`, if there is one within `grab` of it.pub fn dividerAt(l: Layout, x: f32, y: f32, grab: f32) ?Divider { const h = l.horizontal; if (x >= h.x and x < h.right() and y >= h.y - grab and y < h.bottom() + grab) return .horizontal; const v = l.vertical; if (y >= v.y and y < v.bottom() and x >= v.x - grab and x < v.right() + grab) return .vertical; return null;}
/// Moves `which` to where the pointer is, keeping every pane at least/// `min` across.pub fn drag(p: *Panes, which: Divider, area: Rect, x: f32, y: f32, min: f32) void { switch (which) { .horizontal => if (area.height > 2 * min) { p.split_y = std.math.clamp((y - area.y) / area.height, min / area.height, 1 - min / area.height); }, .vertical => if (area.width > 2 * min) { p.split_x = std.math.clamp((x - area.x) / area.width, min / area.width, 1 - min / area.width); }, }}
const testing = std.testing;
test "the panes fill the area, and the dividers are between them" { const p: Panes = .{}; const area: Rect = .{ .x = 0, .y = 30, .width = 1000, .height = 605 }; const l = p.layout(area, 5); try testing.expectEqual(@as(f32, 30), l.list.y); try testing.expectEqual(l.list.bottom(), l.horizontal.y); try testing.expectEqual(l.horizontal.bottom(), l.diff.y); try testing.expectEqual(area.bottom(), l.diff.bottom()); try testing.expectEqual(l.diff.right(), l.vertical.x); try testing.expectEqual(l.vertical.right(), l.files.x); try testing.expectEqual(area.right(), l.files.right());}
test "a divider is found near it, and dragged within bounds" { var p: Panes = .{}; const area: Rect = .{ .x = 0, .y = 0, .width = 1000, .height = 600 }; const l = p.layout(area, 4); try testing.expectEqual(@as(?Divider, .horizontal), dividerAt(l, 500, l.horizontal.y - 2, 3)); try testing.expectEqual(@as(?Divider, .vertical), dividerAt(l, l.vertical.x + 1, 500, 3)); try testing.expectEqual(@as(?Divider, null), dividerAt(l, 10, 10, 3)); p.drag(.horizontal, area, 0, -50, 60); try testing.expectEqual(@as(f32, 0.1), p.split_y); p.drag(.vertical, area, 500, 0, 60); try testing.expectEqual(@as(f32, 0.5), p.split_x);}