Compare commits

..

14 commits

Author SHA1 Message Date
48c42737a6
fix: don't skip tab updates while dragging 2026-01-15 22:07:34 +01:00
e9a67d4c71
refactor: make Widget.hover const 2026-01-15 20:45:37 +01:00
a4b80377c1
fix: tabbar initial render 2026-01-15 20:30:13 +01:00
a632305a6f
refactor: tab rendering 2026-01-15 20:12:17 +01:00
8c6091c419
fix: only update tabbar widgets if buffers have changed 2026-01-15 19:53:40 +01:00
ff38c37df7
refactor: make tab bar split aware 2026-01-15 17:56:08 +01:00
120a9f0bf5
fix: don't clear_hover_focus in refresh_hover
This causes an endless render loop because the hover status
ping pongs. This was probably originally added to avoid dangling
widget pointers. We handle that correctly now elsewhere with
is_live_widget_ptr.
2026-01-15 17:53:26 +01:00
8cfab31104
refactor: add tracing for update_hover, clear_hover_focus and refresh_hover 2026-01-15 17:53:12 +01:00
9d016527f2
refactor: add tracing of frame_clock_running 2026-01-15 17:51:39 +01:00
ef60a95d55
refactor: add widget call stack tracing for render continuations 2026-01-15 17:51:38 +01:00
c3cf5ea02f
refactor: add source location tracing for need_render calls 2026-01-15 17:51:38 +01:00
58bd1fe12a
refactor: add tui.get_drag_source 2026-01-15 17:51:38 +01:00
760d498f53
refactor: allow updating full drag context 2026-01-15 15:45:55 +01:00
998ee051ba
refactor: simplify tui.drag_button type 2026-01-15 15:45:32 +01:00
14 changed files with 189 additions and 112 deletions

View file

