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  Window drag  Native window context menu  ### 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:
parent
db89353193
commit
d1ee2d0749
13 changed files with 277 additions and 11 deletions
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
mod linux_window_controls;
|
||||
mod title_bar;
|
||||
mod windows_window_controls;
|
||||
|
||||
|
|
145
crates/ui/src/components/title_bar/linux_window_controls.rs
Normal file
145
crates/ui/src/components/title_bar/linux_window_controls.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue