diff options
Diffstat (limited to 'src/gui/gui.zig')
| -rw-r--r-- | src/gui/gui.zig | 151 |
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); } |
