diff options
Diffstat (limited to 'src/gui')
| -rw-r--r-- | src/gui/gui.zig | 86 |
1 files changed, 73 insertions, 13 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 9748d4c0..5dfacee9 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -73,6 +73,24 @@ const p9_arrow_set = [32]u8{ 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8, 0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00, }; +/// acme's boxcursor (acme.c:956), while a grip carries a pane or column. +const p9_box_clr = [32]u8{ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, + 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; +const p9_box_set = [32]u8{ + 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, + 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00, +}; +const p9_box_mask = blk: { + var m: [32]u8 = undefined; + for (&m, p9_box_set, p9_box_clr) |*b, s, cl| b.* = s | cl; + break :blk m; +}; const p9_arrow_mask = blk: { var m: [32]u8 = undefined; for (&m, p9_arrow_set, p9_arrow_clr) |*b, s, cl| b.* = s | cl; @@ -1849,6 +1867,7 @@ const Gui = struct { pointer_present: bool = false, arrow_cursor: ?*c.SDL_Cursor = null, link_cursor: ?*c.SDL_Cursor = null, + box_cursor: ?*c.SDL_Cursor = null, pointer_shape: pardes.Surface.PointerShape = .arrow, pointer_mapped: bool = false, pointer_cell: ?MouseCell = null, @@ -2307,6 +2326,9 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u defer if (arrow_cursor) |cursor| c.SDL_DestroyCursor(cursor); const link_cursor = c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_POINTER); defer if (link_cursor) |cursor| c.SDL_DestroyCursor(cursor); + // Its hot spot the middle, as acme's {-7, -7} offset puts it. + const box_cursor = c.SDL_CreateCursor(&p9_box_set, &p9_box_mask, 16, 16, 7, 7); + defer if (box_cursor) |cursor| c.SDL_DestroyCursor(cursor); if (arrow_cursor) |cur| { _ = c.SDL_SetCursor(cur); } else log.err("SDL_CreateCursor: {s}", .{c.SDL_GetError()}); @@ -2442,6 +2464,7 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u .window = window, .arrow_cursor = arrow_cursor, .link_cursor = link_cursor, + .box_cursor = box_cursor, .device = device, .swapchain_format = swapchain_format, .pipeline = pipeline, @@ -3758,9 +3781,10 @@ test "tagline pointer mapping retains physical grips and carries logical text co try std.testing.expect(grip.tag_hit == null); try std.testing.expectEqual(r.x + config.GUTTER - 1, grip.col); - const body = gridCellAtDimensions(core, x, (@as(f32, @floatFromInt(tag_y + 1)) + 0.5) * body_h, body_w, body_h, tag_w); + const body_y = tag_y + core.panes[core.active].?.tag_rows; // a long tag wraps + const body = gridCellAtDimensions(core, x, (@as(f32, @floatFromInt(body_y)) + 0.5) * body_h, body_w, body_h, tag_w); try std.testing.expectEqual(pixelCell(x, 10), body.col); - try std.testing.expectEqual(tag_y + 1, body.row); + try std.testing.expectEqual(body_y, body.row); try std.testing.expect(body.tag_hit == null); const topbar = gridCellAtDimensions(core, 3.5 * tag_w, 0.5 * body_h, body_w, body_h, tag_w); @@ -4075,15 +4099,13 @@ const Shell = struct { host_io.commandEof(s.core, s.ptys, e.pane, s, closeWatched); break :eof; } - // Unwatched, a command's exit is read here, as its end. + // Unwatched, a command's exit is read here, as its end, + // and a shell's, for a run waiting on it and the log. const unwatched = if (s.core.panes[e.pane]) |pane| pane.command != null else false; - var status: ?u8 = null; - if (unwatched) { - status = host_io.exitStatus(pt.pid, 100); - if (status != null) pt.pid = 0; // reaped: no shell to retire - } + const status = host_io.exitStatus(pt.pid, 100); + if (status != null) pt.pid = 0; // reaped: no shell to retire s.closePty(e.pane); - if (unwatched) s.core.update(.{ .exited = .{ .pane = e.pane, .status = status } }); + if (unwatched or status != null) s.core.update(.{ .exited = .{ .pane = e.pane, .status = status } }); s.core.update(.{ .eof = .{ .pane = e.pane } }); } s.saw_event = true; @@ -4428,7 +4450,7 @@ fn ptyResize(ctx: ?*anyopaque, pane: u8, cols: u16, rows: u16) void { fn ptySignal(ctx: ?*anyopaque, pane: u8, sig: pardes.PtySignal) void { const s = shellOf(ctx); // A command whose exit is recorded is reaped: its group may be another's. - if (s.ptys[pane]) |pt| if (pt.fd >= 0 and !pt.cmd.exited) host_io.signalTty(pt.pid, pt.fd, sig); + if (s.ptys[pane]) |pt| if (pt.fd >= 0 and !pt.cmd.exited) host_io.signalTty(pt.pid, pt.fd, sig, pt.cmd.watched); } fn ttyTaken(ctx: ?*anyopaque, pane: u8) bool { @@ -4448,7 +4470,7 @@ fn killJob(ctx: ?*anyopaque, pane: u8) bool { fn writeFile(ctx: ?*anyopaque, pane: u8, path: []const u8, bytes: []const u8) void { const s = shellOf(ctx); filesystem.write(s.core, path, bytes) catch |err| - return s.core.saveFailed(pane, "save", err); + return s.core.saveFailed(pane, path, err); if (s.core.panes[pane]) |pane_state| if (pane_state.file) |f| { if (std.mem.eql(u8, f.path, path)) if (s.watches[pane]) |*w| if (w.serial == pane_state.serial) switch (w.generation) { @@ -5296,7 +5318,11 @@ fn renderFrame( ) !bool { if (surface.pointer_shape != g.pointer_shape) { g.pointer_shape = surface.pointer_shape; - const cursor = if (g.pointer_shape == .link) g.link_cursor else g.arrow_cursor orelse c.SDL_GetDefaultCursor(); + const cursor = switch (g.pointer_shape) { + .link => g.link_cursor, + .box => g.box_cursor orelse g.arrow_cursor, + else => g.arrow_cursor orelse c.SDL_GetDefaultCursor(), + }; if (cursor) |value| _ = c.SDL_SetCursor(value); } g.scene_target_failed = false; @@ -5842,7 +5868,6 @@ test "the layer cover is the per-cell layer scan, over a core's layouts and rand var notices = false; for (0..32) |step| { core.settings.tag_bottom = step & 1 != 0; - core.settings.column_tags = step & 2 != 0; if (step & 3 == 0) try std.testing.expect(core.executeBuiltinLine(2, "Collapse")); core.leader_on = step & 4 != 0; if (step & 8 != 0) { @@ -8300,6 +8325,41 @@ test "the rule between stacked panes stays in the tag band's slack, at any tagli } } +test "an empty column is ruled on both sides, as between two filled ones" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); + defer core.deinit(); + _ = try core.newShell(1, ""); + try std.testing.expect(pardes.layout.splitColumn(core, 0, 1, false)); + // Newcol from the left pane: an empty column between the two filled ones. + core.newColumn(0); + pardes.test_api.sync(core); + try std.testing.expectEqual(@as(usize, 3), core.ncol); + try std.testing.expectEqual(@as(usize, 0), core.col_n[1]); + const s = try core.render(core.scratch.allocator()); + var g: Gui = undefined; + g.decor = .empty; + defer g.decor.deinit(gpa); + g.cell_w = 10; + g.cell_h = 20; + g.tagline_width = 6; + g.tagline_height = 12; + const layout = fixedCellLayout(&g); + var groups = makeGroups(&.{}, false); + try buildDecor(&g, gpa, s, &groups, layout, 800, 480, true); + const border = [3]f32{ @as(f32, @floatFromInt(s.chrome.border[0])) / 255, @as(f32, @floatFromInt(s.chrome.border[1])) / 255, @as(f32, @floatFromInt(s.chrome.border[2])) / 255 }; + // Its left edge and its right edge (the next column's left): each a + // rule `rule_px` wide in the border colour, down to the window's foot. + for ([_]usize{ 1, 2 }) |column| { + const px: f32 = @floatFromInt(@as(u32, core.col_x[column]) * 10); + const rule = for (g.decor.items) |item| { + if (@abs(item.x0 - (px / 800 * 2 - 1)) < 1e-4 and @abs(item.x1 - ((px + @as(f32, @floatFromInt(s.chrome.rule_px))) / 800 * 2 - 1)) < 1e-4 and + std.mem.eql(f32, &.{ item.br, item.bg, item.bb }, &border)) break item; + } else return error.TestExpectedColumnRule; + try std.testing.expectEqual(@as(f32, -1), rule.y1); + } +} + test "decor is read from the regions, each pane's in its own group" { const gpa = std.testing.allocator; const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 }); |
