From 6c40cd963d9ffe1fc10c4e5d8dea3d37b2364bbe Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Wed, 10 Jul 2024 11:58:03 -0500 Subject: [PATCH] gtk: implement unfocused-split opacity and fill For a long time, us GTK users have been subject to lesser UX by not knowing which split was focused. Improve the GTK UX by implementing both unfocused-split-opacity and unfocused-split-fill. This is implemented by setting the background-color of the notebook stack, and conditionally applying a new css class "unfocused-split" to the unfocused split. --- src/apprt/gtk/App.zig | 4 ++++ src/apprt/gtk/Surface.zig | 11 +++++++++++ src/apprt/gtk/Window.zig | 30 ++++++++++++++++++++++++++++++ src/config/Config.zig | 4 ---- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 6d8bdce27..f0ba41d3a 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -70,6 +70,10 @@ x11_xkb: ?x11.Xkb = null, /// and initialization was successful. transient_cgroup_base: ?[]const u8 = null, +/// True if we have initialized runtime CSS. We only need to do this once for the application, but +/// we can't perform the intialization until we have created a window +runtime_css_intialized: bool = false, + pub fn init(core_app: *CoreApp, opts: Options) !App { _ = opts; diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 62a432c9e..d9b60bb39 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -1809,6 +1809,9 @@ fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) vo // Notify our IM context c.gtk_im_context_focus_in(self.im_context); + // Unconditionally remove the unfocused split css class + c.gtk_widget_remove_css_class(@ptrCast(@alignCast(self.gl_area)), "unfocused-split"); + // Notify our surface self.core_surface.focusCallback(true) catch |err| { log.err("error in focus callback err={}", .{err}); @@ -1823,6 +1826,14 @@ fn gtkFocusLeave(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) vo // Notify our IM context c.gtk_im_context_focus_out(self.im_context); + // We only add the unfocused-split class if we are actually a split + switch (self.container) { + .split_br, + .split_tl, + => c.gtk_widget_add_css_class(@ptrCast(@alignCast(self.gl_area)), "unfocused-split"), + else => {}, + } + self.core_surface.focusCallback(false) catch |err| { log.err("error in focus callback err={}", .{err}); return; diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 0cb73c407..b68d0370e 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -16,6 +16,7 @@ const input = @import("../../input.zig"); const CoreSurface = @import("../../Surface.zig"); const App = @import("App.zig"); +const Color = configpkg.Config.Color; const Surface = @import("Surface.zig"); const Tab = @import("Tab.zig"); const c = @import("c.zig"); @@ -71,6 +72,35 @@ pub fn init(self: *Window, app: *App) !void { c.gtk_widget_set_opacity(@ptrCast(window), app.config.@"background-opacity"); } + if (!app.runtime_css_intialized) { + // Intialize runtime CSS. This CSS requires ghostty configuration values so we don't set it + // in style.css. We also have to have a window in order to add a style_context to a display, + // so we intialize after creation of our first window + app.runtime_css_intialized = true; + + const display = c.gdk_display_get_default(); + const provider = c.gtk_css_provider_new(); + defer c.g_object_unref(provider); + const fill: Color = app.config.@"unfocused-split-fill" orelse app.config.background; + var css_buf: [128]u8 = undefined; + + // We will add the unfocused-split class in our focus callbacks. We unconditionally add the + // background-color to the notebook stack because it only comes into play if we have an + // unfocused split + const css = try std.fmt.bufPrintZ( + &css_buf, + "widget.unfocused-split {{ opacity: {d:.2}; }}\nstack {{ background-color: rgb({d},{d},{d});}}", + .{ + app.config.@"unfocused-split-opacity", + fill.r, + fill.g, + fill.b, + }, + ); + c.gtk_css_provider_load_from_string(provider, css); + c.gtk_style_context_add_provider_for_display(display, @ptrCast(provider), c.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + } + // Use the new GTK4 header bar. We only create a header bar if we have // window decorations. if (app.config.@"window-decoration") { diff --git a/src/config/Config.zig b/src/config/Config.zig index e3165a988..e4de1d22e 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -409,8 +409,6 @@ palette: Palette = .{}, /// is 0.15. This value still looks weird but you can at least see what's going /// on. A value outside of the range 0.15 to 1 will be clamped to the nearest /// valid value. -/// -/// This is only supported on macOS. @"unfocused-split-opacity": f64 = 0.7, /// The color to dim the unfocused split. Unfocused splits are dimmed by @@ -418,8 +416,6 @@ palette: Palette = .{}, /// that rectangle and can be used to carefully control the dimming effect. /// /// This will default to the background color. -/// -/// This is only supported on macOS. @"unfocused-split-fill": ?Color = null, /// The command to run, usually a shell. If this is not an absolute path, it'll