diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index e6bf24bd1..fa73c2436 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -471,12 +471,12 @@ pub fn performAction( .mouse_shape => try self.setMouseShape(target, value), .mouse_over_link => self.setMouseOverLink(target, value), .toggle_tab_overview => self.toggleTabOverview(target), + .toggle_split_zoom => self.toggleSplitZoom(target), .toggle_window_decorations => self.toggleWindowDecorations(target), .quit_timer => self.quitTimer(value), // Unimplemented .close_all_windows, - .toggle_split_zoom, .toggle_quick_terminal, .toggle_visibility, .size_limit, @@ -671,6 +671,13 @@ fn toggleTabOverview(_: *App, target: apprt.Target) void { } } +fn toggleSplitZoom(_: *App, target: apprt.Target) void { + switch (target) { + .app => {}, + .surface => |surface| surface.rt_surface.toggleSplitZoom(), + } +} + fn toggleWindowDecorations( _: *App, target: apprt.Target, diff --git a/src/apprt/gtk/Split.zig b/src/apprt/gtk/Split.zig index 5afac6f5b..54fa30e1c 100644 --- a/src/apprt/gtk/Split.zig +++ b/src/apprt/gtk/Split.zig @@ -77,6 +77,7 @@ pub fn init( }); errdefer surface.destroy(alloc); sibling.dimSurface(); + sibling.setSplitZoom(false); // Create the actual GTKPaned, attach the proper children. const orientation: c_uint = switch (direction) { @@ -258,7 +259,7 @@ pub fn grabFocus(self: *Split) void { /// Update the paned children to represent the current state. /// This should be called anytime the top/left or bottom/right /// element is changed. -fn updateChildren(self: *const Split) void { +pub fn updateChildren(self: *const Split) void { // We have to set both to null. If we overwrite the pane with // the same value, then GTK bugs out (the GL area unrealizes // and never rerealizes). @@ -372,7 +373,15 @@ fn directionNext(self: *const Split, from: Side) ?struct { } } -fn removeChildren(self: *const Split) void { - c.gtk_paned_set_start_child(@ptrCast(self.paned), null); - c.gtk_paned_set_end_child(@ptrCast(self.paned), null); +pub fn detachTopLeft(self: *const Split) void { + c.gtk_paned_set_start_child(self.paned, null); +} + +pub fn detachBottomRight(self: *const Split) void { + c.gtk_paned_set_end_child(self.paned, null); +} + +fn removeChildren(self: *const Split) void { + self.detachTopLeft(); + self.detachBottomRight(); } diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 9c7a394f4..8172b7490 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -330,6 +330,9 @@ url_widget: ?URLWidget = null, /// The overlay that shows resizing information. resize_overlay: ResizeOverlay = .{}, +/// Whether or not the current surface is zoomed in (see `toggle_split_zoom`). +zoomed_in: bool = false, + /// If non-null this is the widget on the overlay which dims the surface when it is unfocused unfocused_widget: ?*c.GtkWidget = null, @@ -643,6 +646,8 @@ pub fn redraw(self: *Surface) void { /// Close this surface. pub fn close(self: *Surface, processActive: bool) void { + self.setSplitZoom(false); + // If we're not part of a window hierarchy, we never confirm // so we can just directly remove ourselves and exit. const window = self.container.window() orelse { @@ -791,7 +796,16 @@ pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void } pub fn grabFocus(self: *Surface) void { - if (self.container.tab()) |tab| tab.focus_child = self; + if (self.container.tab()) |tab| { + // If any other surface was focused and zoomed in, set it to non zoomed in + // so that self can grab focus. + if (tab.focus_child) |focus_child| { + if (focus_child.zoomed_in and focus_child != self) { + focus_child.setSplitZoom(false); + } + } + tab.focus_child = self; + } const widget = @as(*c.GtkWidget, @ptrCast(self.gl_area)); _ = c.gtk_widget_grab_focus(widget); @@ -801,7 +815,7 @@ pub fn grabFocus(self: *Surface) void { fn updateTitleLabels(self: *Surface) void { // If we have no title, then we have nothing to update. - const title = self.title_text orelse return; + const title = self.getTitle() orelse return; // If we have a tab and are the focused child, then we have to update the tab if (self.container.tab()) |tab| { @@ -822,9 +836,19 @@ fn updateTitleLabels(self: *Surface) void { } } +const zoom_title_prefix = "🔍 "; + pub fn setTitle(self: *Surface, slice: [:0]const u8) !void { const alloc = self.app.core_app.alloc; - const copy = try alloc.dupeZ(u8, slice); + + // Always allocate with the "🔍 " at the beginning and slice accordingly + // is the surface is zoomed in or not. + const copy: [:0]const u8 = copy: { + const new_title = try alloc.allocSentinel(u8, zoom_title_prefix.len + slice.len, 0); + @memcpy(new_title[0..zoom_title_prefix.len], zoom_title_prefix); + @memcpy(new_title[zoom_title_prefix.len..], slice); + break :copy new_title; + }; errdefer alloc.free(copy); if (self.title_text) |old| alloc.free(old); @@ -834,7 +858,14 @@ pub fn setTitle(self: *Surface, slice: [:0]const u8) !void { } pub fn getTitle(self: *Surface) ?[:0]const u8 { - return self.title_text; + if (self.title_text) |title_text| { + return if (self.zoomed_in) + title_text + else + title_text[zoom_title_prefix.len..]; + } + + return null; } pub fn setMouseShape( @@ -1875,3 +1906,41 @@ pub fn present(self: *Surface) void { self.grabFocus(); } + +fn detachFromSplit(self: *Surface) void { + const split = self.container.split() orelse return; + switch (self.container.splitSide() orelse unreachable) { + .top_left => split.detachTopLeft(), + .bottom_right => split.detachBottomRight(), + } +} + +fn attachToSplit(self: *Surface) void { + const split = self.container.split() orelse return; + split.updateChildren(); +} + +pub fn setSplitZoom(self: *Surface, new_split_zoom: bool) void { + if (new_split_zoom == self.zoomed_in) return; + const tab = self.container.tab() orelse return; + + const tab_widget = tab.elem.widget(); + const surface_widget = self.primaryWidget(); + + if (new_split_zoom) { + self.detachFromSplit(); + c.gtk_box_remove(tab.box, tab_widget); + c.gtk_box_append(tab.box, surface_widget); + } else { + c.gtk_box_remove(tab.box, surface_widget); + self.attachToSplit(); + c.gtk_box_append(tab.box, tab_widget); + } + + self.zoomed_in = new_split_zoom; + self.grabFocus(); +} + +pub fn toggleSplitZoom(self: *Surface) void { + self.setSplitZoom(!self.zoomed_in); +} diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 5a4cd3f3e..347bc56d2 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -317,8 +317,7 @@ pub const Action = union(enum) { /// Focus on a split in a given direction. goto_split: SplitFocusDirection, - /// zoom/unzoom the current split. This is currently only supported - /// on macOS. Contributions welcome for other platforms. + /// zoom/unzoom the current split. toggle_split_zoom: void, /// Resize the current split by moving the split divider in the given