summaryrefslogtreecommitdiff
path: root/src/apprt
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-09-22 19:58:50 -0700
committerGitHub <noreply@github.com>2025-09-22 19:58:50 -0700
commite951dedc66eb1077490e626d0e4edd7301d0e4e1 (patch)
tree8240c70d3409341d2506b682971c6dcc478a1cff /src/apprt
parent8cb52323e5575877eef44029c153eda006cece80 (diff)
parent36e09cdbe1e314c3ad565d7286e1265684071992 (diff)
GTK Fix unfocused-split-fill (#8813)
Attempts a resolution for https://github.com/ghostty-org/ghostty/discussions/8572 This matches the behavior of the old GTK apprt where unfocused-split-fill /opacity doesn't apply when there is only one active surface.
Diffstat (limited to 'src/apprt')
-rw-r--r--src/apprt/gtk/class/split_tree.zig43
-rw-r--r--src/apprt/gtk/class/surface.zig34
-rw-r--r--src/apprt/gtk/ui/1.2/surface.blp14
3 files changed, 91 insertions, 0 deletions
diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig
index 3b6dcb4a9..755b51e9a 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,
+ .{
+ .getter = getIsSplit,
+ },
+ ),
+ },
+ );
+ };
};
pub const signals = struct {
@@ -210,6 +229,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 +538,18 @@ pub const SplitTree = extern struct {
));
}
+ 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,
+ };
+ }
+
//---------------------------------------------------------------
// Virtual methods
@@ -816,6 +855,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);
@@ -873,6 +915,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 616ec54f5..fb933073c 100644
--- a/src/apprt/gtk/class/surface.zig
+++ b/src/apprt/gtk/class/surface.zig
@@ -275,6 +275,24 @@ pub const Surface = 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.privateFieldAccessor(
+ Self,
+ Private,
+ &Private.offset,
+ "is_split",
+ ),
+ },
+ );
+ };
};
pub const signals = struct {
@@ -503,6 +521,10 @@ pub const Surface = extern struct {
/// A weak reference to an inspector window.
inspector: ?*InspectorWindow = null,
+ // 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,
context_menu: *gtk.PopoverMenu,
@@ -601,6 +623,16 @@ 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,
+ focused: c_int,
+ is_split: c_int,
+ ) callconv(.c) c_int {
+ return @intFromBool(focused == 0 and is_split != 0);
+ }
+
pub fn toggleFullscreen(self: *Self) void {
signals.@"toggle-fullscreen".impl.emit(
self,
@@ -2829,6 +2861,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, &.{
@@ -2847,6 +2880,7 @@ pub const Surface = extern struct {
properties.title.impl,
properties.@"title-override".impl,
properties.zoom.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 f22f2c09a..ad971e991 100644
--- a/src/apprt/gtk/ui/1.2/surface.blp
+++ b/src/apprt/gtk/ui/1.2/surface.blp
@@ -115,6 +115,20 @@ 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.focused, template.is-split) as <bool>;
+ transition-duration: 0;
+
+ DrawingArea {
+ styles [
+ "unfocused-split",
+ ]
+ }
+ }
+
// Event controllers for interactivity
EventControllerFocus {
enter => $focus_enter();