From 50d47a73f2c02ccf907d7bd2fa50c5a326f79383 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 13 Aug 2024 19:38:35 +0200 Subject: [PATCH] feat: add mouse resizing of panel views --- src/tui/Button.zig | 16 ++++++++++++++- src/tui/Widget.zig | 15 ++++++++++++++ src/tui/WidgetList.zig | 10 ++++++++++ src/tui/filelist_view.zig | 11 ++++------- src/tui/mainview.zig | 33 ++++++++++++++++++++++++++----- src/tui/status/diagstate.zig | 3 ++- src/tui/status/filestate.zig | 3 ++- src/tui/status/keystate.zig | 19 +++++------------- src/tui/status/linenumstate.zig | 3 ++- src/tui/status/minilog.zig | 17 ++++++++++++---- src/tui/status/modestate.zig | 3 ++- src/tui/status/modstate.zig | 12 +++-------- src/tui/status/selectionstate.zig | 24 +++++++++++----------- src/tui/status/statusbar.zig | 27 ++++++++++++++++++------- src/tui/tui.zig | 7 +++++-- 15 files changed, 138 insertions(+), 65 deletions(-) diff --git a/src/tui/Button.zig b/src/tui/Button.zig index 1c17373..92e596a 100644 --- a/src/tui/Button.zig +++ b/src/tui/Button.zig @@ -23,6 +23,7 @@ pub fn Options(context: type) type { on_render: *const fn (ctx: *context, button: *State(Context), theme: *const Widget.Theme) bool = on_render_default, on_layout: *const fn (ctx: *context, button: *State(Context)) Widget.Layout = on_layout_default, on_receive: *const fn (ctx: *context, button: *State(Context), from: tp.pid_ref, m: tp.message) error{Exit}!bool = on_receive_default, + on_event: ?Widget.EventHandler = null, pub const Context = context; pub fn do_nothing(_: *context, _: *State(Context)) void {} @@ -109,7 +110,17 @@ pub fn State(ctx_type: type) type { self.call_click_handler(btn); tui.need_render(); return true; + } else if (try m.match(.{ "D", event_type.PRESS, tp.extract(&btn), tp.more })) { + if (self.opts.on_event) |h| { + self.active = false; + h.send(from, m) catch {}; + } + return true; } else if (try m.match(.{ "D", event_type.RELEASE, tp.extract(&btn), tp.more })) { + if (self.opts.on_event) |h| { + self.active = false; + h.send(from, m) catch {}; + } self.call_click_handler(btn); tui.need_render(); return true; @@ -122,7 +133,10 @@ pub fn State(ctx_type: type) type { } fn call_click_handler(self: *Self, btn: u32) void { - if (btn == key.BUTTON1) self.active = false; + if (btn == key.BUTTON1) { + if (!self.active) return; + self.active = false; + } if (!self.hover) return; switch (btn) { key.BUTTON1 => self.opts.on_click(&self.opts.ctx, self), diff --git a/src/tui/Widget.zig b/src/tui/Widget.zig index e37bbe9..bf7092c 100644 --- a/src/tui/Widget.zig +++ b/src/tui/Widget.zig @@ -49,6 +49,7 @@ pub const VTable = struct { unsubscribe: *const fn (ctx: *anyopaque, h: EventHandler) error{NotSupported}!void, get: *const fn (ctx: *anyopaque, name_: []const u8) ?*Self, walk: *const fn (ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn, self_widget: *Self) bool, + hover: *const fn (ctx: *anyopaque) bool, type_name: []const u8, }; @@ -136,6 +137,11 @@ pub fn to(pimpl: anytype) Self { return if (comptime @hasDecl(child, "walk")) child.walk(@as(*child, @ptrCast(@alignCast(ctx))), walk_ctx, f, self) else false; } }.walk, + .hover = struct { + pub fn hover(ctx: *anyopaque) bool { + return if (comptime @hasField(child, "hover")) @as(*child, @ptrCast(@alignCast(ctx))).hover else false; + } + }.hover, }, }; } @@ -211,6 +217,10 @@ pub fn walk(self: *Self, walk_ctx: *anyopaque, f: WalkFn) bool { return if (self.vtable.walk(self.ptr, walk_ctx, f, self)) true else f(walk_ctx, self); } +pub fn hover(self: *Self) bool { + return self.vtable.hover(self.ptr); +} + pub fn empty(a: Allocator, parent: Plane, layout_: Layout) !Self { const child: type = struct { plane: Plane, layout: Layout }; const widget = try a.create(child); @@ -270,6 +280,11 @@ pub fn empty(a: Allocator, parent: Plane, layout_: Layout) !Self { return false; } }.walk, + .hover = struct { + pub fn hover(_: *anyopaque) bool { + return false; + } + }.hover, }, }; } diff --git a/src/tui/WidgetList.zig b/src/tui/WidgetList.zig index d0b79b0..796bf32 100644 --- a/src/tui/WidgetList.zig +++ b/src/tui/WidgetList.zig @@ -28,6 +28,7 @@ direction: Direction, box: ?Widget.Box = null, ctx: ?*anyopaque = null, on_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default, +after_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default, on_resize: *const fn (ctx: ?*anyopaque, self: *Self, pos_: Widget.Box) void = on_resize_default, pub fn createH(a: Allocator, parent: Widget, name: [:0]const u8, layout_: Layout) !*Self { @@ -135,6 +136,8 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { if (w.widget.render(theme)) { more = true; }; + + self.after_render(self.ctx, theme); return more; } @@ -190,6 +193,8 @@ fn on_resize_default(_: ?*anyopaque, self: *Self, pos: Widget.Box) void { pub fn resize(self: *Self, pos_: Widget.Box) void { self.box = pos_; var pos = pos_; + self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return; + self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return; const total = self.get_size_a(&pos).*; var avail = total; var statics: usize = 0; @@ -243,3 +248,8 @@ pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, self_w: *Widget) boo if (w.widget.walk(ctx, f)) return true; return f(ctx, self_w); } + +pub fn hover(self: *Self) bool { + for (self.widgets.items) |*w| if (w.widget.hover()) return true; + return false; +} diff --git a/src/tui/filelist_view.zig b/src/tui/filelist_view.zig index 21dc319..5293820 100644 --- a/src/tui/filelist_view.zig +++ b/src/tui/filelist_view.zig @@ -140,9 +140,6 @@ fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), th const style_info: Widget.Theme.Style = .{ .fg = theme.editor_information.fg, .fs = theme.editor_information.fs, .bg = style_base.bg }; const style_separator: Widget.Theme.Style = .{ .fg = theme.editor_selection.bg, .bg = style_base.bg }; // const style_error: Widget.Theme.Style = .{ .fg = theme.editor_error.fg, .fs = theme.editor_error.fs, .bg = style_base.bg }; - button.plane.set_base_style(" ", style_base); - button.plane.erase(); - button.plane.home(); var idx: usize = undefined; var iter = button.opts.label; // label contains cbor, just the index if (!(cbor.matchValue(&iter, cbor.extract(&idx)) catch false)) { @@ -155,6 +152,9 @@ fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), th if (idx >= self.entries.items.len) { return false; } + button.plane.set_base_style(" ", style_base); + button.plane.erase(); + button.plane.home(); const entry = &self.entries.items[idx]; const pointer = if (selected) "⏵" else " "; _ = button.plane.print("{s} ", .{pointer}) catch {}; @@ -229,10 +229,7 @@ fn handle_menu_action(menu: **Menu.State(*Self), button: *Button.State(*Menu.Sta return; } idx += self.view_pos; - if (idx >= self.entries.items.len) { - self.logger.print_err(name, "table entry index out of range: {d}/{d}", .{ idx, self.entries.items.len }); - return; - } + if (idx >= self.entries.items.len) return; self.selected = idx; self.update_selected(); const entry = &self.entries.items[idx]; diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index b80ff8a..ae2e983 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -8,6 +8,8 @@ const location_history = @import("location_history"); const project_manager = @import("project_manager"); const Plane = @import("renderer").Plane; +const key = @import("renderer").input.key; +const event_type = @import("renderer").input.event_type; const tui = @import("tui.zig"); const command = @import("command.zig"); @@ -35,6 +37,7 @@ last_match_text: ?[]const u8 = null, location_history: location_history, file_stack: std.ArrayList([]const u8), find_in_files_done: bool = false, +panel_height: ?usize = null, const NavState = struct { time: i64 = 0, @@ -45,11 +48,11 @@ const NavState = struct { matches: usize = 0, }; -pub fn create(a: std.mem.Allocator, n: Plane) !Widget { +pub fn create(a: std.mem.Allocator) !Widget { const self = try a.create(Self); self.* = .{ .a = a, - .plane = n, + .plane = tui.current().stdplane(), .widgets = undefined, .widgets_widget = undefined, .floating_views = WidgetStack.init(a), @@ -62,8 +65,8 @@ pub fn create(a: std.mem.Allocator, n: Plane) !Widget { const widgets = try WidgetList.createV(a, w, @typeName(Self), .dynamic); self.widgets = widgets; self.widgets_widget = widgets.widget(); - try widgets.add(try Widget.empty(a, n, .dynamic)); - self.statusbar = try widgets.addP(try @import("status/statusbar.zig").create(a, w)); + try widgets.add(try Widget.empty(a, self.widgets_widget.plane.*, .dynamic)); + self.statusbar = try widgets.addP(try @import("status/statusbar.zig").create(a, w, EventHandler.bind(self, handle_statusbar_event))); if (tp.env.get().is("show-input")) self.toggle_inputview_async(); if (tp.env.get().is("show-log")) @@ -125,6 +128,10 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool { } pub fn handle_resize(self: *Self, pos: Box) void { + self.plane = tui.current().stdplane(); + if (self.panel_height) |h| if (h >= self.box().h) { + self.panel_height = null; + }; self.widgets.handle_resize(pos); self.floating_views.resize(pos); } @@ -133,6 +140,22 @@ pub fn box(self: *const Self) Box { return Box.from(self.plane); } +pub fn handle_statusbar_event(self: *Self, _: tp.pid_ref, m: tp.message) tp.result { + var y: usize = undefined; + if (try m.match(.{ "D", event_type.PRESS, key.BUTTON1, tp.any, tp.any, tp.extract(&y), tp.any, tp.any })) + return self.statusbar_primary_drag(y); +} + +fn statusbar_primary_drag(self: *Self, y: usize) tp.result { + const panels = self.panels orelse blk: { + cmds.toggle_panel(self, .{}) catch return; + break :blk self.panels.?; + }; + const h = self.plane.dim_y(); + self.panel_height = @max(2, h - @min(h, y + 1)); + panels.layout = .{ .static = self.panel_height.? }; +} + fn toggle_panel_view(self: *Self, view: anytype, enable_only: bool) !bool { var enabled = true; if (self.panels) |panels| { @@ -149,7 +172,7 @@ fn toggle_panel_view(self: *Self, view: anytype, enable_only: bool) !bool { try panels.add(try view.create(self.a, self.widgets.plane)); } } else { - const panels = try WidgetList.createH(self.a, self.widgets.widget(), "panel", .{ .static = self.box().h / 5 }); + const panels = try WidgetList.createH(self.a, self.widgets.widget(), "panel", .{ .static = self.panel_height orelse self.box().h / 5 }); try self.widgets.add(panels.widget()); try panels.add(try view.create(self.a, self.widgets.plane)); self.panels = panels; diff --git a/src/tui/status/diagstate.zig b/src/tui/status/diagstate.zig index cabd008..37c6322 100644 --- a/src/tui/status/diagstate.zig +++ b/src/tui/status/diagstate.zig @@ -19,7 +19,7 @@ rendered: [:0]const u8 = "", const Self = @This(); -pub fn create(a: Allocator, parent: Plane) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { return Button.create_widget(Self, a, parent, .{ .ctx = .{}, .label = "", @@ -27,6 +27,7 @@ pub fn create(a: Allocator, parent: Plane) !Widget { .on_layout = layout, .on_render = render, .on_receive = receive, + .on_event = event_handler, }); } diff --git a/src/tui/status/filestate.zig b/src/tui/status/filestate.zig index a2c3385..f3f9b23 100644 --- a/src/tui/status/filestate.zig +++ b/src/tui/status/filestate.zig @@ -34,7 +34,7 @@ file: bool = false, const project_icon = ""; const Self = @This(); -pub fn create(a: Allocator, parent: Plane) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { const btn = try Button.create(Self, a, parent, .{ .ctx = .{ .a = a, @@ -52,6 +52,7 @@ pub fn create(a: Allocator, parent: Plane) !Widget { .on_layout = layout, .on_render = render, .on_receive = receive, + .on_event = event_handler, }); return Widget.to(btn); } diff --git a/src/tui/status/keystate.zig b/src/tui/status/keystate.zig index bbd74cd..3d5d8c1 100644 --- a/src/tui/status/keystate.zig +++ b/src/tui/status/keystate.zig @@ -15,7 +15,6 @@ const EventHandler = @import("../EventHandler.zig"); const history = 8; -parent: Plane, plane: Plane, frame: u64 = 0, idle_frame: u64 = 0, @@ -33,23 +32,15 @@ const idle_msg = "🐶"; pub const width = idle_msg.len + 20; pub fn create(a: Allocator, parent: Plane) !Widget { - const self: *Self = try a.create(Self); - self.* = try init(parent); - try tui.current().input_listeners.add(EventHandler.bind(self, listen)); - return self.widget(); -} - -fn init(parent: Plane) !Self { - var n = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent); - errdefer n.deinit(); var frame_rate = tp.env.get().num("frame-rate"); if (frame_rate == 0) frame_rate = 60; - - return .{ - .parent = parent, - .plane = n, + const self: *Self = try a.create(Self); + self.* = .{ + .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), .wipe_after_frames = @divTrunc(frame_rate, 2), }; + try tui.current().input_listeners.add(EventHandler.bind(self, listen)); + return self.widget(); } pub fn widget(self: *Self) Widget { diff --git a/src/tui/status/linenumstate.zig b/src/tui/status/linenumstate.zig index a51ed8a..5bd281b 100644 --- a/src/tui/status/linenumstate.zig +++ b/src/tui/status/linenumstate.zig @@ -18,7 +18,7 @@ rendered: [:0]const u8 = "", const Self = @This(); -pub fn create(a: Allocator, parent: Plane) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { return Button.create_widget(Self, a, parent, .{ .ctx = .{}, .label = "", @@ -26,6 +26,7 @@ pub fn create(a: Allocator, parent: Plane) !Widget { .on_layout = layout, .on_render = render, .on_receive = receive, + .on_event = event_handler, }); } diff --git a/src/tui/status/minilog.zig b/src/tui/status/minilog.zig index 03079c3..c657c33 100644 --- a/src/tui/status/minilog.zig +++ b/src/tui/status/minilog.zig @@ -10,12 +10,12 @@ const tui = @import("../tui.zig"); const mainview = @import("../mainview.zig"); const logview = @import("../logview.zig"); -parent: Plane, plane: Plane, msg: std.ArrayList(u8), msg_counter: usize = 0, clear_timer: ?tp.Cancellable = null, level: Level = .info, +on_event: ?Widget.EventHandler, const message_display_time_seconds = 2; const error_display_time_seconds = 4; @@ -26,12 +26,12 @@ const Level = enum { err, }; -pub fn create(a: std.mem.Allocator, parent: Plane) !Widget { +pub fn create(a: std.mem.Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { const self: *Self = try a.create(Self); self.* = .{ - .parent = parent, .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), .msg = std.ArrayList(u8).init(a), + .on_event = event_handler, }; logview.init(a); try tui.current().message_filters.add(MessageFilter.bind(self, receive_log)); @@ -52,6 +52,15 @@ pub fn deinit(self: *Self, a: std.mem.Allocator) void { a.destroy(self); } +pub fn receive(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool { + var btn: u32 = 0; + if (try m.match(.{ "D", tp.any, tp.extract(&btn), tp.more })) { + if (self.on_event) |h| h.send(from, m) catch {}; + return true; + } + return false; +} + pub fn layout(self: *Self) Widget.Layout { return .{ .static = if (self.msg.items.len > 0) self.msg.items.len + 2 else 1 }; } @@ -92,7 +101,7 @@ fn process_log(self: *Self, m: tp.message) !void { const err_stop = "error.Stop"; if (std.mem.eql(u8, msg, err_stop)) return; - if (msg.len >= err_stop.len + 1 and std.mem.eql(u8, msg[0..err_stop.len + 1], err_stop ++ "\n")) + if (msg.len >= err_stop.len + 1 and std.mem.eql(u8, msg[0 .. err_stop.len + 1], err_stop ++ "\n")) return; try self.set(msg, .err); } else if (try m.match(.{ "log", tp.extract(&src), tp.more })) { diff --git a/src/tui/status/modestate.zig b/src/tui/status/modestate.zig index 031eccc..ffc3d75 100644 --- a/src/tui/status/modestate.zig +++ b/src/tui/status/modestate.zig @@ -14,13 +14,14 @@ const command = @import("../command.zig"); const ed = @import("../editor.zig"); const tui = @import("../tui.zig"); -pub fn create(a: Allocator, parent: Plane) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { return Button.create_widget(void, a, parent, .{ .ctx = {}, .label = tui.get_mode(), .on_click = on_click, .on_layout = layout, .on_render = render, + .on_event = event_handler, }); } diff --git a/src/tui/status/modstate.zig b/src/tui/status/modstate.zig index c8959dd..c34a799 100644 --- a/src/tui/status/modstate.zig +++ b/src/tui/status/modstate.zig @@ -25,19 +25,13 @@ pub const width = 5; pub fn create(a: Allocator, parent: Plane) !Widget { const self: *Self = try a.create(Self); - self.* = try init(parent); + self.* = .{ + .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), + }; try tui.current().input_listeners.add(EventHandler.bind(self, listen)); return self.widget(); } -fn init(parent: Plane) !Self { - var n = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent); - errdefer n.deinit(); - return .{ - .plane = n, - }; -} - pub fn widget(self: *Self) Widget { return Widget.to(self); } diff --git a/src/tui/status/selectionstate.zig b/src/tui/status/selectionstate.zig index d98d81c..0d46cbc 100644 --- a/src/tui/status/selectionstate.zig +++ b/src/tui/status/selectionstate.zig @@ -15,22 +15,17 @@ cursels: usize = 0, selection: ?ed.Selection = null, buf: [256]u8 = undefined, rendered: [:0]const u8 = "", +on_event: ?Widget.EventHandler, const Self = @This(); -pub fn create(a: Allocator, parent: Plane) !Widget { +pub fn create(a: Allocator, parent: Plane, event_handler: ?Widget.EventHandler) !Widget { const self: *Self = try a.create(Self); - self.* = try init(parent); - return Widget.to(self); -} - -fn init(parent: Plane) !Self { - var n = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent); - errdefer n.deinit(); - - return .{ - .plane = n, + self.* = .{ + .plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent), + .on_event = event_handler, }; + return Widget.to(self); } pub fn deinit(self: *Self, a: Allocator) void { @@ -81,7 +76,12 @@ fn format(self: *Self) void { self.buf[self.rendered.len] = 0; } -pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { +pub fn receive(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool { + var btn: u32 = 0; + if (try m.match(.{ "D", tp.any, tp.extract(&btn), tp.more })) { + if (self.on_event) |h| h.send(from, m) catch {}; + return true; + } if (try m.match(.{ "E", "match", tp.extract(&self.matches) })) self.format(); if (try m.match(.{ "E", "cursels", tp.extract(&self.cursels) })) diff --git a/src/tui/status/statusbar.zig b/src/tui/status/statusbar.zig index d19ea1c..0248e00 100644 --- a/src/tui/status/statusbar.zig +++ b/src/tui/status/statusbar.zig @@ -6,15 +6,28 @@ const tui = @import("../tui.zig"); const Self = @This(); -pub fn create(a: std.mem.Allocator, parent: Widget) !Widget { +pub fn create(a: std.mem.Allocator, parent: Widget, event_handler: ?Widget.EventHandler) !Widget { var w = try WidgetList.createH(a, parent, "statusbar", .{ .static = 1 }); - if (tui.current().config.modestate_show) try w.add(try @import("modestate.zig").create(a, w.plane)); - try w.add(try @import("filestate.zig").create(a, w.plane)); - try w.add(try @import("minilog.zig").create(a, w.plane)); - if (tui.current().config.selectionstate_show) try w.add(try @import("selectionstate.zig").create(a, w.plane)); - try w.add(try @import("diagstate.zig").create(a, w.plane)); - try w.add(try @import("linenumstate.zig").create(a, w.plane)); + w.after_render = render_grip; + w.ctx = w; + if (tui.current().config.modestate_show) try w.add(try @import("modestate.zig").create(a, w.plane, event_handler)); + try w.add(try @import("filestate.zig").create(a, w.plane, event_handler)); + try w.add(try @import("minilog.zig").create(a, w.plane, event_handler)); + if (tui.current().config.selectionstate_show) try w.add(try @import("selectionstate.zig").create(a, w.plane, event_handler)); + try w.add(try @import("diagstate.zig").create(a, w.plane, event_handler)); + try w.add(try @import("linenumstate.zig").create(a, w.plane, event_handler)); if (tui.current().config.modstate_show) try w.add(try @import("modstate.zig").create(a, w.plane)); if (tui.current().config.keystate_show) try w.add(try @import("keystate.zig").create(a, w.plane)); return w.widget(); } + +fn render_grip(ctx: ?*anyopaque, theme: *const Widget.Theme) void { + const w: *WidgetList = @ptrCast(@alignCast(ctx.?)); + if (w.hover()) { + w.plane.set_style(theme.statusbar_hover); + const width = w.plane.dim_x(); + const grip_pos = width / 2; + w.plane.cursor_move_yx(0, @intCast(grip_pos)) catch {}; + _ = w.plane.putstr("  ") catch {}; + } +} diff --git a/src/tui/tui.zig b/src/tui/tui.zig index c3770e6..701aa87 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -119,7 +119,6 @@ fn init(a: Allocator) !*Self { self.rdr.dispatch_mouse_drag = dispatch_mouse_drag; self.rdr.dispatch_event = dispatch_event; try self.rdr.run(); - const n = self.rdr.stdplane(); try frame_clock.start(); try self.commands.init(self); @@ -132,7 +131,7 @@ fn init(a: Allocator) !*Self { try self.listen_sigwinch(); }, } - self.mainview = try mainview.create(a, n); + self.mainview = try mainview.create(a); self.resize(); self.rdr.set_terminal_style(self.theme.editor); try self.rdr.render(); @@ -701,6 +700,10 @@ pub fn resize(self: *Self) void { need_render(); } +pub fn stdplane(self: *Self) renderer.Plane { + return self.rdr.stdplane(); +} + pub fn screen(self: *Self) Widget.Box { return Widget.Box.from(self.rdr.stdplane()); }