refactor: make tab bar split aware

This commit is contained in:
CJ van den Berg 2026-01-15 15:46:30 +01:00
parent 120a9f0bf5
commit ff38c37df7
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9

View file

@ -104,6 +104,7 @@ pub const TabBar = struct {
const TabBarTab = struct { const TabBarTab = struct {
buffer_ref: usize, buffer_ref: usize,
widget: Widget, widget: Widget,
view: ?usize,
}; };
fn init(allocator: std.mem.Allocator, parent: Plane, event_handler: ?EventHandler, min_tabs: ?usize) !Self { 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 { 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.resize(Widget.Box.from(self.plane));
self.widget_list_widget.update(); 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 { pub fn render(self: *Self, theme: *const Widget.Theme) bool {
@ -200,14 +206,9 @@ pub const TabBar = struct {
if (btn.hover) break idx; if (btn.hover) break idx;
} else return; } else return;
if (dragging != hover_) { if (dragging != hover_) {
const tmp = self.tabs[dragging]; self.swap_tabs_by_index(dragging, hover_);
self.tabs[dragging] = self.tabs[hover_];
self.tabs[hover_] = tmp;
if (self.tabs[dragging].widget.dynamic_cast(Tab.ButtonType)) |btn| btn.hover = false; if (self.tabs[dragging].widget.dynamic_cast(Tab.ButtonType)) |btn| btn.hover = false;
self.update(); 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, 0);
tui.refresh_hover(@src());
} }
} }
} }
@ -229,25 +230,51 @@ pub const TabBar = struct {
return self.widget_list_widget.hover(); 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"); const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
try self.update_tab_buffers(); try self.update_tab_buffers();
const prev_widget_count = self.widget_list.widgets.items.len; var prev_widget_count: usize = 0;
while (self.widget_list.pop()) |widget| if (widget.dynamic_cast(Tab.ButtonType) == null) 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); 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 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| { for (self.tabs) |tab| {
const tab_view = tab.view orelse 0;
if (tab_view != view) continue;
if (first) { if (first) {
first = false; first = false;
} else { } else {
try self.widget_list.add(try self.make_spacer()); try view_widget_list.add(try self.make_spacer(view_widget_list.plane));
widget_count += 1;
} }
try self.widget_list.add(tab.widget); try view_widget_list.add(tab.widget);
widget_count += 1;
if (tab.widget.dynamic_cast(Tab.ButtonType)) |btn| { if (tab.widget.dynamic_cast(Tab.ButtonType)) |btn| {
if (buffer_manager.buffer_from_ref(tab.buffer_ref)) |buffer| if (buffer_manager.buffer_from_ref(tab.buffer_ref)) |buffer|
try btn.update_label(Tab.name_from_buffer(buffer)); try btn.update_label(Tab.name_from_buffer(buffer));
} }
} }
}
if (prev_widget_count != self.widget_list.widgets.items.len) if (prev_widget_count != self.widget_list.widgets.items.len)
tui.refresh_hover(@src()); tui.refresh_hover(@src());
} }
@ -295,14 +322,14 @@ pub const TabBar = struct {
else else
try result.addOne(self.allocator), 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; self.place_next = .atend;
} }
fn make_spacer(self: @This()) !Widget { fn make_spacer(self: @This(), parent: Plane) !Widget {
return spacer.create( return spacer.create(
self.allocator, self.allocator,
self.widget_list.plane, parent,
self.tab_style.spacer, self.tab_style.spacer,
self.tab_style.spacer_fg, self.tab_style.spacer_fg,
self.tab_style.spacer_bg, self.tab_style.spacer_bg,
@ -371,9 +398,23 @@ pub const TabBar = struct {
tp.trace(tp.channel.debug, .{ "swap_tabs", "not_found", "buffer_ref_b" }); tp.trace(tp.channel.debug, .{ "swap_tabs", "not_found", "buffer_ref_b" });
return; 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]; const tmp = self.tabs[tab_a_idx];
self.tabs[tab_a_idx] = self.tabs[tab_b_idx]; self.tabs[tab_a_idx] = self.tabs[tab_b_idx];
self.tabs[tab_b_idx] = tmp; 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 }); tp.trace(tp.channel.debug, .{ "swap_tabs", "swapped", "indexes", tab_a_idx, tab_b_idx });
} }
@ -417,21 +458,27 @@ pub const TabBar = struct {
while (count > 0) : (count -= 1) { while (count > 0) : (count -= 1) {
var buffer_name: ?[]const u8 = undefined; var buffer_name: ?[]const u8 = undefined;
if (!(cbor.matchValue(&iter2, cbor.extract(&buffer_name)) catch false)) return error.MatchTabBufferNameFailed; if (!(cbor.matchValue(&iter2, cbor.extract(&buffer_name)) catch false)) return error.MatchTabBufferNameFailed;
if (buffer_name) |name| if (name_to_ref(name)) |buffer_ref| { 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)).* = .{ (try result.addOne(self.allocator)).* = .{
.buffer_ref = buffer_ref, .buffer_ref = buffer_ref,
.widget = try Tab.create(self, buffer_ref, &self.tab_style), .widget = try Tab.create(self, buffer_ref, &self.tab_style),
.view = buffer_view,
}; };
}; }
} }
self.tabs = try result.toOwnedSlice(self.allocator); self.tabs = try result.toOwnedSlice(self.allocator);
iter.* = iter2; 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"); 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 };
} }
}; };