Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/title_bar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ workspace-hack.workspace = true
[target.'cfg(windows)'.dependencies]
windows.workspace = true

[target.'cfg(target_os = "linux")'.dependencies]
dconf = "0.1.1"

[dev-dependencies]
call = { workspace = true, features = ["test-support"] }
client = { workspace = true, features = ["test-support"] }
Expand Down
142 changes: 114 additions & 28 deletions crates/title_bar/src/platform_title_bar.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::WindowControlsPosition;
use gpui::{
AnyElement, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement, MouseButton,
ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
Expand All @@ -17,19 +18,40 @@ pub struct PlatformTitleBar {
children: SmallVec<[AnyElement; 2]>,
should_move: bool,
system_window_tabs: Entity<SystemWindowTabs>,
window_controls_position: WindowControlsPosition,
}

impl PlatformTitleBar {
pub fn new(id: impl Into<ElementId>, cx: &mut Context<Self>) -> Self {
let platform_style = PlatformStyle::platform();
let system_window_tabs = cx.new(|_cx| SystemWindowTabs::new());
let window_controls_position = if cfg!(target_os = "linux") {
if let Ok(Some(value)) =
dconf::read_string("/org/gnome/desktop/wm/preferences/button-layout")
{
// GNOME Tweaks has settings to put the window buttons on left or right,
// as well as hide minimize/maximize buttons. Regardless of the state of
// the minimize/maximize toggles, whenever the buttons are on the left,
// the dconf value string ends with ":icon".
if value.ends_with(":icon") {
Comment on lines +32 to +36
Copy link
Contributor Author

@Be-ing Be-ing Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, GNOME Tweaks doesn't actually use this substring. That came from KDE's settings 🫠 https://bugs.kde.org/show_bug.cgi?id=478266#c3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And COSMIC writes just a : with neither icon nor appmenu 🫠 🫠

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to parse the string as it is. Not try and assume things about it to make implementing it easier.

WindowControlsPosition::Left
} else {
WindowControlsPosition::Right
}
} else {
WindowControlsPosition::Right
}
} else {
WindowControlsPosition::Right
};

Self {
id: id.into(),
platform_style,
children: SmallVec::new(),
should_move: false,
system_window_tabs,
window_controls_position,
}
}

Expand Down Expand Up @@ -110,6 +132,16 @@ impl Render for PlatformTitleBar {
.items_center()
.justify_between()
.overflow_x_hidden()
.map(|this| {
if self.platform_style == PlatformStyle::Linux {
match self.window_controls_position {
WindowControlsPosition::Left => this.justify_start(),
WindowControlsPosition::Right => this.justify_between(),
}
} else {
this.justify_between()
}
})
.w_full()
// Note: On Windows the title bar behavior is handled by the platform implementation.
.when(self.platform_style == PlatformStyle::Mac, |this| {
Expand All @@ -133,35 +165,89 @@ impl Render for PlatformTitleBar {
PlatformStyle::Mac => title_bar,
PlatformStyle::Linux => {
if matches!(decorations, Decorations::Client { .. }) {
title_bar
.child(platform_linux::LinuxWindowControls::new(close_action))
.when(supported_controls.window_menu, |titlebar| {
titlebar
.on_mouse_down(MouseButton::Right, move |ev, window, _| {
window.show_window_menu(ev.position)
match self.window_controls_position {
WindowControlsPosition::Left => {
// macOS style: controls at the beginning of the title bar
h_flex()
.w_full()
.bg(titlebar_color)
.child(platform_linux::LinuxWindowControls::new(
close_action,
WindowControlsPosition::Left,
))
.child(title_bar)
.when(supported_controls.window_menu, |titlebar| {
titlebar.on_mouse_down(
MouseButton::Right,
move |ev, window, _| {
window.show_window_menu(ev.position)
},
)
})
.on_mouse_move(cx.listener(move |this, _ev, window, _| {
if this.should_move {
this.should_move = false;
window.start_window_move();
}
}))
.on_mouse_down_out(cx.listener(
move |this, _ev, _window, _cx| {
this.should_move = false;
},
))
.on_mouse_up(
MouseButton::Left,
cx.listener(move |this, _ev, _window, _cx| {
this.should_move = false;
}),
)
.on_mouse_down(
MouseButton::Left,
cx.listener(move |this, _ev, _window, _cx| {
this.should_move = true;
}),
)
}
WindowControlsPosition::Right => {
// Windows style: controls at the end of the titlebar
title_bar
.child(platform_linux::LinuxWindowControls::new(
close_action,
WindowControlsPosition::Right,
))
.when(supported_controls.window_menu, |titlebar| {
titlebar.on_mouse_down(
MouseButton::Right,
move |ev, window, _| {
window.show_window_menu(ev.position)
},
)
})
})
.on_mouse_move(cx.listener(move |this, _ev, window, _| {
if this.should_move {
this.should_move = false;
window.start_window_move();
}
}))
.on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
this.should_move = false;
}))
.on_mouse_up(
MouseButton::Left,
cx.listener(move |this, _ev, _window, _cx| {
this.should_move = false;
}),
)
.on_mouse_down(
MouseButton::Left,
cx.listener(move |this, _ev, _window, _cx| {
this.should_move = true;
}),
)
.on_mouse_move(cx.listener(move |this, _ev, window, _| {
if this.should_move {
this.should_move = false;
window.start_window_move();
}
}))
.on_mouse_down_out(cx.listener(
move |this, _ev, _window, _cx| {
this.should_move = false;
},
))
.on_mouse_up(
MouseButton::Left,
cx.listener(move |this, _ev, _window, _cx| {
this.should_move = false;
}),
)
.on_mouse_down(
MouseButton::Left,
cx.listener(move |this, _ev, _window, _cx| {
this.should_move = true;
}),
)
}
}
} else {
title_bar
}
Expand Down
Loading
Loading