Update Platform Controls (#13751)

Continuing from #13597, this PR refactors platform controls to extract a
generic set of platform controls that can be used for any platform that
does not define it's own/we don't use the system ones.

In the future, these controls will likely be used as a fallback on
windows as well when the windows icon font isn't available.

Release Notes:

- Added updated window controls on Linux
This commit is contained in:
Nate Butler 2024-07-02 16:15:59 -04:00 committed by GitHub
parent eb845ee201
commit 7db68547fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 272 additions and 149 deletions

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5 4.5L4.5 11.5" stroke="black" stroke-linecap="square" stroke-linejoin="round"/>
<path d="M4.5 4.5L11.5 11.5" stroke="black" stroke-linecap="square" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 291 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5 4.5H4.5V11.5H11.5V4.5Z" stroke="#FBF1C7"/>
</svg>

After

Width:  |  Height:  |  Size: 161 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 8H12" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 138 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 6.5H3.5V12.5H9.5V6.5Z" stroke="#FBF1C7"/>
<path d="M10 8.5L12.5 8.5L12.5 3.5L7.5 3.5L7.5 6" stroke="#FBF1C7"/>
</svg>

After

Width:  |  Height:  |  Size: 228 B

View File

@ -1,3 +1,4 @@
pub mod platform_generic;
pub mod platform_linux;
pub mod platform_mac;
pub mod platform_windows;

View File

@ -0,0 +1,47 @@
use gpui::{prelude::*, Action};
use ui::prelude::*;
use crate::window_controls::{WindowControl, WindowControlType};
#[derive(IntoElement)]
pub struct GenericWindowControls {
close_window_action: Box<dyn Action>,
}
impl GenericWindowControls {
pub fn new(close_action: Box<dyn Action>) -> Self {
Self {
close_window_action: close_action,
}
}
}
impl RenderOnce for GenericWindowControls {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
h_flex()
.id("generic-window-controls")
.px_3()
.gap_1p5()
.child(WindowControl::new(
"minimize",
WindowControlType::Minimize,
cx,
))
.child(WindowControl::new(
"maximize-or-restore",
if cx.is_maximized() {
WindowControlType::Restore
} else {
WindowControlType::Maximize
},
cx,
))
.child(WindowControl::new_close(
"close",
WindowControlType::Close,
self.close_window_action,
cx,
))
}
}

View File

@ -1,145 +1,24 @@
use gpui::{prelude::*, Action, Rgba, WindowAppearance};
use gpui::{prelude::*, Action};
use ui::prelude::*;
use super::platform_generic::GenericWindowControls;
#[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 {
pub fn new(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())
}
}
})
GenericWindowControls::new(self.close_window_action.boxed_clone()).into_any_element()
}
}

View File

@ -1,6 +1,7 @@
mod call_controls;
mod collab;
mod platforms;
mod window_controls;
use crate::platforms::{platform_linux, platform_mac, platform_windows};
use auto_update::AutoUpdateStatus;
@ -70,9 +71,10 @@ impl Render for TitleBar {
let close_action = Box::new(workspace::CloseWindow);
let platform_supported = cfg!(target_os = "macos");
let height = Self::height(cx);
h_flex()
let mut title_bar = h_flex()
.id("titlebar")
.w_full()
.pt(Self::top_padding(cx))
@ -371,28 +373,34 @@ impl Render for TitleBar {
}
}),
)
)
.when(
self.platform_style == PlatformStyle::Windows && !cx.is_fullscreen(),
|title_bar| title_bar.child(platform_windows::WindowsWindowControls::new(height)),
)
.when(
self.platform_style == PlatformStyle::Linux
&& !cx.is_fullscreen()
&& cx.should_render_window_controls(),
|title_bar| {
title_bar
.child(platform_linux::LinuxWindowControls::new(height, close_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();
}
})
},
)
);
// Windows Window Controls
title_bar = title_bar.when(
self.platform_style == PlatformStyle::Windows && !cx.is_fullscreen(),
|title_bar| title_bar.child(platform_windows::WindowsWindowControls::new(height)),
);
// Linux Window Controls
title_bar = title_bar.when(
self.platform_style == PlatformStyle::Linux
&& !cx.is_fullscreen()
&& cx.should_render_window_controls(),
|title_bar| {
title_bar
.child(platform_linux::LinuxWindowControls::new(close_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();
}
})
},
);
title_bar
}
}

View File

