From b34e22053d1011d427052c64b3c46693fdb8e505 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 03:55:05 -0500 Subject: [PATCH 01/15] unfocused-split-fill working again though it can't detect when its actually a split yet --- src/apprt/gtk/class/surface.zig | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index c26d0c1ef..d64f6fe0a 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -435,6 +435,7 @@ pub const Surface = extern struct { /// The current focus state of the terminal based on the /// focus events. focused: bool = true, + unfocused_widget: ?*gtk.Widget = null, /// Whether this surface is "zoomed" or not. A zoomed surface /// shows up taking the full bounds of a split view. @@ -1989,6 +1990,11 @@ pub const Surface = extern struct { _ = glib.idleAddOnce(idleFocus, self.ref()); self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec); + if (priv.unfocused_widget) |widget| { + priv.terminal_page.removeOverlay(widget); + priv.unfocused_widget = null; + } + // Bell stops ringing as soon as we gain focus self.setBellRinging(false); } @@ -1999,6 +2005,16 @@ pub const Surface = extern struct { priv.im_context.as(gtk.IMContext).focusOut(); _ = glib.idleAddOnce(idleFocus, self.ref()); self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec); + + if (priv.unfocused_widget) |_| return; + // add unfocused-split-fill + priv.unfocused_widget = unfocused_widget: { + const drawing_area = gtk.DrawingArea.new(); + const unfocused_widget = drawing_area.as(gtk.Widget); + unfocused_widget.addCssClass("unfocused-split"); + priv.terminal_page.addOverlay(unfocused_widget); + break :unfocused_widget unfocused_widget; + }; } /// The focus callback must be triggered on an idle loop source because From 6ee9a3767b13aea5a7212cf6ccba220268068aeb Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 04:32:09 -0500 Subject: [PATCH 02/15] worse way but its at least split aware, doesn't seem like the path forward --- src/apprt/gtk/class/split_tree.zig | 7 ++++++- src/apprt/gtk/class/surface.zig | 29 +++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig index 3b6dcb4a9..0fe06a0b1 100644 --- a/src/apprt/gtk/class/split_tree.zig +++ b/src/apprt/gtk/class/split_tree.zig @@ -399,7 +399,12 @@ pub const SplitTree = extern struct { const tree = self.getTree() orelse return null; var it = tree.iterator(); while (it.next()) |entry| { - if (entry.view.getFocused()) return entry.handle; + if (!entry.view.getFocused() and it.nodes.len > 1) { + entry.view.setUnfocused(); + } + if (entry.view.getFocused()) { + return entry.handle; + } } // If none are currently focused, the most previously focused diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index d64f6fe0a..9906a1baf 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -1492,6 +1492,19 @@ pub const Surface = extern struct { return self.private().focused; } + pub fn setUnfocused(self: *Self) void { + const priv = self.private(); + if (!priv.focused and (priv.unfocused_widget == null)) { + priv.unfocused_widget = unfocused_widget: { + const drawing_area = gtk.DrawingArea.new(); + const unfocused_widget = drawing_area.as(gtk.Widget); + unfocused_widget.addCssClass("unfocused-split"); + priv.terminal_page.addOverlay(unfocused_widget); + break :unfocused_widget unfocused_widget; + }; + } + } + /// Change the configuration for this surface. pub fn setConfig(self: *Self, config: *Config) void { const priv = self.private(); @@ -2006,15 +2019,15 @@ pub const Surface = extern struct { _ = glib.idleAddOnce(idleFocus, self.ref()); self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec); - if (priv.unfocused_widget) |_| return; + // if (priv.unfocused_widget) |_| return; // add unfocused-split-fill - priv.unfocused_widget = unfocused_widget: { - const drawing_area = gtk.DrawingArea.new(); - const unfocused_widget = drawing_area.as(gtk.Widget); - unfocused_widget.addCssClass("unfocused-split"); - priv.terminal_page.addOverlay(unfocused_widget); - break :unfocused_widget unfocused_widget; - }; + // priv.unfocused_widget = unfocused_widget: { + // const drawing_area = gtk.DrawingArea.new(); + // const unfocused_widget = drawing_area.as(gtk.Widget); + // unfocused_widget.addCssClass("unfocused-split"); + // priv.terminal_page.addOverlay(unfocused_widget); + // break :unfocused_widget unfocused_widget; + // }; } /// The focus callback must be triggered on an idle loop source because From 52c2f02fa40f3f96dc2aa124a9e2fe8978bb0a48 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 20:18:28 -0500 Subject: [PATCH 03/15] use focus signal in split_tree to determine if we apply unfocused-split-fill --- src/apprt/gtk/class/split_tree.zig | 15 ++++++++------- src/apprt/gtk/class/surface.zig | 11 +---------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig index 0fe06a0b1..6ea84d055 100644 --- a/src/apprt/gtk/class/split_tree.zig +++ b/src/apprt/gtk/class/split_tree.zig @@ -399,12 +399,7 @@ pub const SplitTree = extern struct { const tree = self.getTree() orelse return null; var it = tree.iterator(); while (it.next()) |entry| { - if (!entry.view.getFocused() and it.nodes.len > 1) { - entry.view.setUnfocused(); - } - if (entry.view.getFocused()) { - return entry.handle; - } + if (entry.view.getFocused()) return entry.handle; } // If none are currently focused, the most previously focused @@ -715,7 +710,13 @@ pub const SplitTree = extern struct { // We never CLEAR our last_focused because the property is specifically // the last focused surface. We let the weakref clear itself when // the surface is destroyed. - if (!surface.getFocused()) return; + if (!surface.getFocused()) { + // If we have more than 1 active surface and we aren't focused we want to apply unfocused-split-fill + if (self.getTree()) |tree| { + if (tree.nodes.len > 1) surface.setUnfocused(); + } + return; + } self.private().last_focused.set(surface); // Our active surface probably changed diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 9906a1baf..eb1e17823 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -1492,6 +1492,7 @@ pub const Surface = extern struct { return self.private().focused; } + /// If unfocused add the unfocused-split widget for this surface pub fn setUnfocused(self: *Self) void { const priv = self.private(); if (!priv.focused and (priv.unfocused_widget == null)) { @@ -2018,16 +2019,6 @@ pub const Surface = extern struct { priv.im_context.as(gtk.IMContext).focusOut(); _ = glib.idleAddOnce(idleFocus, self.ref()); self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec); - - // if (priv.unfocused_widget) |_| return; - // add unfocused-split-fill - // priv.unfocused_widget = unfocused_widget: { - // const drawing_area = gtk.DrawingArea.new(); - // const unfocused_widget = drawing_area.as(gtk.Widget); - // unfocused_widget.addCssClass("unfocused-split"); - // priv.terminal_page.addOverlay(unfocused_widget); - // break :unfocused_widget unfocused_widget; - // }; } /// The focus callback must be triggered on an idle loop source because From cfcd11863ee0ea9b225117d96213b7eb41b87801 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 20:24:51 -0500 Subject: [PATCH 04/15] rename setUnfocused to setUnfocusedFill --- src/apprt/gtk/class/split_tree.zig | 2 +- src/apprt/gtk/class/surface.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig index 6ea84d055..825aebeb3 100644 --- a/src/apprt/gtk/class/split_tree.zig +++ b/src/apprt/gtk/class/split_tree.zig @@ -713,7 +713,7 @@ pub const SplitTree = extern struct { if (!surface.getFocused()) { // If we have more than 1 active surface and we aren't focused we want to apply unfocused-split-fill if (self.getTree()) |tree| { - if (tree.nodes.len > 1) surface.setUnfocused(); + if (tree.nodes.len > 1) surface.setUnfocusedFill(); } return; } diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index eb1e17823..7e1ea057f 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -1493,7 +1493,7 @@ pub const Surface = extern struct { } /// If unfocused add the unfocused-split widget for this surface - pub fn setUnfocused(self: *Self) void { + pub fn setUnfocusedFill(self: *Self) void { const priv = self.private(); if (!priv.focused and (priv.unfocused_widget == null)) { priv.unfocused_widget = unfocused_widget: { From fa5a44e591a9bac62aafed64d988912f189e23f5 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 21:37:59 -0500 Subject: [PATCH 05/15] apply unfocused-split with surface blueprint --- src/apprt/gtk/class/split_tree.zig | 2 +- src/apprt/gtk/class/surface.zig | 52 +++++++++++++++++++++--------- src/apprt/gtk/ui/1.2/surface.blp | 13 ++++++++ 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig index 825aebeb3..012db91f2 100644 --- a/src/apprt/gtk/class/split_tree.zig +++ b/src/apprt/gtk/class/split_tree.zig @@ -713,7 +713,7 @@ pub const SplitTree = extern struct { if (!surface.getFocused()) { // If we have more than 1 active surface and we aren't focused we want to apply unfocused-split-fill if (self.getTree()) |tree| { - if (tree.nodes.len > 1) surface.setUnfocusedFill(); + if (tree.nodes.len > 1) surface.setUnfocusedSplit(); } return; } diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 7e1ea057f..9253e3981 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -153,6 +153,24 @@ pub const Surface = extern struct { ); }; + pub const @"unfocused-split" = struct { + pub const name = "unfocused-split"; + const impl = gobject.ext.defineProperty( + name, + Self, + bool, + .{ + .default = true, + .accessor = gobject.ext.privateFieldAccessor( + Self, + Private, + &Private.offset, + "unfocused_split", + ), + }, + ); + }; + pub const @"min-size" = struct { pub const name = "min-size"; const impl = gobject.ext.defineProperty( @@ -435,7 +453,7 @@ pub const Surface = extern struct { /// The current focus state of the terminal based on the /// focus events. focused: bool = true, - unfocused_widget: ?*gtk.Widget = null, + unfocused_split: bool = false, /// Whether this surface is "zoomed" or not. A zoomed surface /// shows up taking the full bounds of a split view. @@ -601,6 +619,15 @@ pub const Surface = extern struct { return @intFromBool(config.@"bell-features".border); } + /// Callback used to determine whether unfocused-split-fill / unfocused-split-opacity + /// should be applied to the surface + fn closureShouldUnfocusedSplitBeShown( + _: *Self, + unfocused_split_: c_int, + ) callconv(.c) c_int { + return @intFromBool(unfocused_split_ != 0); + } + pub fn toggleFullscreen(self: *Self) void { signals.@"toggle-fullscreen".impl.emit( self, @@ -1305,6 +1332,7 @@ pub const Surface = extern struct { priv.mouse_shape = .text; priv.mouse_hidden = false; priv.focused = true; + priv.unfocused_split = false; priv.size = .{ .width = 0, .height = 0 }; // If our configuration is null then we get the configuration @@ -1493,17 +1521,10 @@ pub const Surface = extern struct { } /// If unfocused add the unfocused-split widget for this surface - pub fn setUnfocusedFill(self: *Self) void { + pub fn setUnfocusedSplit(self: *Self) void { const priv = self.private(); - if (!priv.focused and (priv.unfocused_widget == null)) { - priv.unfocused_widget = unfocused_widget: { - const drawing_area = gtk.DrawingArea.new(); - const unfocused_widget = drawing_area.as(gtk.Widget); - unfocused_widget.addCssClass("unfocused-split"); - priv.terminal_page.addOverlay(unfocused_widget); - break :unfocused_widget unfocused_widget; - }; - } + priv.unfocused_split = !priv.focused; + self.as(gobject.Object).notifyByPspec(properties.@"unfocused-split".impl.param_spec); } /// Change the configuration for this surface. @@ -2004,10 +2025,9 @@ pub const Surface = extern struct { _ = glib.idleAddOnce(idleFocus, self.ref()); self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec); - if (priv.unfocused_widget) |widget| { - priv.terminal_page.removeOverlay(widget); - priv.unfocused_widget = null; - } + // remove unfocused split fill and opacity + priv.unfocused_split = false; + self.as(gobject.Object).notifyByPspec(properties.@"unfocused-split".impl.param_spec); // Bell stops ringing as soon as we gain focus self.setBellRinging(false); @@ -2783,6 +2803,7 @@ pub const Surface = extern struct { class.bindTemplateCallback("notify_mouse_shape", &propMouseShape); class.bindTemplateCallback("notify_bell_ringing", &propBellRinging); class.bindTemplateCallback("should_border_be_shown", &closureShouldBorderBeShown); + class.bindTemplateCallback("should_unfocused_split_be_shown", &closureShouldUnfocusedSplitBeShown); // Properties gobject.ext.registerProperties(class, &.{ @@ -2793,6 +2814,7 @@ pub const Surface = extern struct { properties.@"error".impl, properties.@"font-size-request".impl, properties.focused.impl, + properties.@"unfocused-split".impl, properties.@"min-size".impl, properties.@"mouse-shape".impl, properties.@"mouse-hidden".impl, diff --git a/src/apprt/gtk/ui/1.2/surface.blp b/src/apprt/gtk/ui/1.2/surface.blp index f22f2c09a..ba17393b5 100644 --- a/src/apprt/gtk/ui/1.2/surface.blp +++ b/src/apprt/gtk/ui/1.2/surface.blp @@ -115,6 +115,19 @@ Overlay terminal_page { label: bind template.mouse-hover-url; } + [overlay] + // Apply unfocused-split-fill and unfocused-split opacity to current surface + // this is only applied when a tab has more than one surface + Revealer { + reveal-child: bind $should_unfocused_split_be_shown(template.unfocused-split) as ; + transition-duration: 0; + DrawingArea { + styles [ + "unfocused-split", + ] + } + } + // Event controllers for interactivity EventControllerFocus { enter => $focus_enter(); From 180cc31d874c27c6ce20b68ee500ef42ca4d3897 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 21:41:15 -0500 Subject: [PATCH 06/15] fix comments to specific the config option --- src/apprt/gtk/class/surface.zig | 6 +++--- src/apprt/gtk/ui/1.2/surface.blp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 9253e3981..dc6637655 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -623,9 +623,9 @@ pub const Surface = extern struct { /// should be applied to the surface fn closureShouldUnfocusedSplitBeShown( _: *Self, - unfocused_split_: c_int, + unfocused_split: c_int, ) callconv(.c) c_int { - return @intFromBool(unfocused_split_ != 0); + return @intFromBool(unfocused_split != 0); } pub fn toggleFullscreen(self: *Self) void { @@ -2025,7 +2025,7 @@ pub const Surface = extern struct { _ = glib.idleAddOnce(idleFocus, self.ref()); self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec); - // remove unfocused split fill and opacity + // remove unfocused-split-fill and unfocused-split-opacity priv.unfocused_split = false; self.as(gobject.Object).notifyByPspec(properties.@"unfocused-split".impl.param_spec); diff --git a/src/apprt/gtk/ui/1.2/surface.blp b/src/apprt/gtk/ui/1.2/surface.blp index ba17393b5..ba1556a61 100644 --- a/src/apprt/gtk/ui/1.2/surface.blp +++ b/src/apprt/gtk/ui/1.2/surface.blp @@ -116,7 +116,7 @@ Overlay terminal_page { } [overlay] - // Apply unfocused-split-fill and unfocused-split opacity to current surface + // Apply unfocused-split-fill and unfocused-split-opacity to current surface // this is only applied when a tab has more than one surface Revealer { reveal-child: bind $should_unfocused_split_be_shown(template.unfocused-split) as ; From 42fc42de2d2ff108d4aa3ff309b246c1f3fc23b0 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 21:45:01 -0500 Subject: [PATCH 07/15] fix surface blueprint formatting --- src/apprt/gtk/ui/1.2/surface.blp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apprt/gtk/ui/1.2/surface.blp b/src/apprt/gtk/ui/1.2/surface.blp index ba1556a61..0b9a77133 100644 --- a/src/apprt/gtk/ui/1.2/surface.blp +++ b/src/apprt/gtk/ui/1.2/surface.blp @@ -121,6 +121,7 @@ Overlay terminal_page { Revealer { reveal-child: bind $should_unfocused_split_be_shown(template.unfocused-split) as ; transition-duration: 0; + DrawingArea { styles [ "unfocused-split", From 3d3551d1edf0dee6ca4d0ec4f1956e025fdcb74a Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 22:45:17 -0500 Subject: [PATCH 08/15] add n_siblings member, and when splits are created / removed update the number of siblings for the remaining nodes --- src/apprt/gtk/class/split_tree.zig | 8 +--- src/apprt/gtk/class/surface.zig | 61 +++++++++++++++--------------- src/apprt/gtk/ui/1.2/surface.blp | 2 +- src/datastruct/split_tree.zig | 16 +++++++- 4 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig index 012db91f2..3b6dcb4a9 100644 --- a/src/apprt/gtk/class/split_tree.zig +++ b/src/apprt/gtk/class/split_tree.zig @@ -710,13 +710,7 @@ pub const SplitTree = extern struct { // We never CLEAR our last_focused because the property is specifically // the last focused surface. We let the weakref clear itself when // the surface is destroyed. - if (!surface.getFocused()) { - // If we have more than 1 active surface and we aren't focused we want to apply unfocused-split-fill - if (self.getTree()) |tree| { - if (tree.nodes.len > 1) surface.setUnfocusedSplit(); - } - return; - } + if (!surface.getFocused()) return; self.private().last_focused.set(surface); // Our active surface probably changed diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index dc6637655..165195469 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -153,24 +153,6 @@ pub const Surface = extern struct { ); }; - pub const @"unfocused-split" = struct { - pub const name = "unfocused-split"; - const impl = gobject.ext.defineProperty( - name, - Self, - bool, - .{ - .default = true, - .accessor = gobject.ext.privateFieldAccessor( - Self, - Private, - &Private.offset, - "unfocused_split", - ), - }, - ); - }; - pub const @"min-size" = struct { pub const name = "min-size"; const impl = gobject.ext.defineProperty( @@ -292,6 +274,26 @@ pub const Surface = extern struct { }, ); }; + + pub const @"n-siblings" = struct { + pub const name = "n-siblings"; + const impl = gobject.ext.defineProperty( + name, + Self, + u64, + .{ + .default = 0, + .minimum = 0, + .maximum = std.math.maxInt(c_uint), + .accessor = gobject.ext.privateFieldAccessor( + Self, + Private, + &Private.offset, + "n_siblings", + ), + }, + ); + }; }; pub const signals = struct { @@ -453,7 +455,6 @@ pub const Surface = extern struct { /// The current focus state of the terminal based on the /// focus events. focused: bool = true, - unfocused_split: bool = false, /// Whether this surface is "zoomed" or not. A zoomed surface /// shows up taking the full bounds of a split view. @@ -521,6 +522,8 @@ pub const Surface = extern struct { /// A weak reference to an inspector window. inspector: ?*InspectorWindow = null, + n_siblings: u64 = 0, + // Template binds child_exited_overlay: *ChildExited, context_menu: *gtk.PopoverMenu, @@ -623,9 +626,10 @@ pub const Surface = extern struct { /// should be applied to the surface fn closureShouldUnfocusedSplitBeShown( _: *Self, - unfocused_split: c_int, + focused: c_int, + n_siblings: c_int, ) callconv(.c) c_int { - return @intFromBool(unfocused_split != 0); + return @intFromBool(focused == 0 and n_siblings > 0); } pub fn toggleFullscreen(self: *Self) void { @@ -1332,7 +1336,6 @@ pub const Surface = extern struct { priv.mouse_shape = .text; priv.mouse_hidden = false; priv.focused = true; - priv.unfocused_split = false; priv.size = .{ .width = 0, .height = 0 }; // If our configuration is null then we get the configuration @@ -1520,11 +1523,11 @@ pub const Surface = extern struct { return self.private().focused; } - /// If unfocused add the unfocused-split widget for this surface - pub fn setUnfocusedSplit(self: *Self) void { + /// Set number of siblings related to this surface. + pub fn setNSiblings(self: *Self, siblings: u64) void { const priv = self.private(); - priv.unfocused_split = !priv.focused; - self.as(gobject.Object).notifyByPspec(properties.@"unfocused-split".impl.param_spec); + priv.n_siblings = siblings; + self.as(gobject.Object).notifyByPspec(properties.@"n-siblings".impl.param_spec); } /// Change the configuration for this surface. @@ -2025,10 +2028,6 @@ pub const Surface = extern struct { _ = glib.idleAddOnce(idleFocus, self.ref()); self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec); - // remove unfocused-split-fill and unfocused-split-opacity - priv.unfocused_split = false; - self.as(gobject.Object).notifyByPspec(properties.@"unfocused-split".impl.param_spec); - // Bell stops ringing as soon as we gain focus self.setBellRinging(false); } @@ -2814,7 +2813,6 @@ pub const Surface = extern struct { properties.@"error".impl, properties.@"font-size-request".impl, properties.focused.impl, - properties.@"unfocused-split".impl, properties.@"min-size".impl, properties.@"mouse-shape".impl, properties.@"mouse-hidden".impl, @@ -2823,6 +2821,7 @@ pub const Surface = extern struct { properties.title.impl, properties.@"title-override".impl, properties.zoom.impl, + properties.@"n-siblings".impl, }); // Signals diff --git a/src/apprt/gtk/ui/1.2/surface.blp b/src/apprt/gtk/ui/1.2/surface.blp index 0b9a77133..35d897525 100644 --- a/src/apprt/gtk/ui/1.2/surface.blp +++ b/src/apprt/gtk/ui/1.2/surface.blp @@ -119,7 +119,7 @@ Overlay terminal_page { // Apply unfocused-split-fill and unfocused-split-opacity to current surface // this is only applied when a tab has more than one surface Revealer { - reveal-child: bind $should_unfocused_split_be_shown(template.unfocused-split) as ; + reveal-child: bind $should_unfocused_split_be_shown(template.focused, template.n-siblings) as ; transition-duration: 0; DrawingArea { diff --git a/src/datastruct/split_tree.zig b/src/datastruct/split_tree.zig index 28b45ceed..212711e3d 100644 --- a/src/datastruct/split_tree.zig +++ b/src/datastruct/split_tree.zig @@ -507,12 +507,16 @@ pub fn SplitTree(comptime V: type) type { // We need to increase the reference count of all the nodes. try refNodes(gpa, nodes); - return .{ + const result: Self = .{ .arena = arena, .nodes = nodes, // Splitting always resets zoom state. .zoomed = null, }; + + result.updateNodesNumberSiblings(); + + return result; } /// Remove a node from the tree. @@ -556,6 +560,8 @@ pub fn SplitTree(comptime V: type) type { // Increase the reference count of all the nodes. try refNodes(gpa, nodes); + result.updateNodesNumberSiblings(); + return result; } @@ -865,6 +871,14 @@ pub fn SplitTree(comptime V: type) type { }; } + /// Set the number of siblings for each split in the tree + fn updateNodesNumberSiblings(self: *const Self) void { + var it = self.iterator(); + while (it.next()) |entry| { + entry.view.setNSiblings(self.nodes.len - 1); + } + } + /// Spatial representation of the split tree. See spatial. pub const Spatial = struct { /// The slots of the spatial representation in the same order From 1c59ed5d605b8cd9ddcccdd7d54a3fb2fdcc9ea7 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 22:47:50 -0500 Subject: [PATCH 09/15] comment for the n_siblings surface member --- src/apprt/gtk/class/surface.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 165195469..527a6136b 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -522,6 +522,9 @@ pub const Surface = extern struct { /// A weak reference to an inspector window. inspector: ?*InspectorWindow = null, + /// Number of siblings related to this surface. This is used for + /// the unfocused-split-* options which are only applied when ther is + /// more than once surface (split) in a tab n_siblings: u64 = 0, // Template binds From 3254bb41d5437196e69fba70a00f4538ff2e5b59 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sat, 20 Sep 2025 22:49:35 -0500 Subject: [PATCH 10/15] fix typo --- src/apprt/gtk/class/surface.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 527a6136b..087f2d286 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -523,7 +523,7 @@ pub const Surface = extern struct { inspector: ?*InspectorWindow = null, /// Number of siblings related to this surface. This is used for - /// the unfocused-split-* options which are only applied when ther is + /// the unfocused-split-* options which are only applied when their is /// more than once surface (split) in a tab n_siblings: u64 = 0, From fdcaeebb9943f8057963bb97df94cfffecf8c011 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sun, 21 Sep 2025 20:35:20 -0500 Subject: [PATCH 11/15] bind is-split property between split_tree class and surface class --- src/apprt/gtk/class/split_tree.zig | 47 ++++++++++++++++++++++++++++++ src/apprt/gtk/class/surface.zig | 38 +++++++++++------------- src/apprt/gtk/ui/1.2/surface.blp | 2 +- src/datastruct/split_tree.zig | 16 +--------- 4 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig index 3b6dcb4a9..35161b4d6 100644 --- a/src/apprt/gtk/class/split_tree.zig +++ b/src/apprt/gtk/class/split_tree.zig @@ -112,6 +112,25 @@ pub const SplitTree = extern struct { }, ); }; + + pub const @"is-split" = struct { + pub const name = "is-split"; + const impl = gobject.ext.defineProperty( + name, + Self, + bool, + .{ + .default = false, + .accessor = gobject.ext.typedAccessor( + Self, + bool, + .{ + .setter = setIsSplit, + }, + ), + }, + ); + }; }; pub const signals = struct { @@ -153,6 +172,9 @@ pub const SplitTree = extern struct { /// close dialog. pending_close: ?Surface.Tree.Node.Handle, + /// True if the current split tree contains more than one surface + is_split: bool = false, + pub var offset: c_int = 0; }; @@ -210,6 +232,14 @@ pub const SplitTree = extern struct { } } + // Bind is-split property for new surface + _ = self.as(gobject.Object).bindProperty( + "is-split", + surface.as(gobject.Object), + "is-split", + .{ .sync_create = true }, + ); + // Create our tree var single_tree = try Surface.Tree.init(alloc, surface); defer single_tree.deinit(); @@ -511,6 +541,12 @@ pub const SplitTree = extern struct { )); } + fn setIsSplit(self: *Self, v: bool) void { + const priv = self.private(); + priv.is_split = v; + self.as(gobject.Object).notifyByPspec(properties.@"is-split".impl.param_spec); + } + //--------------------------------------------------------------- // Virtual methods @@ -808,6 +844,16 @@ pub const SplitTree = extern struct { )); } + // Determine if tree has more than one surface + const root_handle: Surface.Tree.Node.Handle = .root; + const root = tree.nodes[root_handle.idx()]; + const is_split: bool = switch (root) { + .leaf => false, + .split => true, + }; + + self.setIsSplit(is_split); + // If we have a last focused surface, we need to refocus it, because // during the frame between setting the bin to null and rebuilding, // GTK will reset our focus state (as it should!) @@ -873,6 +919,7 @@ pub const SplitTree = extern struct { properties.@"has-surfaces".impl, properties.@"is-zoomed".impl, properties.tree.impl, + properties.@"is-split".impl, }); // Bindings diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 087f2d286..e1efab620 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -275,21 +275,19 @@ pub const Surface = extern struct { ); }; - pub const @"n-siblings" = struct { - pub const name = "n-siblings"; + pub const @"is-split" = struct { + pub const name = "is-split"; const impl = gobject.ext.defineProperty( name, Self, - u64, + bool, .{ - .default = 0, - .minimum = 0, - .maximum = std.math.maxInt(c_uint), + .default = false, .accessor = gobject.ext.privateFieldAccessor( Self, Private, &Private.offset, - "n_siblings", + "is_split", ), }, ); @@ -522,10 +520,9 @@ pub const Surface = extern struct { /// A weak reference to an inspector window. inspector: ?*InspectorWindow = null, - /// Number of siblings related to this surface. This is used for - /// the unfocused-split-* options which are only applied when their is - /// more than once surface (split) in a tab - n_siblings: u64 = 0, + // True if the current surface is a split, this is used to apply + // unfocused-split-* options + is_split: bool = false, // Template binds child_exited_overlay: *ChildExited, @@ -630,9 +627,9 @@ pub const Surface = extern struct { fn closureShouldUnfocusedSplitBeShown( _: *Self, focused: c_int, - n_siblings: c_int, + is_split: c_int, ) callconv(.c) c_int { - return @intFromBool(focused == 0 and n_siblings > 0); + return @intFromBool(focused == 0 and is_split != 0); } pub fn toggleFullscreen(self: *Self) void { @@ -1526,13 +1523,6 @@ pub const Surface = extern struct { return self.private().focused; } - /// Set number of siblings related to this surface. - pub fn setNSiblings(self: *Self, siblings: u64) void { - const priv = self.private(); - priv.n_siblings = siblings; - self.as(gobject.Object).notifyByPspec(properties.@"n-siblings".impl.param_spec); - } - /// Change the configuration for this surface. pub fn setConfig(self: *Self, config: *Config) void { const priv = self.private(); @@ -1630,6 +1620,12 @@ pub const Surface = extern struct { self.as(gobject.Object).notifyByPspec(properties.@"error".impl.param_spec); } + // pub fn setIsSplit(self: *Self, v: bool) void { + // const priv = self.private(); + // priv.is_split = v; + // self.as(gobject.Object).notifyByPspec(properties.@"is-split".impl.param_spec); + // } + fn propConfig( self: *Self, _: *gobject.ParamSpec, @@ -2824,7 +2820,7 @@ pub const Surface = extern struct { properties.title.impl, properties.@"title-override".impl, properties.zoom.impl, - properties.@"n-siblings".impl, + properties.@"is-split".impl, }); // Signals diff --git a/src/apprt/gtk/ui/1.2/surface.blp b/src/apprt/gtk/ui/1.2/surface.blp index 35d897525..ad971e991 100644 --- a/src/apprt/gtk/ui/1.2/surface.blp +++ b/src/apprt/gtk/ui/1.2/surface.blp @@ -119,7 +119,7 @@ Overlay terminal_page { // Apply unfocused-split-fill and unfocused-split-opacity to current surface // this is only applied when a tab has more than one surface Revealer { - reveal-child: bind $should_unfocused_split_be_shown(template.focused, template.n-siblings) as ; + reveal-child: bind $should_unfocused_split_be_shown(template.focused, template.is-split) as ; transition-duration: 0; DrawingArea { diff --git a/src/datastruct/split_tree.zig b/src/datastruct/split_tree.zig index 212711e3d..28b45ceed 100644 --- a/src/datastruct/split_tree.zig +++ b/src/datastruct/split_tree.zig @@ -507,16 +507,12 @@ pub fn SplitTree(comptime V: type) type { // We need to increase the reference count of all the nodes. try refNodes(gpa, nodes); - const result: Self = .{ + return .{ .arena = arena, .nodes = nodes, // Splitting always resets zoom state. .zoomed = null, }; - - result.updateNodesNumberSiblings(); - - return result; } /// Remove a node from the tree. @@ -560,8 +556,6 @@ pub fn SplitTree(comptime V: type) type { // Increase the reference count of all the nodes. try refNodes(gpa, nodes); - result.updateNodesNumberSiblings(); - return result; } @@ -871,14 +865,6 @@ pub fn SplitTree(comptime V: type) type { }; } - /// Set the number of siblings for each split in the tree - fn updateNodesNumberSiblings(self: *const Self) void { - var it = self.iterator(); - while (it.next()) |entry| { - entry.view.setNSiblings(self.nodes.len - 1); - } - } - /// Spatial representation of the split tree. See spatial. pub const Spatial = struct { /// The slots of the spatial representation in the same order From 4e7f847d039bf4b3be721ffded46ef75521f3a3d Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sun, 21 Sep 2025 20:37:47 -0500 Subject: [PATCH 12/15] remove setIsSplit in surface class --- src/apprt/gtk/class/surface.zig | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index e1efab620..24f247137 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -1620,12 +1620,6 @@ pub const Surface = extern struct { self.as(gobject.Object).notifyByPspec(properties.@"error".impl.param_spec); } - // pub fn setIsSplit(self: *Self, v: bool) void { - // const priv = self.private(); - // priv.is_split = v; - // self.as(gobject.Object).notifyByPspec(properties.@"is-split".impl.param_spec); - // } - fn propConfig( self: *Self, _: *gobject.ParamSpec, From fd9014952fce6151bd0070cabd648dbdc7c7227a Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Sun, 21 Sep 2025 22:59:42 -0500 Subject: [PATCH 13/15] use getter to notify is-split property --- src/apprt/gtk/class/split_tree.zig | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig index 35161b4d6..69a4e40f3 100644 --- a/src/apprt/gtk/class/split_tree.zig +++ b/src/apprt/gtk/class/split_tree.zig @@ -125,7 +125,7 @@ pub const SplitTree = extern struct { Self, bool, .{ - .setter = setIsSplit, + .getter = getIsSplit, }, ), }, @@ -541,10 +541,16 @@ pub const SplitTree = extern struct { )); } - fn setIsSplit(self: *Self, v: bool) void { - const priv = self.private(); - priv.is_split = v; - self.as(gobject.Object).notifyByPspec(properties.@"is-split".impl.param_spec); + fn getIsSplit(self: *Self) bool { + const tree: *const Surface.Tree = self.private().tree orelse &.empty; + if (tree.isEmpty()) return false; + + const root_handle: Surface.Tree.Node.Handle = .root; + const root = tree.nodes[root_handle.idx()]; + return switch (root) { + .leaf => false, + .split => true, + }; } //--------------------------------------------------------------- @@ -844,16 +850,6 @@ pub const SplitTree = extern struct { )); } - // Determine if tree has more than one surface - const root_handle: Surface.Tree.Node.Handle = .root; - const root = tree.nodes[root_handle.idx()]; - const is_split: bool = switch (root) { - .leaf => false, - .split => true, - }; - - self.setIsSplit(is_split); - // If we have a last focused surface, we need to refocus it, because // during the frame between setting the bin to null and rebuilding, // GTK will reset our focus state (as it should!) @@ -862,6 +858,9 @@ pub const SplitTree = extern struct { v.grabFocus(); } + // Our split status may have changed + self.as(gobject.Object).notifyByPspec(properties.@"is-split".impl.param_spec); + // Our active surface may have changed self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec); From f36ccc48662c9296c8b18fc3bd1329ba7c3d9de1 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:22:53 -0500 Subject: [PATCH 14/15] remove is_split properity in priv since it has a getter method --- pkg/opengl/Shader.zig | 1 + src/apprt/gtk/class/split_tree.zig | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/opengl/Shader.zig b/pkg/opengl/Shader.zig index 8973ace30..1105e0745 100644 --- a/pkg/opengl/Shader.zig +++ b/pkg/opengl/Shader.zig @@ -23,6 +23,7 @@ pub inline fn create(typ: c.GLenum) errors.Error!Shader { /// Set the source and compile a shader. pub inline fn setSourceAndCompile(s: Shader, source: [:0]const u8) !void { + log.info("shader source:{s}", .{source}); glad.context.ShaderSource.?(s.id, 1, &@as([*c]const u8, @ptrCast(source)), null); glad.context.CompileShader.?(s.id); diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig index 69a4e40f3..755b51e9a 100644 --- a/src/apprt/gtk/class/split_tree.zig +++ b/src/apprt/gtk/class/split_tree.zig @@ -172,9 +172,6 @@ pub const SplitTree = extern struct { /// close dialog. pending_close: ?Surface.Tree.Node.Handle, - /// True if the current split tree contains more than one surface - is_split: bool = false, - pub var offset: c_int = 0; }; From 36e09cdbe1e314c3ad565d7286e1265684071992 Mon Sep 17 00:00:00 2001 From: rhodes-b <59537185+rhodes-b@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:30:03 -0500 Subject: [PATCH 15/15] remove debug print from helping something with graphics issues --- pkg/opengl/Shader.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/opengl/Shader.zig b/pkg/opengl/Shader.zig index 1105e0745..8973ace30 100644 --- a/pkg/opengl/Shader.zig +++ b/pkg/opengl/Shader.zig @@ -23,7 +23,6 @@ pub inline fn create(typ: c.GLenum) errors.Error!Shader { /// Set the source and compile a shader. pub inline fn setSourceAndCompile(s: Shader, source: [:0]const u8) !void { - log.info("shader source:{s}", .{source}); glad.context.ShaderSource.?(s.id, 1, &@as([*c]const u8, @ptrCast(source)), null); glad.context.CompileShader.?(s.id);