mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
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:
parent
eb845ee201
commit
7db68547fa
4
assets/icons/generic_close.svg
Normal file
4
assets/icons/generic_close.svg
Normal 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 |
3
assets/icons/generic_maximize.svg
Normal file
3
assets/icons/generic_maximize.svg
Normal 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 |
3
assets/icons/generic_minimize.svg
Normal file
3
assets/icons/generic_minimize.svg
Normal 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 |
4
assets/icons/generic_restore.svg
Normal file
4
assets/icons/generic_restore.svg
Normal 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 |
@ -1,3 +1,4 @@
|
||||
pub mod platform_generic;
|
||||
pub mod platform_linux;
|
||||
pub mod platform_mac;
|
||||
pub mod platform_windows;
|
||||
|
47
crates/title_bar/src/platforms/platform_generic.rs
Normal file
47
crates/title_bar/src/platforms/platform_generic.rs
Normal 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,
|
||||
))
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
164
crates/title_bar/src/window_controls.rs
Normal file
164
crates/title_bar/src/window_controls.rs
Normal 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(),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user