summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/gui.zig151
1 files changed, 125 insertions, 26 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 2c556895..d0757d7f 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -239,7 +239,7 @@ test "a persistent scene presents a new panel's frame zero before advancing it"
for (second.panelTracks()) |track| try std.testing.expectEqual(@as(u16, 1), track.frame);
}
-const max_overlay_vertices: usize = 6 + touch_click_flash_vertices + max_touch_points * (1100 + max_touch_trail_points * overlay_circle_vertices);
+const max_overlay_vertices: usize = 18 + touch_click_flash_vertices + max_touch_points * (1100 + max_touch_trail_points * overlay_circle_vertices);
// Ctrl+ / Ctrl-: how far one press moves g.px, and the two sizes it stops at.
// ONE size for the whole window, not one per pane: the core lays every pane
@@ -2014,6 +2014,8 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options) !void {
gpa,
surface,
core.theme().bg,
+ config.gui_topbar_pane_border_rgb orelse core.chromeTheme().scroll_track,
+ core.chromeTheme().tag_bg,
core.settings.scene_effects,
core.settings.debug,
) catch |err| blk: {
@@ -3710,7 +3712,7 @@ fn prepareNativeImages(
if (saved.serial != active.serial) continue;
appendPreparedImage(g, gpa, saved, active, null, false);
}
- } else if (active.effect == .dissolve or active.effect == .ascii) {
+ } else if (active.effect == .dissolve) {
// Old layer first. Its shader half disappears at the same
// per-cell threshold at which the current half appears.
for (g.presented_images.items) |saved| {
@@ -3729,8 +3731,8 @@ fn prepareNativeImages(
const current_track = if (track) |active| switch (active.effect) {
// An identical cached placement is semantically unchanged
// and must bypass a data effect exactly like an unchanged
- // cell in Surface.changed_cells.
- .dissolve, .ascii => if (snapshotContainsPlacement(g, current)) null else active,
+ // cell in Surface.cell_diffs.
+ .dissolve => if (snapshotContainsPlacement(g, current)) null else active,
else => active,
} else null;
appendPreparedImage(g, gpa, saved, current_track, null, false);
@@ -3860,6 +3862,8 @@ fn renderFrame(
gpa: std.mem.Allocator,
surface: *pardes.Surface,
theme_bg: ?[3]u8,
+ topbar_pane_border_rgb: [3]u8,
+ tagline_rgb: [3]u8,
scene_effects: pardes.panel_animation.SceneEffect,
debug_on: bool,
) !bool {
@@ -3920,14 +3924,22 @@ fn renderFrame(
const layout = fixedCellLayout(g);
var paint_plan = makePaintPlan(surface.panelTracks(), surface.hasPanelDiff());
- // Cursors/debug overlays do not carry pane ownership. Hide them for the
+ // Cursors/debug overlays do not carry pane ownership. Hide those for the
// short interval in which panel geometry differs from logical geometry;
// otherwise a cursor could remain pinned at the final cell, or paint over
- // a later opening pane, while its own pane moves underneath it.
- var overlay_count = if (paint_plan.len == 1)
- buildOverlay(g, surface, layout, sw, sh, debug_on)
- else
- 0;
+ // a later opening pane, while its own pane moves underneath it. The
+ // topbar/pane rule is anchored window chrome and remains present.
+ var overlay_count = buildOverlay(
+ g,
+ surface,
+ layout,
+ sw,
+ sh,
+ topbar_pane_border_rgb,
+ tagline_rgb,
+ paint_plan.len == 1,
+ debug_on,
+ );
if (overlay_count != 0 and !uploadOverlayGpu(g, cmd, overlay_count))
overlay_count = 0;
if (!prepareNativeImages(g, gpa, cmd, surface, &paint_plan, sw, sh)) {
@@ -3962,7 +3974,7 @@ fn renderFrame(
const batch_index = paintBatchAt(&paint_plan, col, row);
const track = paint_plan.batches[batch_index].track;
const data_effect = if (track) |active|
- active.effect == .dissolve or active.effect == .ascii
+ active.effect == .dissolve
else
false;
const data_diff = data_effect and surface.panelCellChanged(col, row);
@@ -4022,7 +4034,7 @@ fn renderFrame(
const track = paint_plan.batches[batch_index].track;
const logical_idx: u32 = @as(u32, row) * surface.cols + col;
const data_effect = if (track) |active|
- active.effect == .dissolve or active.effect == .ascii
+ active.effect == .dissolve
else
false;
const data_diff = data_effect and surface.panelCellChanged(col, row);
@@ -4219,6 +4231,55 @@ const ResolvedCell = struct {
role: pardes.FontRole,
};
+fn topbarPaneBorderPixels(cell_h: u32, tagline_h: u32) u32 {
+ const spare = cell_h -| tagline_h;
+ return @min(@as(u32, config.gui_topbar_pane_border_px), spare * 2);
+}
+
+/// Vertical offset of a tagline band inside its body-sized logical row. Row
+/// zero and the first pane-tag row face a shared rule instead of centering two
+/// independent bands. A Tagbottom band on the final grid row faces the window
+/// edge, eliminating the matching unused half-band at the bottom.
+fn taglineBandOffset(row: u16, canvas_h: f32, cell_h: u32, tagline_h: u32) u32 {
+ const spare = cell_h -| tagline_h;
+ const border = topbarPaneBorderPixels(cell_h, tagline_h);
+ if (row == 0) return spare - border / 2;
+ if (row == pardes.TOPBAR_H) return border - border / 2;
+ const rows: u32 = @max(1, @as(u32, @intFromFloat(@floor(canvas_h / @as(f32, @floatFromInt(cell_h))))));
+ if (@as(u32, row) + 1 >= rows) return spare;
+ return spare / 2;
+}
+
+fn topbarPaneBorderHeight(surface: *const pardes.Surface, cell_h: u32, tagline_h: u32) ?u32 {
+ if (surface.rows <= pardes.TOPBAR_H) return null;
+ const height = topbarPaneBorderPixels(cell_h, tagline_h);
+ if (height == 0) return null;
+ const base = @as(usize, pardes.TOPBAR_H) * surface.cols;
+ for (surface.cells[base..][0..surface.cols]) |cell|
+ if (!cell.default and cell.style.font_role == .tagline) return height;
+ return null;
+}
+
+fn bottomTaglinePresent(surface: *const pardes.Surface) bool {
+ if (surface.rows == 0) return false;
+ const base = @as(usize, surface.rows - 1) * surface.cols;
+ for (surface.cells[base..][0..surface.cols]) |cell|
+ if (!cell.default and cell.style.font_role == .tagline) return true;
+ return false;
+}
+
+test "tagline bands face the topbar rule and Tagbottom faces the window edge" {
+ const cell_h: u32 = 20;
+ const tagline_h: u32 = 16;
+ const canvas_h: f32 = 200;
+ const border = topbarPaneBorderPixels(cell_h, tagline_h);
+ try std.testing.expectEqual(@as(u32, 1), border);
+ try std.testing.expectEqual(@as(u32, 4), taglineBandOffset(0, canvas_h, cell_h, tagline_h));
+ try std.testing.expectEqual(@as(u32, 1), taglineBandOffset(1, canvas_h, cell_h, tagline_h));
+ try std.testing.expectEqual(@as(u32, 2), taglineBandOffset(5, canvas_h, cell_h, tagline_h));
+ try std.testing.expectEqual(@as(u32, 4), taglineBandOffset(9, canvas_h, cell_h, tagline_h));
+}
+
fn resolveCell(g: *Gui, cell: *const pardes.Cell, is_cursor: bool, page: [3]u8) ResolvedCell {
var fg = fg_default;
var bg = page;
@@ -4283,8 +4344,11 @@ fn emitInstance(
@floatFromInt(g.tagline_height)
else
layout.h;
- const py0 = layout.y_off + @as(f32, @floatFromInt(row)) * layout.h +
- @max(0.0, (layout.h - visual_h) * 0.5);
+ const band_offset: f32 = if (resolved.role == .tagline)
+ @floatFromInt(taglineBandOffset(row, win_h, g.cell_h, g.tagline_height))
+ else
+ 0;
+ const py0 = layout.y_off + @as(f32, @floatFromInt(row)) * layout.h + band_offset;
const x0 = (px0 / win_w) * 2.0 - 1.0;
const x1 = ((px0 + layout.w) / win_w) * 2.0 - 1.0;
const y0 = 1.0 - (py0 / win_h) * 2.0;
@@ -4812,7 +4876,7 @@ fn addCursorBar(
const x0 = layout.x_off + @as(f32, @floatFromInt(x)) * layout.w;
const height = std.math.clamp(visual_height, 1.0, layout.h);
const y0 = layout.y_off + @as(f32, @floatFromInt(y)) * layout.h +
- (layout.h - height) * 0.5;
+ @as(f32, @floatFromInt(taglineBandOffset(y, builder.win_h, @intFromFloat(layout.h), @intFromFloat(height))));
builder.addRect(
x0,
y0,
@@ -4827,23 +4891,58 @@ fn addCursorBar(
);
}
-fn buildOverlay(g: *Gui, surface: *const pardes.Surface, layout: CellLayout, sw: u32, sh: u32, debug_on: bool) u32 {
+fn buildOverlay(
+ g: *Gui,
+ surface: *const pardes.Surface,
+ layout: CellLayout,
+ sw: u32,
+ sh: u32,
+ topbar_pane_border_rgb: [3]u8,
+ tagline_rgb: [3]u8,
+ transient_on: bool,
+ debug_on: bool,
+) u32 {
if (g.overlay_vertices.len == 0 or sw == 0 or sh == 0) return 0;
const win_w: f32 = @floatFromInt(sw);
const win_h: f32 = @floatFromInt(sh);
var builder = OverlayBuilder{ .vertices = g.overlay_vertices, .win_w = win_w, .win_h = win_h };
- if (surface.cursor) |cursor| {
- if (cursor.x < surface.cols and cursor.y < surface.rows) {
- const cell = surface.cells[@as(usize, cursor.y) * surface.cols + cursor.x];
- const role = cell.style.font_role;
- const visual_height: f32 = if (role == .tagline)
- @floatFromInt(g.tagline_height)
- else
- layout.h;
- addCursorBar(&builder, cursor.x, cursor.y, cursor.bar, visual_height, layout);
+ if (topbarPaneBorderHeight(surface, g.cell_h, g.tagline_height)) |height| {
+ const y0: f32 = @floatFromInt(g.cell_h - height / 2);
+ const rgb = topbar_pane_border_rgb;
+ builder.addRect(0, y0, win_w, y0 + @as(f32, @floatFromInt(height)), .{
+ .r = @as(f32, @floatFromInt(rgb[0])) / 255.0,
+ .g = @as(f32, @floatFromInt(rgb[1])) / 255.0,
+ .b = @as(f32, @floatFromInt(rgb[2])) / 255.0,
+ .a = 1.0,
+ });
+ }
+ // The core grid contains only complete cells. Extend a bottommost
+ // Tagbottom band through the swapchain remainder so an arbitrary window
+ // height cannot reintroduce a page-colored strip below the final row.
+ const grid_bottom = @as(f32, @floatFromInt(surface.rows)) * layout.h;
+ if (grid_bottom < win_h and bottomTaglinePresent(surface)) {
+ const rgb = tagline_rgb;
+ builder.addRect(0, grid_bottom, win_w, win_h, .{
+ .r = @as(f32, @floatFromInt(rgb[0])) / 255.0,
+ .g = @as(f32, @floatFromInt(rgb[1])) / 255.0,
+ .b = @as(f32, @floatFromInt(rgb[2])) / 255.0,
+ .a = 1.0,
+ });
+ }
+ if (transient_on) {
+ if (surface.cursor) |cursor| {
+ if (cursor.x < surface.cols and cursor.y < surface.rows) {
+ const cell = surface.cells[@as(usize, cursor.y) * surface.cols + cursor.x];
+ const role = cell.style.font_role;
+ const visual_height: f32 = if (role == .tagline)
+ @floatFromInt(g.tagline_height)
+ else
+ layout.h;
+ addCursorBar(&builder, cursor.x, cursor.y, cursor.bar, visual_height, layout);
+ }
}
}
- appendTouchOverlay(g, &builder, debug_on);
+ if (transient_on) appendTouchOverlay(g, &builder, debug_on);
return @intCast(builder.len);
}