wayland: Window controls and drag (#11525)

Based on https://github.com/zed-industries/zed/pull/11046

- Partially fixes #10346 
- Fixes https://github.com/zed-industries/zed/issues/9964

## Features
Window buttons

![image](https://github.com/zed-industries/zed/assets/71973804/1b7e0504-3925-45ba-90b5-5adb55e0d739)

Window drag

![image](https://github.com/zed-industries/zed/assets/71973804/9c509a37-e5a5-484c-9f80-c722aeee4380)

Native window context menu

![image](https://github.com/zed-industries/zed/assets/71973804/048ecf52-e277-49bb-a106-91cad226fd8a)

### Limitations

- No resizing
- Wayland only (though X11 always has window decorations)

### Technical

This PR adds three APIs to gpui.

1. `show_window_menu`: Triggers the native title bar context menu.
2. `start_system_move`: Tells the compositor to start dragging the
window.
3. `should_render_window_controls`: Whether the compositor doesn't
support server side decorations.

These APIs have only been implemented for Wayland, but they should be
portable to other platforms.

Release Notes:

- N/A

---------

Co-authored-by: Akilan Elango <akilan1997@gmail.com>
This commit is contained in:
apricotbucket28 2024-05-14 13:44:55 -03:00 committed by GitHub
parent db89353193
commit d1ee2d0749
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 277 additions and 11 deletions

View file

@ -1,4 +1,4 @@
use gpui::Render;
use gpui::{NoAction, Render};
use story::{StoryContainer, StoryItem, StorySection};
use crate::{prelude::*, PlatformStyle, TitleBar};
@ -19,7 +19,7 @@ impl Render for TitleBarStory {
StorySection::new().child(
StoryItem::new(
"Default (macOS)",
TitleBar::new("macos")
TitleBar::new("macos", Box::new(NoAction))
.platform_style(PlatformStyle::Mac)
.map(add_sample_children),
)
@ -31,7 +31,7 @@ impl Render for TitleBarStory {
StorySection::new().child(
StoryItem::new(
"Default (Linux)",
TitleBar::new("linux")
TitleBar::new("linux", Box::new(NoAction))
.platform_style(PlatformStyle::Linux)
.map(add_sample_children),
)
@ -43,7 +43,7 @@ impl Render for TitleBarStory {
StorySection::new().child(
StoryItem::new(
"Default (Windows)",
TitleBar::new("windows")
TitleBar::new("windows", Box::new(NoAction))
.platform_style(PlatformStyle::Windows)
.map(add_sample_children),
)

View file

@ -1,3 +1,4 @@
mod linux_window_controls;
mod title_bar;
mod windows_window_controls;

View file

@ -0,0 +1,145 @@
use gpui::{prelude::*, Action, Rgba, WindowAppearance};
use crate::prelude::*;
#[derive(IntoElement)]
pub struct LinuxWindowControls {
button_height: Pixels,
close_window_action: Box<dyn Action>,
}
impl LinuxWindowControls {
pub fn new(button_height: Pixels, close_window_action: Box<dyn Action>) -> Self {
Self {
button_height,
close_window_action,
}
}
}
impl RenderOnce for LinuxWindowControls {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let close_button_hover_color = Rgba {
r: 232.0 / 255.0,
g: 17.0 / 255.0,
b: 32.0 / 255.0,
a: 1.0,
};
let button_hover_color = match cx.appearance() {
WindowAppearance::Light | WindowAppearance::VibrantLight => Rgba {
r: 0.1,
g: 0.1,
b: 0.1,
a: 0.2,
},
WindowAppearance::Dark | WindowAppearance::VibrantDark => Rgba {
r: 0.9,
g: 0.9,
b: 0.9,
a: 0.1,
},
};
div()
.id("linux-window-controls")
.flex()
.flex_row()
.justify_center()
.content_stretch()
.max_h(self.button_height)
.min_h(self.button_height)
.child(TitlebarButton::new(
"minimize",
TitlebarButtonType::Minimize,
button_hover_color,
self.close_window_action.boxed_clone(),
))
.child(TitlebarButton::new(
"maximize-or-restore",
if cx.is_maximized() {
TitlebarButtonType::Restore
} else {
TitlebarButtonType::Maximize
},
button_hover_color,
self.close_window_action.boxed_clone(),
))
.child(TitlebarButton::new(
"close",
TitlebarButtonType::Close,
close_button_hover_color,
self.close_window_action,
))
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
enum TitlebarButtonType {
Minimize,
Restore,
Maximize,
Close,
}
#[derive(IntoElement)]
struct TitlebarButton {
id: ElementId,
icon: TitlebarButtonType,
hover_background_color: Rgba,
close_window_action: Box<dyn Action>,
}
impl TitlebarButton {
pub fn new(
id: impl Into<ElementId>,
icon: TitlebarButtonType,
hover_background_color: Rgba,
close_window_action: Box<dyn Action>,
) -> Self {
Self {
id: id.into(),
icon,
hover_background_color,
close_window_action,
}
}
}
impl RenderOnce for TitlebarButton {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let width = px(36.);
h_flex()
.id(self.id)
.justify_center()
.content_center()
.w(width)
.h_full()
.hover(|style| style.bg(self.hover_background_color))
.active(|style| {
let mut active_color = self.hover_background_color;
active_color.a *= 0.2;
style.bg(active_color)
})
.child(Icon::new(match self.icon {
TitlebarButtonType::Minimize => IconName::Dash,
TitlebarButtonType::Restore => IconName::Minimize,
TitlebarButtonType::Maximize => IconName::Maximize,
TitlebarButtonType::Close => IconName::Close,
}))
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_click(move |_, cx| {
cx.stop_propagation();
match self.icon {
TitlebarButtonType::Minimize => cx.minimize_window(),
TitlebarButtonType::Restore => cx.zoom_window(),
TitlebarButtonType::Maximize => cx.zoom_window(),
TitlebarButtonType::Close => {
cx.dispatch_action(self.close_window_action.boxed_clone())
}
}
})
}
}

View file

@ -1,6 +1,7 @@
use gpui::{AnyElement, Interactivity, Stateful};
use gpui::{Action, AnyElement, Interactivity, Stateful};
use smallvec::SmallVec;
use crate::components::title_bar::linux_window_controls::LinuxWindowControls;
use crate::components::title_bar::windows_window_controls::WindowsWindowControls;
use crate::prelude::*;
@ -9,6 +10,7 @@ pub struct TitleBar {
platform_style: PlatformStyle,
content: Stateful<Div>,
children: SmallVec<[AnyElement; 2]>,
close_window_action: Box<dyn Action>,
}
impl TitleBar {
@ -45,11 +47,12 @@ impl TitleBar {
}
}
pub fn new(id: impl Into<ElementId>) -> Self {
pub fn new(id: impl Into<ElementId>, close_window_action: Box<dyn Action>) -> Self {
Self {
platform_style: PlatformStyle::platform(),
content: div().id(id.into()),
children: SmallVec::new(),
close_window_action,
}
}
@ -111,5 +114,22 @@ impl RenderOnce for TitleBar {
self.platform_style == PlatformStyle::Windows && !cx.is_fullscreen(),
|title_bar| title_bar.child(WindowsWindowControls::new(height)),
)
.when(
self.platform_style == PlatformStyle::Linux
&& !cx.is_fullscreen()
&& cx.should_render_window_controls(),
|title_bar| {
title_bar
.child(LinuxWindowControls::new(height, self.close_window_action))
.on_mouse_down(gpui::MouseButton::Right, move |ev, cx| {
cx.show_window_menu(ev.position)
})
.on_mouse_move(move |ev, cx| {
if ev.dragging() {
cx.start_system_move();
}
})
},
)
}
}