Skip to content

Commit 4a12775

Browse files
committed
gtk-ng: add "title bar styles"
This PR adds a "tabs" title bar style similar to the macOS title bar style. When `gtk-titlebar-style=tabs` the title bar and the tab bar will be merged together. The config entry for controlling this is kept separate from macOS as macOS has more styles defined that don't map to a GTK title bar style and it's likely that users that use both macOS and GTK would want different settings for each platform.
1 parent 6238103 commit 4a12775

File tree

3 files changed

+180
-19
lines changed

3 files changed

+180
-19
lines changed

src/apprt/gtk-ng/class/window.zig

Lines changed: 92 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const gtk = @import("gtk");
1111
const i18n = @import("../../../os/main.zig").i18n;
1212
const apprt = @import("../../../apprt.zig");
1313
const configpkg = @import("../../../config.zig");
14+
const TitlebarStyle = configpkg.Config.GtkTitlebarStyle;
1415
const input = @import("../../../input.zig");
1516
const CoreSurface = @import("../../../Surface.zig");
1617
const ext = @import("../ext.zig");
@@ -101,6 +102,25 @@ pub const Window = extern struct {
101102
);
102103
};
103104

105+
pub const @"titlebar-style" = struct {
106+
pub const name = "titlebar-style";
107+
const impl = gobject.ext.defineProperty(
108+
name,
109+
Self,
110+
TitlebarStyle,
111+
.{
112+
.default = .native,
113+
.accessor = gobject.ext.typedAccessor(
114+
Self,
115+
TitlebarStyle,
116+
.{
117+
.getter = Self.getTitlebarStyle,
118+
},
119+
),
120+
},
121+
);
122+
};
123+
104124
pub const @"headerbar-visible" = struct {
105125
pub const name = "headerbar-visible";
106126
const impl = gobject.ext.defineProperty(
@@ -546,6 +566,7 @@ pub const Window = extern struct {
546566
"tabs-visible",
547567
"tabs-wide",
548568
"toolbar-style",
569+
"titlebar-style",
549570
}) |key| {
550571
self.as(gobject.Object).notifyByPspec(
551572
@field(properties, key).impl.param_spec,
@@ -716,6 +737,14 @@ pub const Window = extern struct {
716737
return false;
717738
}
718739

740+
fn isFullscreen(self: *Window) bool {
741+
return self.as(gtk.Window).isFullscreen() != 0;
742+
}
743+
744+
fn isMaximized(self: *Window) bool {
745+
return self.as(gtk.Window).isMaximized() != 0;
746+
}
747+
719748
fn getHeaderbarVisible(self: *Self) bool {
720749
const priv = self.private();
721750

@@ -727,46 +756,72 @@ pub const Window = extern struct {
727756
if (priv.quick_terminal) return false;
728757

729758
// If we're fullscreen we never show the header bar.
730-
if (self.as(gtk.Window).isFullscreen() != 0) return false;
759+
if (self.isFullscreen()) return false;
731760

732761
// The remainder needs a config
733762
const config_obj = self.private().config orelse return true;
734763
const config = config_obj.get();
735764

736-
// *Conditionally* disable the header bar when maximized,
737-
// and gtk-titlebar-hide-when-maximized is set
738-
if (self.as(gtk.Window).isMaximized() != 0 and
739-
config.@"gtk-titlebar-hide-when-maximized")
740-
{
765+
// *Conditionally* disable the header bar when maximized, and
766+
// gtk-titlebar-hide-when-maximized is set
767+
if (self.isMaximized() and config.@"gtk-titlebar-hide-when-maximized") {
741768
return false;
742769
}
743770

744-
return config.@"gtk-titlebar";
771+
return switch (config.@"gtk-titlebar-style") {
772+
// If the titlebar style is tabs never show the titlebar.
773+
.tabs => false,
774+
775+
// If the titlebar style is native show the titlebar if configured
776+
// to do so.
777+
.native => config.@"gtk-titlebar",
778+
};
745779
}
746780

747781
fn getTabsAutohide(self: *Self) bool {
748782
const priv = self.private();
749783
const config = if (priv.config) |v| v.get() else return true;
750-
return switch (config.@"window-show-tab-bar") {
751-
// Auto we always autohide... obviously.
752-
.auto => true,
753784

754-
// Always we never autohide because we always show the tab bar.
755-
.always => false,
785+
return switch (config.@"gtk-titlebar-style") {
786+
// If the titlebar style is tabs we cannot autohide.
787+
.tabs => false,
788+
789+
.native => switch (config.@"window-show-tab-bar") {
790+
// Auto we always autohide... obviously.
791+
.auto => true,
756792

757-
// Never we autohide because it doesn't actually matter,
758-
// since getTabsVisible will return false.
759-
.never => true,
793+
// Always we never autohide because we always show the tab bar.
794+
.always => false,
795+
796+
// Never we autohide because it doesn't actually matter,
797+
// since getTabsVisible will return false.
798+
.never => true,
799+
},
760800
};
761801
}
762802

763803
fn getTabsVisible(self: *Self) bool {
764804
const priv = self.private();
765805
const config = if (priv.config) |v| v.get() else return true;
766-
return switch (config.@"window-show-tab-bar") {
767-
.always, .auto => true,
768-
.never => false,
769-
};
806+
807+
switch (config.@"gtk-titlebar-style") {
808+
.tabs => {
809+
// *Conditionally* disable the tab bar when maximized, the titlebar
810+
// style is tabs, and gtk-titlebar-hide-when-maximized is set.
811+
if (self.isMaximized() and config.@"gtk-titlebar-hide-when-maximized") {
812+
return false;
813+
}
814+
815+
// If the titlebar style is tabs the tab bar must always be visible.
816+
return true;
817+
},
818+
.native => {
819+
return switch (config.@"window-show-tab-bar") {
820+
.always, .auto => true,
821+
.never => false,
822+
};
823+
},
824+
}
770825
}
771826

772827
fn getTabsWide(self: *Self) bool {
@@ -785,6 +840,12 @@ pub const Window = extern struct {
785840
};
786841
}
787842

843+
fn getTitlebarStyle(self: *Self) TitlebarStyle {
844+
const priv = self.private();
845+
const config = if (priv.config) |v| v.get() else return .native;
846+
return config.@"gtk-titlebar-style";
847+
}
848+
788849
fn propConfig(
789850
_: *adw.ApplicationWindow,
790851
_: *gobject.ParamSpec,
@@ -894,6 +955,16 @@ pub const Window = extern struct {
894955
};
895956
}
896957

958+
fn closureTitlebarStyleIsTab(
959+
_: *Self,
960+
value: TitlebarStyle,
961+
) callconv(.c) bool {
962+
return switch (value) {
963+
.native => false,
964+
.tabs => true,
965+
};
966+
}
967+
897968
//---------------------------------------------------------------
898969
// Virtual methods
899970

@@ -1662,6 +1733,7 @@ pub const Window = extern struct {
16621733
properties.@"tabs-visible".impl,
16631734
properties.@"tabs-wide".impl,
16641735
properties.@"toolbar-style".impl,
1736+
properties.@"titlebar-style".impl,
16651737
});
16661738

16671739
// Bindings
@@ -1689,6 +1761,7 @@ pub const Window = extern struct {
16891761
class.bindTemplateCallback("notify_menu_active", &propMenuActive);
16901762
class.bindTemplateCallback("notify_quick_terminal", &propQuickTerminal);
16911763
class.bindTemplateCallback("notify_scale_factor", &propScaleFactor);
1764+
class.bindTemplateCallback("titlebar_style_is_tabs", &closureTitlebarStyleIsTab);
16921765

16931766
// Virtual methods
16941767
gobject.Object.virtual_methods.dispose.implement(class, &dispose);

src/apprt/gtk-ng/ui/1.5/window.blp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,64 @@ template $GhosttyWindow: Adw.ApplicationWindow {
7979
expand-tabs: bind template.tabs-wide;
8080
view: tab_view;
8181
visible: bind template.tabs-visible;
82+
83+
[start]
84+
Gtk.Box {
85+
orientation: horizontal;
86+
visible: bind $titlebar_style_is_tabs(template.titlebar-style) as <bool>;
87+
88+
Gtk.WindowControls {
89+
side: start;
90+
}
91+
92+
Adw.SplitButton {
93+
styles [
94+
"flat",
95+
]
96+
97+
clicked => $new_tab();
98+
icon-name: "tab-new-symbolic";
99+
tooltip-text: _("New Tab");
100+
dropdown-tooltip: _("New Split");
101+
menu-model: split_menu;
102+
can-focus: false;
103+
focus-on-click: false;
104+
}
105+
}
106+
107+
[end]
108+
Gtk.Box {
109+
orientation: horizontal;
110+
visible: bind $titlebar_style_is_tabs(template.titlebar-style) as <bool>;
111+
112+
Gtk.ToggleButton {
113+
styles [
114+
"flat",
115+
]
116+
117+
icon-name: "view-grid-symbolic";
118+
tooltip-text: _("View Open Tabs");
119+
active: bind tab_overview.open bidirectional;
120+
can-focus: false;
121+
focus-on-click: false;
122+
}
123+
124+
Gtk.MenuButton {
125+
styles [
126+
"flat",
127+
]
128+
129+
notify::active => $notify_menu_active();
130+
icon-name: "open-menu-symbolic";
131+
menu-model: main_menu;
132+
tooltip-text: _("Main Menu");
133+
can-focus: false;
134+
}
135+
136+
Gtk.WindowControls {
137+
side: end;
138+
}
139+
}
82140
}
83141

84142
Box {

src/config/Config.zig

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2892,6 +2892,21 @@ else
28922892
/// more subtle border.
28932893
@"gtk-toolbar-style": GtkToolbarStyle = .raised,
28942894

2895+
/// The style of the GTK titlbar. Available values are `native` and `tabs`.
2896+
///
2897+
/// The `native` titlebar style is a traditional titlebar with a title, a few
2898+
/// buttons and window controls. A separate tab bar will show up below the
2899+
/// titlebar if you have multiple tabs open in the window.
2900+
///
2901+
/// The `tabs` titlebar merges the tab bar and the traditional titlebar.
2902+
/// This frees up vertical space on your screen if you use multiple tabs. One
2903+
/// limitation of the `tabs` titlebar is that you cannot drag the titlebar
2904+
/// by the titles any longer (as they are tab titles now). Other areas of the
2905+
/// `tabs` title bar can be used to drag the window around.
2906+
///
2907+
/// The default style is `native`.
2908+
@"gtk-titlebar-style": GtkTitlebarStyle = .native,
2909+
28952910
/// If `true` (default), then the Ghostty GTK tabs will be "wide." Wide tabs
28962911
/// are the new typical Gnome style where tabs fill their available space.
28972912
/// If you set this to `false` then tabs will only take up space they need,
@@ -6947,6 +6962,21 @@ pub const GtkToolbarStyle = enum {
69476962
@"raised-border",
69486963
};
69496964

6965+
/// See gtk-titlebar-style
6966+
pub const GtkTitlebarStyle = enum(c_int) {
6967+
native,
6968+
tabs,
6969+
6970+
pub const getGObjectType = switch (build_config.app_runtime) {
6971+
.gtk, .@"gtk-ng" => @import("gobject").ext.defineEnum(
6972+
GtkTitlebarStyle,
6973+
.{ .name = "GhosttyGtkTitlebarStyle" },
6974+
),
6975+
6976+
.none => void,
6977+
};
6978+
};
6979+
69506980
/// See app-notifications
69516981
pub const AppNotifications = packed struct {
69526982
@"clipboard-copy": bool = true,

0 commit comments

Comments
 (0)