diff --git a/src/apprt/gtk/Split.zig b/src/apprt/gtk/Split.zig index a0589e88a..458d22765 100644 --- a/src/apprt/gtk/Split.zig +++ b/src/apprt/gtk/Split.zig @@ -12,7 +12,6 @@ const CoreSurface = @import("../../Surface.zig"); const Surface = @import("Surface.zig"); const Tab = @import("Tab.zig"); const Position = @import("relation.zig").Position; -const Parent = @import("relation.zig").Parent; const Child = @import("relation.zig").Child; const c = @import("c.zig"); @@ -25,27 +24,8 @@ paned: *c.GtkPaned, container: Surface.Container, /// The elements of this split panel. -top_left: Elem, -bottom_right: Elem, - -/// Elem is the possible element of the split. -pub const Elem = union(enum) { - /// A surface is a leaf element of the split -- a terminal surface. - surface: *Surface, - - /// A split is a nested split within a split. This lets you for example - /// have a horizontal split with a vertical split on the left side - /// (amongst all other possible combinations). - split: *Split, - - /// Returns the GTK widget to add to the paned for the given element - pub fn widget(self: Child) *c.GtkWidget { - return switch (self) { - .surface => |surface| @ptrCast(surface.gl_area), - .split => |split| @ptrCast(@alignCast(split.paned)), - }; - } -}; +top_left: Surface.Container.Elem, +bottom_right: Surface.Container.Elem, /// Create a new split panel with the given sibling surface in the given /// direction. The direction is where the new surface will be initialized. @@ -73,7 +53,6 @@ pub fn init( const alloc = sibling.app.core_app.alloc; var surface = try Surface.create(alloc, sibling.app, .{ .parent2 = &sibling.core_surface, - .parent = .{ .paned = .{ self, .end } }, }); errdefer surface.destroy(alloc); @@ -93,25 +72,21 @@ pub fn init( sibling.container = .{ .split_tl = &self.top_left }; surface.container = .{ .split_br = &self.bottom_right }; - // If the sibling is already in a split, then we need to - // nest them properly. This gets the pointer to the split element - // that the original split was in, then updates it to point to this - // split. This split then contains the surface as an element. - if (container.splitElem()) |parent_elem| { - parent_elem.* = .{ .split = self }; - } - self.* = .{ .paned = @ptrCast(paned), .container = container, .top_left = .{ .surface = sibling }, .bottom_right = .{ .surface = surface }, }; -} -/// Set the parent of Split. -pub fn setParent(self: *Split, parent: Parent) void { - self.parent = parent; + // Replace the previous containers element with our split. + // This allows a non-split to become a split, a split to + // become a nested split, etc. + container.replace(.{ .split = self }); + + // Update our children so that our GL area is properly + // added to the paned. + self.updateChildren(); } /// Focus on first Surface that can be found in given position. If there's a @@ -211,6 +186,23 @@ fn removeChildInPosition(self: *Split, position: Position) void { } } +// TODO: ehhhhhh +pub fn replace( + self: *Split, + ptr: *Surface.Container.Elem, + new: Surface.Container.Elem, +) void { + // We can write our element directly. There's nothing special. + assert(&self.top_left == ptr or &self.bottom_right == ptr); + ptr.* = new; + + // Update our paned children. This will reset the divider + // position but we want to keep it in place so save and restore it. + const pos = c.gtk_paned_get_position(self.paned); + self.updateChildren(); + c.gtk_paned_set_position(self.paned, pos); +} + /// Update the paned children to represent the current state. /// This should be called anytime the top/left or bottom/right /// element is changed. diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 5f79c001f..2891c0788 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -32,9 +32,6 @@ pub const Options = struct { /// The parent surface to inherit settings such as font size, working /// directory, etc. from. parent2: ?*CoreSurface = null, - - /// The parent this surface is created under. - parent: Parent, }; /// The container that this surface is directly attached to. @@ -49,8 +46,33 @@ pub const Container = union(enum) { /// A split within a split hierarchy. The key determines the /// position of the split within the parent split. - split_tl: *Split.Elem, - split_br: *Split.Elem, + split_tl: *Elem, + split_br: *Elem, + + /// Elem is the possible element of any container. A container can + /// hold both a surface and a split. Any valid container should + /// have an Elem value so that it can be properly used with + /// splits. + pub const Elem = union(enum) { + /// A surface is a leaf element of the split -- a terminal + /// surface. + surface: *Surface, + + /// A split is a nested split within a split. This lets you + /// for example have a horizontal split with a vertical split + /// on the left side (amongst all other possible + /// combinations). + split: *Split, + + /// Returns the GTK widget to add to the paned for the given + /// element + pub fn widget(self: Elem) *c.GtkWidget { + return switch (self) { + .surface => |s| @ptrCast(s.gl_area), + .split => |s| @ptrCast(@alignCast(s.paned)), + }; + } + }; /// Returns the window that this surface is attached to. pub fn window(self: Container) ?*Window { @@ -85,14 +107,19 @@ pub const Container = union(enum) { }; } - /// Returns the element of the split that this container - /// is attached to. - pub fn splitElem(self: Container) ?*Split.Elem { - return switch (self) { - .none, .tab_ => null, - .split_tl => |ptr| ptr, - .split_br => |ptr| ptr, - }; + /// Replace the container's element with this element. This is + /// used by children to modify their parents to for example change + /// from a surface to a split or a split back to a surface or + /// a split to a nested split and so on. + pub fn replace(self: Container, elem: Elem) void { + switch (self) { + .none => {}, + .tab_ => |t| t.replaceElem(elem), + inline .split_tl, .split_br => |ptr| { + const s = self.split().?; + s.replace(ptr, elem); + }, + } } }; @@ -111,9 +138,6 @@ container: Container = .{ .none = {} }, /// The app we're part of app: *App, -/// The parent we belong to -parent: Parent, - /// Our GTK area gl_area: *c.GtkGLArea, @@ -158,9 +182,20 @@ pub fn create(alloc: Allocator, app: *App, opts: Options) !*Surface { pub fn init(self: *Surface, app: *App, opts: Options) !void { const widget: *c.GtkWidget = c.gtk_gl_area_new(); const gl_area: *c.GtkGLArea = @ptrCast(widget); + + // We grab the floating reference to GL area. This lets the + // GL area be moved around i.e. between a split, a tab, etc. + // without having to be really careful about ordering to + // prevent a destroy. + // TODO: unref in deinit + _ = c.g_object_ref_sink(@ptrCast(gl_area)); + errdefer c.g_object_unref(@ptrCast(gl_area)); + + // We want the gl area to expand to fill the parent container. c.gtk_widget_set_hexpand(widget, 1); c.gtk_widget_set_vexpand(widget, 1); + // Various other GL properties c.gtk_widget_set_cursor_from_name(@ptrCast(gl_area), "text"); c.gtk_gl_area_set_required_version(gl_area, 3, 3); c.gtk_gl_area_set_has_stencil_buffer(gl_area, 0); @@ -226,7 +261,6 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void { self.* = .{ .app = app, .container = .{ .none = {} }, - .parent = opts.parent, .gl_area = gl_area, .title_text_buf = undefined, .title_text_buf_len = 0, @@ -450,19 +484,21 @@ pub fn getTitleLabel(self: *Surface) ?*c.GtkWidget { pub fn newSplit(self: *Surface, direction: input.SplitDirection) !void { log.debug("splitting direction={}", .{direction}); + const alloc = self.app.core_app.alloc; + _ = try Split.create(alloc, self, direction); - switch (self.parent) { - .none => return, - .paned => |parent_paned_tuple| { - const paned = parent_paned_tuple[0]; - const position = parent_paned_tuple[1]; - - try paned.splitSurfaceInPosition(position, direction); - }, - .tab => |tab| { - try tab.splitSurface(direction); - }, - } + // switch (self.parent) { + // .none => return, + // .paned => |parent_paned_tuple| { + // const paned = parent_paned_tuple[0]; + // const position = parent_paned_tuple[1]; + // + // try paned.splitSurfaceInPosition(position, direction); + // }, + // .tab => |tab| { + // try tab.splitSurface(direction); + // }, + // } } pub fn newTab(self: *Surface) !void { @@ -852,6 +888,7 @@ fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void { fn gtkUnrealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void { _ = area; + log.debug("gl surface unrealized", .{}); const self = userdataSelf(ud.?); self.core_surface.renderer.displayUnrealized(); @@ -973,7 +1010,8 @@ fn gtkMouseDown( // If we have siblings, we also update the title, since it means // another sibling might have updated the title. - if (self.parent != Parent.tab) self.updateTitleLabels(); + // TODO: fixme + //if (self.parent != Parent.tab) self.updateTitleLabels(); } self.core_surface.mouseButtonCallback(.press, button, mods) catch |err| { diff --git a/src/apprt/gtk/Tab.zig b/src/apprt/gtk/Tab.zig index 474871da4..9417d0052 100644 --- a/src/apprt/gtk/Tab.zig +++ b/src/apprt/gtk/Tab.zig @@ -22,12 +22,21 @@ const log = std.log.scoped(.gtk); pub const GHOSTTY_TAB = "ghostty_tab"; window: *Window, + +/// The tab label. label_text: *c.GtkLabel, -// We'll put our children into this box instead of packing them directly, so -// that we can send the box into `c.g_signal_connect_data` for the close button + +/// We'll put our children into this box instead of packing them +/// directly, so that we can send the box into `c.g_signal_connect_data` +/// for the close button box: *c.GtkBox, + +/// The element of this tab so that we can handle splits and so on. +elem: Surface.Container.Elem, + // The child can be either a Surface if the tab is not split or a Paned child: Child, + // We'll update this every time a Surface gains focus, so that we have it // when we switch to another Tab. Then when we switch back to this tab, we // can easily re-focus that terminal. @@ -47,6 +56,7 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void { .window = window, .label_text = undefined, .box = undefined, + .elem = undefined, .child = undefined, .focus_child = undefined, }; @@ -92,11 +102,11 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void { // Create the initial surface since all tabs start as a single non-split var surface = try Surface.create(window.app.core_app.alloc, window.app, .{ .parent2 = parent_, - .parent = .{ .tab = self }, }); errdefer surface.destroy(window.app.core_app.alloc); surface.setContainer(.{ .tab_ = self }); self.child = .{ .surface = surface }; + self.elem = .{ .surface = surface }; // Add Surface to the Tab const gl_area_widget = @as(*c.GtkWidget, @ptrCast(surface.gl_area)); @@ -178,6 +188,17 @@ pub fn removeChild(self: *Tab) void { self.child = .none; } +// TODO: move this +/// Replace the surface element that this tab is showing. +pub fn replaceElem(self: *Tab, elem: Surface.Container.Elem) void { + // Remove our previous widget + c.gtk_box_remove(self.box, self.elem.widget()); + + // Add our new one + c.gtk_box_append(self.box, elem.widget()); + self.elem = elem; +} + /// Sets child to given child and sets parent on child. pub fn setChild(self: *Tab, child: Child) void { const widget = child.widget() orelse return; diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 184628f03..d12439f4a 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -260,16 +260,17 @@ pub fn closeTab(self: *Window, tab: *Tab) void { pub fn closeSurface(self: *Window, surface: *Surface) void { assert(surface.container.window().? == self); - switch (surface.parent) { - .none => unreachable, - .tab => |tab| self.closeTab(tab), - .paned => |paned_tuple| { - const paned = paned_tuple[0]; - const position = paned_tuple[1]; - - self.closeSurfaceInPaned(surface, paned, position); - }, - } + // TODO: fixme + // switch (surface.parent) { + // .none => unreachable, + // .tab => |tab| self.closeTab(tab), + // .paned => |paned_tuple| { + // const paned = paned_tuple[0]; + // const position = paned_tuple[1]; + // + // self.closeSurfaceInPaned(surface, paned, position); + // }, + // } } fn closeSurfaceInPaned(self: *Window, surface: *Surface, paned: *Paned, position: Position) void {