@ -0,0 +1,164 @@
use gpui::{svg, Action, Hsla};
use ui::prelude::*;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum WindowControlType {
Minimize,
Restore,
Maximize,
Close,
}
impl WindowControlType {
/// Returns the icon name for the window control type.
///
/// Will take a [PlatformStyle] in the future to return a different
/// icon name based on the platform.
pub fn icon(&self) -> IconName {
match self {
WindowControlType::Minimize => IconName::GenericMinimize,
WindowControlType::Restore => IconName::GenericRestore,
WindowControlType::Maximize => IconName::GenericMaximize,
WindowControlType::Close => IconName::GenericClose,
}
}
}
#[allow(unused)]
pub struct WindowControlStyle {
background: Hsla,
background_hover: Hsla,
icon: Hsla,
icon_hover: Hsla,
}
impl WindowControlStyle {
pub fn default(cx: &WindowContext) -> Self {
let colors = cx.theme().colors();
Self {
background: colors.ghost_element_background,
background_hover: colors.ghost_element_background,
icon: colors.icon,
icon_hover: colors.icon_muted,
}
}
#[allow(unused)]
/// Sets the background color of the control.
pub fn background(mut self, color: impl Into<Hsla>) -> Self {
self.background = color.into();
self
}
#[allow(unused)]
/// Sets the background color of the control when hovered.
pub fn background_hover(mut self, color: impl Into<Hsla>) -> Self {
self.background_hover = color.into();
self
}
#[allow(unused)]
/// Sets the color of the icon.
pub fn icon(mut self, color: impl Into<Hsla>) -> Self {
self.icon = color.into();
self
}
#[allow(unused)]
/// Sets the color of the icon when hovered.
pub fn icon_hover(mut self, color: impl Into<Hsla>) -> Self {
self.icon_hover = color.into();
self
}
}
#[derive(IntoElement)]
pub struct WindowControl {
id: ElementId,
icon: WindowControlType,
style: WindowControlStyle,
close_action: Option<Box<dyn Action>>,
}
impl WindowControl {
pub fn new(id: impl Into<ElementId>, icon: WindowControlType, cx: &WindowContext) -> Self {
let style = WindowControlStyle::default(cx);
Self {
id: id.into(),
icon,
style,
close_action: None,
}
}
pub fn new_close(
id: impl Into<ElementId>,
icon: WindowControlType,
close_action: Box<dyn Action>,
cx: &WindowContext,
) -> Self {
let style = WindowControlStyle::default(cx);
Self {
id: id.into(),
icon,
style,
close_action: Some(close_action.boxed_clone()),
}
}
#[allow(unused)]
pub fn custom_style(
id: impl Into<ElementId>,
icon: WindowControlType,
style: WindowControlStyle,
) -> Self {
Self {
id: id.into(),
icon,
style,
close_action: None,
}
}
}
impl RenderOnce for WindowControl {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let icon = svg()
.size_5()
.flex_none()
.path(self.icon.icon().path())
.text_color(self.style.icon)
.group_hover("", |this| this.text_color(self.style.icon_hover));
h_flex()
.id(self.id)
.group("")
.cursor_pointer()
.justify_center()
.content_center()
.rounded_md()
.w_5()
.h_5()
.hover(|this| this.bg(self.style.background_hover))
.active(|this| this.bg(self.style.background_hover))
.child(icon)
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_click(move |_, cx| {
cx.stop_propagation();
match self.icon {
WindowControlType::Minimize => cx.minimize_window(),
WindowControlType::Restore => cx.zoom_window(),
WindowControlType::Maximize => cx.zoom_window(),
WindowControlType::Close => cx.dispatch_action(
self.close_action
.as_ref()
.expect("Use WindowControl::new_close() for close control.")
.boxed_clone(),
),
}
})
}
}

View File

@ -146,6 +146,10 @@ pub enum IconName {
FontSize,
FontWeight,
Github,
GenericMinimize,
GenericMaximize,
GenericClose,
GenericRestore,
Hash,
HistoryRerun,
Indicator,
@ -290,6 +294,10 @@ impl IconName {
IconName::FontSize => "icons/font_size.svg",
IconName::FontWeight => "icons/font_weight.svg",
IconName::Github => "icons/github.svg",
IconName::GenericMinimize => "icons/generic_minimize.svg",
IconName::GenericMaximize => "icons/generic_maximize.svg",
IconName::GenericClose => "icons/generic_close.svg",
IconName::GenericRestore => "icons/generic_restore.svg",
IconName::Hash => "icons/hash.svg",
IconName::HistoryRerun => "icons/history_rerun.svg",
IconName::Indicator => "icons/indicator.svg",

View File

@ -23,6 +23,7 @@ pub enum Color {
Selected,
Success,
Warning,
Custom(Hsla),
}
impl Color {
@ -46,6 +47,7 @@ impl Color {
Color::Selected => cx.theme().colors().text_accent,
Color::Success => cx.theme().status().success,
Color::Warning => cx.theme().status().warning,
Color::Custom(color) => *color,
}
}
}