@ -123,7 +123,7 @@ fn State(ctx_type: type) type {
input.mouse.BUTTON1 => {
self.active = true;
self.drag_anchor = self.to_rel_cursor(x, y);
tui.need_render();
tui.need_render(@src());
},
input.mouse.BUTTON4, input.mouse.BUTTON5 => {
self.call_click_handler(btn_enum, self.to_rel_cursor(x, y));
@ -136,7 +136,7 @@ fn State(ctx_type: type) type {
self.drag_anchor = null;
self.drag_pos = null;
self.call_click_handler(@enumFromInt(btn), self.to_rel_cursor(x, y));
tui.need_render();
tui.need_render(@src());
return true;
} else if (try m.match(.{ "D", input.event.press, tp.extract(&btn), tp.any, tp.extract(&x), tp.extract(&y), tp.any, tp.any })) {
self.drag_pos = .{ .x = x, .y = y };
@ -153,11 +153,11 @@ fn State(ctx_type: type) type {
self.drag_anchor = null;
self.drag_pos = null;
self.call_click_handler(@enumFromInt(btn), self.to_rel_cursor(x, y));
tui.need_render();
tui.need_render(@src());
return true;
} else if (try m.match(.{ "H", tp.extract(&self.hover) })) {
tui.rdr().request_mouse_cursor_pointer(self.hover);
tui.need_render();
tui.need_render(@src());
return true;
}
self.drag_anchor = null;

View file

@ -132,21 +132,21 @@ pub fn State(ctx_type: type) type {
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "B", input.event.press, @intFromEnum(input.mouse.BUTTON1), tp.any, tp.any, tp.any, tp.any, tp.any })) {
self.active = true;
tui.need_render();
tui.need_render(@src());
return true;
} else if (try m.match(.{ "B", input.event.release, @intFromEnum(input.mouse.BUTTON1), tp.any, tp.any, tp.any, tp.any, tp.any })) {
self.opts.on_click(self.opts.ctx, self);
self.active = false;
tui.need_render();
tui.need_render(@src());
return true;
} else if (try m.match(.{ "D", input.event.release, @intFromEnum(input.mouse.BUTTON1), tp.any, tp.any, tp.any, tp.any, tp.any })) {
self.opts.on_click(self.opts.ctx, self);
self.active = false;
tui.need_render();
tui.need_render(@src());
return true;
} else if (try m.match(.{ "H", tp.extract(&self.hover) })) {
tui.rdr().request_mouse_cursor_pointer(self.hover);
tui.need_render();
tui.need_render(@src());
return true;
}
return false;

View file

@ -55,7 +55,7 @@ pub const VTable = struct {
walk: *const fn (ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn, self_widget: *Self) bool,
focus: *const fn (ctx: *anyopaque) void,
unfocus: *const fn (ctx: *anyopaque) void,
hover: *const fn (ctx: *anyopaque) bool,
hover: *const fn (ctx: *const anyopaque) bool,
type_name: []const u8,
};
@ -154,8 +154,8 @@ pub fn to(pimpl: anytype) Self {
}
}.unfocus,
.hover = struct {
pub fn hover(ctx: *anyopaque) bool {
return if (comptime @hasField(child, "hover")) @as(*child, @ptrCast(@alignCast(ctx))).hover else false;
pub fn hover(ctx: *const anyopaque) bool {
return if (comptime @hasField(child, "hover")) @as(*const child, @ptrCast(@alignCast(ctx))).hover else false;
}
}.hover,
},
@ -170,7 +170,7 @@ pub fn dynamic_cast(self: Self, comptime T: type) ?*T {
}
pub fn need_render() void {
tui.need_render();
tui.need_render(@src());
}
pub fn need_reflow() void {
@ -203,7 +203,10 @@ pub fn update(self: Self) void {
}
pub fn render(self: Self, theme: *const Theme) bool {
return self.vtable.render(self.ptr, theme);
const more = self.vtable.render(self.ptr, theme);
if (more)
tp.trace(tp.channel.widget, .{ "continue_by", self.vtable.type_name });
return more;
}
pub fn resize(self: Self, pos: Box) void {
@ -242,7 +245,7 @@ pub fn unfocus(self: *Self) void {
self.vtable.unfocus(self.ptr);
}
pub fn hover(self: *Self) bool {
pub fn hover(self: *const Self) bool {
return self.vtable.hover(self.ptr);
}
@ -313,7 +316,7 @@ pub fn empty(allocator: Allocator, parent: Plane, layout_: Layout) !Self {
pub fn unfocus(_: *anyopaque) void {}
}.unfocus,
.hover = struct {
pub fn hover(_: *anyopaque) bool {
pub fn hover(_: *const anyopaque) bool {
return false;
}
}.hover,

View file

@ -6934,7 +6934,7 @@ pub const EditorWidget = struct {
return;
} else {
self.editor.cancel_all_matches();
tui.need_render();
tui.need_render(@src());
}
},
.none => {},

View file

@ -209,7 +209,7 @@ pub fn receive(_: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
var hover: bool = false;
if (try m.match(.{ "H", tp.extract(&hover) })) {
tui.rdr().request_mouse_cursor_default(hover);
tui.need_render();
tui.need_render(@src());
return true;
}
if (try m.match(.{ "B", input.event.press, @intFromEnum(input.mouse.BUTTON1), tp.more }) or
@ -453,7 +453,7 @@ const cmds = struct {
const padding = tui.get_widget_style(widget_type).padding;
self.menu_len = self.menu_count + padding.top + padding.bottom;
self.menu_w = self.menu_label_max + 2 + padding.left + padding.right;
tui.need_render();
tui.need_render(@src());
try tui.save_config();
}
pub const home_next_widget_style_meta: Meta = .{};

View file

@ -605,7 +605,7 @@ const cmds = struct {
if (view == null)
try command.executeName("scroll_view_center", .{});
}
tui.need_render();
tui.need_render(@src());
self.location_update_from_editor();
}
@ -613,7 +613,7 @@ const cmds = struct {
tui.reset_drag_context();
try self.create_editor();
try command.executeName("open_scratch_buffer", command.fmt(.{ "help", @embedFile("help.md"), "markdown" }));
tui.need_render();
tui.need_render(@src());
self.location_update_from_editor();
}
pub const open_help_meta: Meta = .{ .description = "Open help" };
@ -622,7 +622,7 @@ const cmds = struct {
tui.reset_drag_context();
try self.create_editor();
try command.executeName("open_scratch_buffer", command.fmt(.{ "font test", @import("fonts.zig").font_test_text, "text" }));
tui.need_render();
tui.need_render(@src());
self.location_update_from_editor();
}
pub const open_font_test_text_meta: Meta = .{ .description = "Open font glyph test text" };
@ -631,7 +631,7 @@ const cmds = struct {
tui.reset_drag_context();
try self.create_editor();
try command.executeName("open_scratch_buffer", command.fmt(.{ "version", root.version_info, "gitcommit" }));
tui.need_render();
tui.need_render(@src());
self.location_update_from_editor();
}
pub const open_version_info_meta: Meta = .{ .description = "Version" };
@ -726,7 +726,7 @@ const cmds = struct {
tui.reset_drag_context();
try self.create_editor();
try command.executeName("open_scratch_buffer", .{ .args = args });
tui.need_render();
tui.need_render(@src());
self.location_update_from_editor();
}
pub const create_scratch_buffer_meta: Meta = .{ .arguments = &.{ .string, .string, .string } };
@ -807,7 +807,7 @@ const cmds = struct {
new_buffer.mark_dirty();
new_editor.clamp();
new_editor.update_buf(new_buffer.root) catch {};
tui.need_render();
tui.need_render(@src());
}
try command.executeName("save_file", .{});
try command.executeName("place_next_tab", command.fmt(.{
@ -836,7 +836,7 @@ const cmds = struct {
const logger = log.logger("buffer");
defer logger.deinit();
logger.print("deleted buffer {s}", .{file_path});
tui.need_render();
tui.need_render(@src());
}
pub const delete_buffer_meta: Meta = .{ .arguments = &.{.string} };
@ -852,13 +852,13 @@ const cmds = struct {
return;
}
_ = self.buffer_manager.close_buffer(buffer);
tui.need_render();
tui.need_render(@src());
}
pub const close_buffer_meta: Meta = .{ .arguments = &.{.string} };
pub fn restore_session(self: *Self, _: Ctx) Result {
try self.read_restore_info();
tui.need_render();
tui.need_render(@src());
}
pub const restore_session_meta: Meta = .{};
@ -1117,7 +1117,7 @@ const cmds = struct {
if (self.get_active_editor()) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse "")) {
self.symbols_complete = true;
try tui.open_overlay(@import("mode/overlay/symbol_palette.zig").Type);
tui.need_render();
tui.need_render(@src());
};
}
pub const add_document_symbol_done_meta: Meta = .{
@ -1181,7 +1181,7 @@ const cmds = struct {
.palette => try tui.open_overlay(@import("mode/overlay/completion_palette.zig").Type),
.dropdown => try tui.open_overlay(@import("mode/overlay/completion_dropdown.zig").Type),
}
tui.need_render();
tui.need_render(@src());
}
};
}
@ -1383,7 +1383,7 @@ const cmds = struct {
const buffer = self.buffer_manager.buffer_from_ref(buffer_ref) orelse return;
if (self.get_editor_for_buffer(buffer)) |editor| if (editor.buffer) |eb| if (eb == buffer) {
editor.smart_buffer_append(command.fmt(.{output})) catch {};
tui.need_render();
tui.need_render(@src());
return;
};
var cursor: Buffer.Cursor = .{};
@ -1393,7 +1393,7 @@ const cmds = struct {
_, _, root_ = try root_.insert_chars(cursor.row, cursor.col, output, self.allocator, metrics);
buffer.store_undo(&[_]u8{}) catch {};
buffer.update(root_);
tui.need_render();
tui.need_render(@src());
}
pub const shell_execute_stream_output_meta: Meta = .{ .arguments = &.{ .integer, .string } };
@ -1407,7 +1407,7 @@ const cmds = struct {
return;
}
buffer.mark_clean();
tui.need_render();
tui.need_render(@src());
}
pub const shell_execute_stream_output_complete_meta: Meta = .{ .arguments = &.{ .integer, .string } };
@ -2074,12 +2074,12 @@ pub fn set_info_content(self: *Self, content: []const u8, mode: enum { replace,
.replace => info.set_content(content) catch |e| return tp.exit_error(e, @errorReturnTrace()),
.append => info.append_content(content) catch |e| return tp.exit_error(e, @errorReturnTrace()),
}
tui.need_render();
tui.need_render(@src());
}
pub fn cancel_info_content(self: *Self) tp.result {
_ = self.toggle_panel_view(info_view, .disable) catch |e| return tp.exit_error(e, @errorReturnTrace());
tui.need_render();
tui.need_render(@src());
}
pub fn vcs_id_update(self: *Self, m: tp.message) void {

View file

@ -179,7 +179,7 @@ pub fn Create(options: type) type {
} else {
log.logger("file_browser").err("receive", tp.unexpected(m));
}
tui.need_render();
tui.need_render(@src());
}
fn add_entry(self: *Self, file_name: []const u8, entry_type: EntryType, file_type: []const u8, icon: []const u8, color: u24) !void {

View file

@ -298,7 +298,7 @@ pub fn Create(options: type) type {
self.menu.select_down();
const padding = tui.get_widget_style(widget_type).padding;
self.do_resize(padding);
tui.refresh_hover();
tui.refresh_hover(@src());
self.selection_updated();
}
}
@ -580,7 +580,7 @@ pub fn Create(options: type) type {
tui.set_next_style(widget_type);
const padding = tui.get_widget_style(widget_type).padding;
self.do_resize(padding);
tui.need_render();
tui.need_render(@src());
try tui.save_config();
}
pub const overlay_next_widget_style_meta: Meta = .{};

View file

@ -273,7 +273,7 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void
self.menu.select_down();
self.need_select_first = false;
}
tui.need_render();
tui.need_render(@src());
} else if (try cbor.match(m.buf, .{
"PRJ",
"recent",
@ -291,7 +291,7 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void
self.menu.select_down();
self.need_select_first = false;
}
tui.need_render();
tui.need_render(@src());
} else if (try cbor.match(m.buf, .{ "PRJ", "recent_done", tp.extract(&self.longest), tp.extract(&query), tp.extract(&self.total_files_in_project) })) {
self.update_count_hint();
self.query_pending = false;
@ -492,7 +492,7 @@ const cmds = struct {
pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result {
tui.set_next_style(widget_type);
self.do_resize();
tui.need_render();
tui.need_render(@src());
try tui.save_config();
}
pub const overlay_next_widget_style_meta: Meta = .{};

View file

@ -349,7 +349,7 @@ pub fn Create(options: type) type {
self.menu.select_down();
const padding = tui.get_widget_style(widget_type).padding;
self.do_resize(padding);
tui.refresh_hover();
tui.refresh_hover(@src());
self.selection_updated();
}
}
@ -635,7 +635,7 @@ pub fn Create(options: type) type {
tui.set_next_style(widget_type);
const padding = tui.get_widget_style(widget_type).padding;
self.do_resize(padding);
tui.need_render();
tui.need_render(@src());
try tui.save_config();
}
pub const overlay_next_widget_style_meta: Meta = .{};

View file

@ -188,7 +188,7 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void
self.menu.select_down();
self.need_select_first = false;
}
tui.need_render();
tui.need_render(@src());
} else if (try cbor.match(m.buf, .{
"PRJ",
"new_or_modified_files",
@ -207,7 +207,7 @@ fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void
self.menu.select_down();
self.need_select_first = false;
}
tui.need_render();
tui.need_render(@src());
} else if (try cbor.match(m.buf, .{ "PRJ", "new_or_modified_files_done", tp.extract(&self.longest), tp.extract(&query) })) {
self.query_pending = false;
self.need_reset = true;
@ -369,7 +369,7 @@ const cmds = struct {
pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result {
tui.set_next_style(widget_type);
self.do_resize();
tui.need_render();
tui.need_render(@src());
try tui.save_config();
}
pub const overlay_next_widget_style_meta: Meta = .{};

View file

@ -95,7 +95,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
fn receive_tick(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool {
if (try cbor.match(m.buf, .{"CLOCK"})) {
tui.need_render();
tui.need_render(@src());
self.update_tick_timer(.ticked);
return true;
}

View file

@ -104,6 +104,7 @@ pub const TabBar = struct {
const TabBarTab = struct {
buffer_ref: usize,
widget: Widget,
view: ?usize,
};
fn init(allocator: std.mem.Allocator, parent: Plane, event_handler: ?EventHandler, min_tabs: ?usize) !Self {
@ -138,9 +139,14 @@ pub const TabBar = struct {
}
pub fn update(self: *Self) void {
self.update_tabs() catch {};
const drag_source, const drag_btn = tui.get_drag_source();
self.update_tabs(drag_source) catch {};
self.widget_list_widget.resize(Widget.Box.from(self.plane));
self.widget_list_widget.update();
for (self.widget_list.widgets.items) |*split_widgetstate| if (split_widgetstate.widget.dynamic_cast(WidgetList)) |split|
for (split.widgets.items) |*widgetstate| if (widgetstate.widget.dynamic_cast(Tab.ButtonType)) |btn| if (btn.drag_pos) |_|
tui.update_drag_source(&widgetstate.widget, drag_btn);
tui.refresh_hover(@src());
}
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
@ -153,7 +159,8 @@ pub const TabBar = struct {
});
self.plane.fill(" ");
self.plane.home();
return self.widget_list_widget.render(theme);
for (self.tabs) |*tab| _ = tab.widget.render(theme);
return false;
}
pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
@ -200,14 +207,9 @@ pub const TabBar = struct {
if (btn.hover) break idx;
} else return;
if (dragging != hover_) {
const tmp = self.tabs[dragging];
self.tabs[dragging] = self.tabs[hover_];
self.tabs[hover_] = tmp;
self.swap_tabs_by_index(dragging, hover_);
if (self.tabs[dragging].widget.dynamic_cast(Tab.ButtonType)) |btn| btn.hover = false;
self.update();
for (self.widget_list.widgets.items) |*widgetstate| if (widgetstate.widget.dynamic_cast(Tab.ButtonType)) |btn| if (btn.drag_pos) |_|
tui.update_drag_source(&widgetstate.widget);
tui.refresh_hover();
}
}
}
@ -229,30 +231,61 @@ pub const TabBar = struct {
return self.widget_list_widget.hover();
}
fn update_tabs(self: *Self) !void {
fn update_tabs(self: *Self, drag_source: ?*Widget) !void {
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
try self.update_tab_buffers();
const prev_widget_count = self.widget_list.widgets.items.len;
while (self.widget_list.pop()) |widget| if (widget.dynamic_cast(Tab.ButtonType) == null)
widget.deinit(self.widget_list.allocator);
var first = true;
for (self.tabs) |tab| {
if (first) {
first = false;
} else {
try self.widget_list.add(try self.make_spacer());
}
try self.widget_list.add(tab.widget);
if (tab.widget.dynamic_cast(Tab.ButtonType)) |btn| {
if (buffer_manager.buffer_from_ref(tab.buffer_ref)) |buffer|
try btn.update_label(Tab.name_from_buffer(buffer));
const buffers_changed = try self.update_tab_buffers();
const dragging = for (self.tabs) |*tab| {
if (tab.widget.dynamic_cast(Tab.ButtonType)) |btn|
if (btn.drag_pos) |_| break true;
} else false;
if (!dragging and !buffers_changed and self.widget_list.widgets.items.len > 0) return;
var prev_widget_count: usize = 0;
for (self.widget_list.widgets.items) |*split_widgetstate| if (split_widgetstate.widget.dynamic_cast(WidgetList)) |split| {
prev_widget_count += 1;
for (split.widgets.items) |_| prev_widget_count += 1;
};
for (self.widget_list.widgets.items) |*split_widget| if (split_widget.widget.dynamic_cast(WidgetList)) |split| {
for (split.widgets.items) |*widget|
if (&widget.widget == drag_source) tui.reset_drag_context();
};
while (self.widget_list.pop()) |split_widget| if (split_widget.dynamic_cast(WidgetList)) |split| {
while (split.pop()) |widget| if (widget.dynamic_cast(Tab.ButtonType) == null)
widget.deinit(self.widget_list.allocator);
split.deinit(self.widget_list.allocator);
};
var max_view: usize = 0;
for (self.tabs) |tab| max_view = @max(max_view, tab.view orelse 0);
var widget_count: usize = 0;
for (0..max_view + 1) |view| {
var first = true;
var view_widget_list = try WidgetList.createH(self.allocator, self.widget_list.plane, "split", .dynamic);
try self.widget_list.add(view_widget_list.widget());
widget_count += 1;
for (self.tabs) |tab| {
const tab_view = tab.view orelse 0;
if (tab_view != view) continue;
if (first) {
first = false;
} else {
try view_widget_list.add(try self.make_spacer(view_widget_list.plane));
widget_count += 1;
}
try view_widget_list.add(tab.widget);
widget_count += 1;
if (tab.widget.dynamic_cast(Tab.ButtonType)) |btn| {
if (buffer_manager.buffer_from_ref(tab.buffer_ref)) |buffer|
try btn.update_label(Tab.name_from_buffer(buffer));
}
}
}
if (prev_widget_count != self.widget_list.widgets.items.len)
tui.refresh_hover();
if (prev_widget_count != widget_count)
tui.refresh_hover(@src());
}
fn update_tab_buffers(self: *Self) !void {
fn update_tab_buffers(self: *Self) !bool {
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
const buffers = try buffer_manager.list_unordered(self.allocator);
defer self.allocator.free(buffers);
@ -278,6 +311,16 @@ pub const TabBar = struct {
}
self.tabs = try result.toOwnedSlice(self.allocator);
if (existing_tabs.len != self.tabs.len)
return true;
for (existing_tabs, self.tabs) |tab_a, tab_b| {
if (tab_a.buffer_ref == tab_b.buffer_ref and
tab_a.view == tab_b.view)
continue;
return true;
}
return false;
}
fn place_new_tab(self: *Self, result: *std.ArrayListUnmanaged(TabBarTab), buffer: *Buffer) !void {
@ -295,14 +338,14 @@ pub const TabBar = struct {
else
try result.addOne(self.allocator),
};
pos.* = .{ .buffer_ref = buffer_ref, .widget = tab };
pos.* = .{ .buffer_ref = buffer_ref, .widget = tab, .view = buffer.get_last_view() };
self.place_next = .atend;
}
fn make_spacer(self: @This()) !Widget {
fn make_spacer(self: @This(), parent: Plane) !Widget {
return spacer.create(
self.allocator,
self.widget_list.plane,
parent,
self.tab_style.spacer,
self.tab_style.spacer_fg,
self.tab_style.spacer_bg,
@ -371,9 +414,23 @@ pub const TabBar = struct {
tp.trace(tp.channel.debug, .{ "swap_tabs", "not_found", "buffer_ref_b" });
return;
};
self.swap_tabs_by_index(tab_a_idx, tab_b_idx);
}
fn swap_tabs_by_index(self: *Self, tab_a_idx: usize, tab_b_idx: usize) void {
const tmp = self.tabs[tab_a_idx];
self.tabs[tab_a_idx] = self.tabs[tab_b_idx];
self.tabs[tab_b_idx] = tmp;
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
if (buffer_manager.buffer_from_ref(self.tabs[tab_a_idx].buffer_ref)) |buffer_a|
if (buffer_manager.buffer_from_ref(self.tabs[tab_b_idx].buffer_ref)) |buffer_b| {
const view_a = buffer_a.get_last_view();
const view_b = buffer_b.get_last_view();
if (view_a != view_b) {
buffer_a.set_last_view(view_b);
buffer_b.set_last_view(view_a);
}
};
tp.trace(tp.channel.debug, .{ "swap_tabs", "swapped", "indexes", tab_a_idx, tab_b_idx });
}
@ -417,21 +474,27 @@ pub const TabBar = struct {
while (count > 0) : (count -= 1) {
var buffer_name: ?[]const u8 = undefined;
if (!(cbor.matchValue(&iter2, cbor.extract(&buffer_name)) catch false)) return error.MatchTabBufferNameFailed;
if (buffer_name) |name| if (name_to_ref(name)) |buffer_ref| {
(try result.addOne(self.allocator)).* = .{
.buffer_ref = buffer_ref,
.widget = try Tab.create(self, buffer_ref, &self.tab_style),
};
};
if (buffer_name) |name| {
const buffer_ref_, const buffer_view = name_to_ref_and_view(name);
if (buffer_ref_) |buffer_ref|
(try result.addOne(self.allocator)).* = .{
.buffer_ref = buffer_ref,
.widget = try Tab.create(self, buffer_ref, &self.tab_style),
.view = buffer_view,
};
}
}
self.tabs = try result.toOwnedSlice(self.allocator);
iter.* = iter2;
}
fn name_to_ref(buffer_name: []const u8) ?usize {
fn name_to_ref_and_view(buffer_name: []const u8) struct { ?usize, ?usize } {
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
return if (buffer_manager.get_buffer_for_file(buffer_name)) |buffer| buffer_manager.buffer_to_ref(buffer) else null;
return if (buffer_manager.get_buffer_for_file(buffer_name)) |buffer|
.{ buffer_manager.buffer_to_ref(buffer), buffer.get_last_view() }
else
.{ null, null };
}
};

View file

@ -58,7 +58,7 @@ last_hover_y: c_int = -1,
commands: Commands = undefined,
logger: log.Logger,
drag_source: ?*Widget = null,
drag_button: ?input.MouseType = null,
drag_button: input.MouseType = 0,
dark_theme: Widget.Theme,
dark_parsed_theme: ?std.json.Parsed(Widget.Theme),
light_theme: Widget.Theme,
@ -407,7 +407,7 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
};
try self.dispatch_flush_input_event();
if (self.unrendered_input_events_count > 0 and !self.frame_clock_running)
need_render();
need_render(@src());
return;
}
@ -556,7 +556,7 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
if (try m.match(.{"MOUSE_IDLE"})) {
if (self.mouse_idle_timer) |*t| t.deinit();
self.mouse_idle_timer = null;
try self.clear_hover_focus();
try self.clear_hover_focus(@src());
return;
}
@ -668,11 +668,13 @@ fn render(self: *Self) void {
if (!self.frame_clock_running) {
self.frame_clock.start() catch {};
self.frame_clock_running = true;
tp.trace(tp.channel.widget, .{ "frame_clock_running", "started", more });
}
} else {
if (self.frame_clock_running) {
self.frame_clock.stop() catch {};
self.frame_clock_running = false;
tp.trace(tp.channel.widget, .{ "frame_clock_running", "stopped", more });
}
}
}
@ -849,32 +851,34 @@ fn update_hover(self: *Self, y: c_int, x: c_int) !?*Widget {
self.last_hover_x = x;
if (y >= 0 and x >= 0) if (self.find_coord_widget(@intCast(y), @intCast(x))) |w| {
if (if (self.hover_focus) |h| h != w else true) {
var buf: [256]u8 = undefined;
if (self.hover_focus) |h| {
if (self.is_live_widget_ptr(h))
_ = try h.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{ "H", false }) catch |e| return tp.exit_error(e, @errorReturnTrace()));
}
tp.trace(tp.channel.debug, .{ "update_hover", if (self.hover_focus) |h| @intFromPtr(h) else 0, @intFromPtr(w) });
if (self.hover_focus) |h| if (self.is_live_widget_ptr(h))
try send_hover_msg(h, false);
self.hover_focus = w;
_ = try w.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{ "H", true }) catch |e| return tp.exit_error(e, @errorReturnTrace()));
try send_hover_msg(w, true);
}
return w;
};
try self.clear_hover_focus();
try self.clear_hover_focus(@src());
return null;
}
fn clear_hover_focus(self: *Self) tp.result {
if (self.hover_focus) |h| {
var buf: [256]u8 = undefined;
if (self.is_live_widget_ptr(h))
_ = try h.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{ "H", false }) catch |e| return tp.exit_error(e, @errorReturnTrace()));
}
fn clear_hover_focus(self: *Self, src: std.builtin.SourceLocation) tp.result {
if (self.hover_focus) |h| if (self.is_live_widget_ptr(h))
try send_hover_msg(h, false);
tp.trace(tp.channel.debug, .{ "tui", "clear_hover_focus", if (self.hover_focus) |h| @intFromPtr(h) else 0, src.fn_name, src.file, src.line });
self.hover_focus = null;
}
pub fn refresh_hover() void {
fn send_hover_msg(widget: *const Widget, hover: bool) tp.result {
var buf: [256]u8 = undefined;
tp.trace(tp.channel.debug, .{ "hover_msg", @intFromPtr(widget), hover });
_ = try widget.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{ "H", hover }) catch |e| return tp.exit_error(e, @errorReturnTrace()));
}
pub fn refresh_hover(src: std.builtin.SourceLocation) void {
const self = current();
self.clear_hover_focus() catch return;
tp.trace(tp.channel.debug, .{ "tui", "refresh_hover", if (self.hover_focus) |h| @intFromPtr(h) else 0, src.fn_name, src.file, src.line });
_ = self.update_hover(self.last_hover_y, self.last_hover_x) catch {};
}
@ -898,7 +902,7 @@ fn enter_overlay_mode(self: *Self, mode: type) command.Result {
self.input_mode_outer_ = self.input_mode_;
self.input_mode_ = new_mode;
if (self.input_mode_) |*m| m.run_init();
refresh_hover();
refresh_hover(@src());
}
fn enter_overlay_mode_with_args(self: *Self, mode: type, ctx: command.Context) command.Result {
@ -910,7 +914,7 @@ fn enter_overlay_mode_with_args(self: *Self, mode: type, ctx: command.Context) c
self.input_mode_outer_ = self.input_mode_;
self.input_mode_ = try mode.create_with_args(self.allocator, ctx);
if (self.input_mode_) |*m| m.run_init();
refresh_hover();
refresh_hover(@src());
}
fn get_input_mode(self: *Self, mode_name: []const u8) !Mode {
@ -1386,7 +1390,7 @@ const cmds = struct {
if (self.input_mode_) |*mode| mode.deinit();
self.input_mode_ = self.input_mode_outer_;
self.input_mode_outer_ = null;
refresh_hover();
refresh_hover(@src());
}
pub const exit_overlay_mode_meta: Meta = .{};
@ -1613,21 +1617,21 @@ const cmds = struct {
pub fn panel_next_widget_style(_: *Self, _: Ctx) Result {
set_next_style(.panel);
need_render();
need_render(@src());
try save_config();
}
pub const panel_next_widget_style_meta: Meta = .{};
pub fn hint_window_next_widget_style(_: *Self, _: Ctx) Result {
set_next_style(.hint_window);
need_render();
need_render(@src());
try save_config();
}
pub const hint_window_next_widget_style_meta: Meta = .{};
pub fn dropdown_next_widget_style(_: *Self, _: Ctx) Result {
set_next_style(.dropdown);
need_render();
need_render(@src());
try save_config();
}
pub const dropdown_next_widget_style_meta: Meta = .{};
@ -1775,9 +1779,10 @@ pub fn get_keybind_mode() ?Mode {
return self.input_mode_ orelse self.delayed_init_input_mode;
}
pub fn update_drag_source(drag_source: *Widget) void {
pub fn update_drag_source(drag_source: *Widget, btn: input.MouseType) void {
const self = current();
self.drag_source = drag_source;
self.drag_button = btn;
}
fn set_drag_source(self: *Self, drag_source: ?*Widget, btn: input.MouseType) void {
@ -1785,6 +1790,11 @@ fn set_drag_source(self: *Self, drag_source: ?*Widget, btn: input.MouseType) voi
self.drag_button = btn;
}
pub fn get_drag_source() struct { ?*Widget, input.MouseType } {
const self = current();
return .{ self.drag_source, self.drag_button };
}
pub fn reset_drag_context() void {
const self = current();
self.drag_source = null;
@ -1797,9 +1807,10 @@ fn maybe_reset_drag_source(self: *Self, btn: input.MouseType) void {
self.drag_button = 0;
}
pub fn need_render() void {
pub fn need_render(src: std.builtin.SourceLocation) void {
const self = current();
if (!(self.render_pending or self.frame_clock_running)) {
tp.trace(tp.channel.debug, .{ "tui", "need_render", src.fn_name, src.file, src.line });
self.render_pending = true;
tp.self_pid().send(.{"render"}) catch {};
}
@ -1812,8 +1823,8 @@ pub fn frames_rendered() usize {
pub fn resize() void {
mainview_widget().resize(screen());
refresh_hover();
need_render();
refresh_hover(@src());
need_render(@src());
}
pub fn plane() renderer.Plane {