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.
pull/8858/head
Mitchell Hashimoto 2025-09-22 19:58:50 -07:00 committed by GitHub
commit e951dedc66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 0 deletions

View File

@ -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 { 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 // Create our tree
var single_tree = try Surface.Tree.init(alloc, surface); var single_tree = try Surface.Tree.init(alloc, surface);
defer single_tree.deinit(); 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 // Virtual methods
@ -816,6 +855,9 @@ pub const SplitTree = extern struct {
v.grabFocus(); 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 // Our active surface may have changed
self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec); 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.@"has-surfaces".impl,
properties.@"is-zoomed".impl, properties.@"is-zoomed".impl,
properties.tree.impl, properties.tree.impl,
properties.@"is-split".impl,
}); });
// Bindings // Bindings

View File

@ -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 { pub const signals = struct {
@ -503,6 +521,10 @@ pub const Surface = extern struct {
/// A weak reference to an inspector window. /// A weak reference to an inspector window.
inspector: ?*InspectorWindow = null, 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 // Template binds
child_exited_overlay: *ChildExited, child_exited_overlay: *ChildExited,
context_menu: *gtk.PopoverMenu, context_menu: *gtk.PopoverMenu,
@ -601,6 +623,16 @@ pub const Surface = extern struct {
return @intFromBool(config.@"bell-features".border); 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 { pub fn toggleFullscreen(self: *Self) void {
signals.@"toggle-fullscreen".impl.emit( signals.@"toggle-fullscreen".impl.emit(
self, self,
@ -2829,6 +2861,7 @@ pub const Surface = extern struct {
class.bindTemplateCallback("notify_mouse_shape", &propMouseShape); class.bindTemplateCallback("notify_mouse_shape", &propMouseShape);
class.bindTemplateCallback("notify_bell_ringing", &propBellRinging); class.bindTemplateCallback("notify_bell_ringing", &propBellRinging);
class.bindTemplateCallback("should_border_be_shown", &closureShouldBorderBeShown); class.bindTemplateCallback("should_border_be_shown", &closureShouldBorderBeShown);
class.bindTemplateCallback("should_unfocused_split_be_shown", &closureShouldUnfocusedSplitBeShown);
// Properties // Properties
gobject.ext.registerProperties(class, &.{ gobject.ext.registerProperties(class, &.{
@ -2847,6 +2880,7 @@ pub const Surface = extern struct {
properties.title.impl, properties.title.impl,
properties.@"title-override".impl, properties.@"title-override".impl,
properties.zoom.impl, properties.zoom.impl,
properties.@"is-split".impl,
}); });
// Signals // Signals

View File

@ -115,6 +115,20 @@ Overlay terminal_page {
label: bind template.mouse-hover-url; 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 // Event controllers for interactivity
EventControllerFocus { EventControllerFocus {
enter => $focus_enter(); enter => $focus_enter();