diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 9ab4c94ff1..9c210871da 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -14,7 +14,7 @@ use std::sync::Arc; use theme::ActiveTheme; use ui::{ h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, - ButtonStyle, ContextMenu, Icon, IconButton, IconName, PlatformTitlebar, TintColor, Tooltip, + ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, TitleBar, Tooltip, }; use util::ResultExt; use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; @@ -58,8 +58,7 @@ impl Render for CollabTitlebarItem { let project_id = self.project.read(cx).remote_id(); let workspace = self.workspace.upgrade(); - PlatformTitlebar::new("collab-titlebar") - .background(cx.theme().colors().title_bar_background) + TitleBar::new("collab-titlebar") // note: on windows titlebar behaviour is handled by the platform implementation .when(cfg!(not(windows)), |this| { this.on_click(|event, cx| { diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index bbda1119ec..c238542478 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -29,10 +29,10 @@ pub enum ComponentStory { ListHeader, ListItem, OverflowScroll, - PlatformTitlebar, Scroll, Tab, TabBar, + TitleBar, ToggleButton, Text, ViewportUnits, @@ -61,11 +61,11 @@ impl ComponentStory { Self::ListHeader => cx.new_view(|_| ui::ListHeaderStory).into(), Self::ListItem => cx.new_view(|_| ui::ListItemStory).into(), Self::OverflowScroll => cx.new_view(|_| crate::stories::OverflowScrollStory).into(), - Self::PlatformTitlebar => cx.new_view(|_| ui::PlatformTitlebarStory).into(), Self::Scroll => ScrollStory::view(cx).into(), Self::Text => TextStory::view(cx).into(), Self::Tab => cx.new_view(|_| ui::TabStory).into(), Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(), + Self::TitleBar => cx.new_view(|_| ui::TitleBarStory).into(), Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(), Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(), Self::Picker => PickerStory::new(cx).into(), diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs index c78774f2b8..b3fc5dd2ee 100644 --- a/crates/ui/src/components.rs +++ b/crates/ui/src/components.rs @@ -9,13 +9,13 @@ mod indicator; mod keybinding; mod label; mod list; -mod platform_titlebar; mod popover; mod popover_menu; mod right_click_menu; mod stack; mod tab; mod tab_bar; +mod title_bar; mod tooltip; #[cfg(feature = "stories")] @@ -32,13 +32,13 @@ pub use indicator::*; pub use keybinding::*; pub use label::*; pub use list::*; -pub use platform_titlebar::*; pub use popover::*; pub use popover_menu::*; pub use right_click_menu::*; pub use stack::*; pub use tab::*; pub use tab_bar::*; +pub use title_bar::*; pub use tooltip::*; #[cfg(feature = "stories")] diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index e5ca6f62f7..d595c6c4d0 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -1,30 +1,6 @@ use crate::{h_flex, prelude::*, Icon, IconName, IconSize}; use gpui::{relative, Action, FocusHandle, IntoElement, Keystroke}; -/// The way a [`KeyBinding`] should be displayed. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum KeyBindingDisplay { - /// Display in macOS style. - Mac, - /// Display in Linux style. - Linux, - /// Display in Windows style. - Windows, -} - -impl KeyBindingDisplay { - /// Returns the [`KeyBindingDisplay`] for the current platform. - pub const fn platform() -> Self { - if cfg!(target_os = "linux") { - KeyBindingDisplay::Linux - } else if cfg!(target_os = "windows") { - KeyBindingDisplay::Windows - } else { - KeyBindingDisplay::Mac - } - } -} - #[derive(IntoElement, Clone)] pub struct KeyBinding { /// A keybinding consists of a key and a set of modifier keys. @@ -33,8 +9,8 @@ pub struct KeyBinding { /// This should always contain at least one element. key_binding: gpui::KeyBinding, - /// How keybindings should be displayed. - display: KeyBindingDisplay, + /// The [`PlatformStyle`] to use when displaying this keybinding. + platform_style: PlatformStyle, } impl KeyBinding { @@ -76,13 +52,13 @@ impl KeyBinding { pub fn new(key_binding: gpui::KeyBinding) -> Self { Self { key_binding, - display: KeyBindingDisplay::platform(), + platform_style: PlatformStyle::platform(), } } - /// Sets how this [`KeyBinding`] should be displayed. - pub fn display(mut self, display: KeyBindingDisplay) -> Self { - self.display = display; + /// Sets the [`PlatformStyle`] for this [`KeyBinding`]. + pub fn platform_style(mut self, platform_style: PlatformStyle) -> Self { + self.platform_style = platform_style; self } } @@ -97,43 +73,49 @@ impl RenderOnce for KeyBinding { h_flex() .flex_none() - .map(|el| match self.display { - KeyBindingDisplay::Mac => el.gap_0p5(), - KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => el, + .map(|el| match self.platform_style { + PlatformStyle::Mac => el.gap_0p5(), + PlatformStyle::Linux | PlatformStyle::Windows => el, }) .p_0p5() .rounded_sm() .text_color(cx.theme().colors().text_muted) - .when(keystroke.modifiers.function, |el| match self.display { - KeyBindingDisplay::Mac => el.child(Key::new("fn")), - KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => { - el.child(Key::new("Fn")).child(Key::new("+")) + .when(keystroke.modifiers.function, |el| { + match self.platform_style { + PlatformStyle::Mac => el.child(Key::new("fn")), + PlatformStyle::Linux | PlatformStyle::Windows => { + el.child(Key::new("Fn")).child(Key::new("+")) + } } }) - .when(keystroke.modifiers.control, |el| match self.display { - KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Control)), - KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => { - el.child(Key::new("Ctrl")).child(Key::new("+")) + .when(keystroke.modifiers.control, |el| { + match self.platform_style { + PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Control)), + PlatformStyle::Linux | PlatformStyle::Windows => { + el.child(Key::new("Ctrl")).child(Key::new("+")) + } } }) - .when(keystroke.modifiers.alt, |el| match self.display { - KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Option)), - KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => { + .when(keystroke.modifiers.alt, |el| match self.platform_style { + PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Option)), + PlatformStyle::Linux | PlatformStyle::Windows => { el.child(Key::new("Alt")).child(Key::new("+")) } }) - .when(keystroke.modifiers.command, |el| match self.display { - KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Command)), - KeyBindingDisplay::Linux => { - el.child(Key::new("Super")).child(Key::new("+")) - } - KeyBindingDisplay::Windows => { - el.child(Key::new("Win")).child(Key::new("+")) + .when(keystroke.modifiers.command, |el| { + match self.platform_style { + PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Command)), + PlatformStyle::Linux => { + el.child(Key::new("Super")).child(Key::new("+")) + } + PlatformStyle::Windows => { + el.child(Key::new("Win")).child(Key::new("+")) + } } }) - .when(keystroke.modifiers.shift, |el| match self.display { - KeyBindingDisplay::Mac => el.child(KeyIcon::new(IconName::Shift)), - KeyBindingDisplay::Linux | KeyBindingDisplay::Windows => { + .when(keystroke.modifiers.shift, |el| match self.platform_style { + PlatformStyle::Mac => el.child(KeyIcon::new(IconName::Shift)), + PlatformStyle::Linux | PlatformStyle::Windows => { el.child(Key::new("Shift")).child(Key::new("+")) } }) diff --git a/crates/ui/src/components/platform_titlebar.rs b/crates/ui/src/components/platform_titlebar.rs deleted file mode 100644 index 9d889d01df..0000000000 --- a/crates/ui/src/components/platform_titlebar.rs +++ /dev/null @@ -1,215 +0,0 @@ -use gpui::{transparent_black, AnyElement, Fill, Interactivity, Rgba, Stateful, WindowAppearance}; -use smallvec::SmallVec; - -use crate::prelude::*; - -pub enum PlatformStyle { - Linux, - Windows, - MacOs, -} - -pub fn titlebar_height(cx: &mut WindowContext) -> Pixels { - (1.75 * cx.rem_size()).max(px(32.)) -} - -impl PlatformStyle { - pub fn platform() -> Self { - if cfg!(target_os = "windows") { - Self::Windows - } else if cfg!(target_os = "macos") { - Self::MacOs - } else { - Self::Linux - } - } - - pub fn windows(&self) -> bool { - matches!(self, Self::Windows) - } - - pub fn macos(&self) -> bool { - matches!(self, Self::MacOs) - } -} - -#[derive(IntoElement)] -pub struct PlatformTitlebar { - platform: PlatformStyle, - background: Fill, - content: Stateful
, - children: SmallVec<[AnyElement; 2]>, -} - -impl PlatformTitlebar { - pub fn new(id: impl Into) -> Self { - Self { - platform: PlatformStyle::platform(), - background: transparent_black().into(), - content: div().id(id.into()), - children: SmallVec::new(), - } - } - - /// Sets the platform style. - pub fn platform_style(mut self, style: PlatformStyle) -> Self { - self.platform = style; - self - } - - /// Sets the background color of the titlebar. - pub fn background(mut self, fill: F) -> Self - where - F: Into, - Self: Sized, - { - self.background = fill.into(); - self - } - - fn top_padding(&self, cx: &WindowContext) -> Pixels { - if self.platform.windows() && cx.is_maximized() { - // todo(windows): get padding from win32 api, need HWND from window context somehow - // should be GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) * 2 - px(8.0) - } else { - px(0.0) - } - } - - fn windows_caption_button_width(_cx: &WindowContext) -> Pixels { - // todo(windows): get padding from win32 api, need HWND from window context somehow - // should be GetSystemMetricsForDpi(SM_CXSIZE, dpi) - px(36.0) - } - - fn render_window_controls_right(&self, cx: &mut WindowContext) -> impl Element { - if !self.platform.windows() { - return div().id("caption-buttons-windows"); - } - - let button_height = titlebar_height(cx) - self.top_padding(cx); - 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, - }, - }; - - fn windows_caption_button( - id: &'static str, - icon_text: &'static str, - hover_color: Rgba, - cx: &WindowContext, - ) -> Stateful
{ - let mut active_color = hover_color; - active_color.a *= 0.2; - h_flex() - .id(id) - .h_full() - .justify_center() - .content_center() - .items_center() - .w(PlatformTitlebar::windows_caption_button_width(cx)) - .hover(|style| style.bg(hover_color)) - .active(|style| style.bg(active_color)) - .child(icon_text) - } - - const MINIMIZE_ICON: &str = "\u{e921}"; - const RESTORE_ICON: &str = "\u{e923}"; - const MAXIMIZE_ICON: &str = "\u{e922}"; - const CLOSE_ICON: &str = "\u{e8bb}"; - - div() - .id("caption-buttons-windows") - .flex() - .flex_row() - .justify_center() - .content_stretch() - .max_h(button_height) - .min_h(button_height) - .font("Segoe Fluent Icons") - .text_size(px(10.0)) - .children(vec![ - windows_caption_button("minimize", MINIMIZE_ICON, button_hover_color, cx), - windows_caption_button( - "maximize", - if cx.is_maximized() { - RESTORE_ICON - } else { - MAXIMIZE_ICON - }, - button_hover_color, - cx, - ), - windows_caption_button("close", CLOSE_ICON, close_button_hover_color, cx), - ]) - } -} - -impl RenderOnce for PlatformTitlebar { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - let titlebar_height = titlebar_height(cx); - let titlebar_top_padding = self.top_padding(cx); - let window_controls_right = self.render_window_controls_right(cx); - - h_flex() - .id("titlebar") - .w_full() - .pt(titlebar_top_padding) - .h(titlebar_height) - .map(|this| { - if cx.is_fullscreen() { - this.pl_2() - } else if self.platform.macos() { - // Use pixels here instead of a rem-based size because the macOS traffic - // lights are a static size, and don't scale with the rest of the UI. - this.pl(px(80.)) - } else { - this.pl_2() - } - }) - .bg(self.background) - .content_stretch() - .child( - self.content - .id("titlebar-content") - .flex() - .flex_row() - .justify_between() - .w_full() - .children(self.children), - ) - .child(window_controls_right) - } -} - -impl InteractiveElement for PlatformTitlebar { - fn interactivity(&mut self) -> &mut Interactivity { - self.content.interactivity() - } -} - -impl StatefulInteractiveElement for PlatformTitlebar {} - -impl ParentElement for PlatformTitlebar { - fn extend(&mut self, elements: impl Iterator) { - self.children.extend(elements) - } -} diff --git a/crates/ui/src/components/stories.rs b/crates/ui/src/components/stories.rs index 8764b39698..eb84ed3ccb 100644 --- a/crates/ui/src/components/stories.rs +++ b/crates/ui/src/components/stories.rs @@ -10,9 +10,9 @@ mod label; mod list; mod list_header; mod list_item; -mod platform_titlebar; mod tab; mod tab_bar; +mod title_bar; mod toggle_button; pub use avatar::*; @@ -27,7 +27,7 @@ pub use label::*; pub use list::*; pub use list_header::*; pub use list_item::*; -pub use platform_titlebar::*; pub use tab::*; pub use tab_bar::*; +pub use title_bar::*; pub use toggle_button::*; diff --git a/crates/ui/src/components/stories/keybinding.rs b/crates/ui/src/components/stories/keybinding.rs index 48be6ad82e..e6901887e4 100644 --- a/crates/ui/src/components/stories/keybinding.rs +++ b/crates/ui/src/components/stories/keybinding.rs @@ -3,7 +3,7 @@ use gpui::Render; use itertools::Itertools; use story::{Story, StoryContainer}; -use crate::{prelude::*, KeyBinding, KeyBindingDisplay}; +use crate::{prelude::*, KeyBinding}; pub struct KeybindingStory; @@ -59,18 +59,22 @@ impl Render for KeybindingStory { .child(KeyBinding::new(binding("ctrl-a shift-z"))) .child(KeyBinding::new(binding("fn-s"))) .child(Story::label("Single Key with All Modifiers (Linux)")) - .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")).display(KeyBindingDisplay::Linux)) + .child( + KeyBinding::new(binding("ctrl-alt-cmd-shift-z")).platform_style(PlatformStyle::Linux), + ) .child(Story::label("Chord (Linux)")) - .child(KeyBinding::new(binding("a z")).display(KeyBindingDisplay::Linux)) + .child(KeyBinding::new(binding("a z")).platform_style(PlatformStyle::Linux)) .child(Story::label("Chord with Modifier (Linux)")) - .child(KeyBinding::new(binding("ctrl-a shift-z")).display(KeyBindingDisplay::Linux)) - .child(KeyBinding::new(binding("fn-s")).display(KeyBindingDisplay::Linux)) + .child(KeyBinding::new(binding("ctrl-a shift-z")).platform_style(PlatformStyle::Linux)) + .child(KeyBinding::new(binding("fn-s")).platform_style(PlatformStyle::Linux)) .child(Story::label("Single Key with All Modifiers (Windows)")) - .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")).display(KeyBindingDisplay::Windows)) + .child( + KeyBinding::new(binding("ctrl-alt-cmd-shift-z")).platform_style(PlatformStyle::Windows), + ) .child(Story::label("Chord (Windows)")) - .child(KeyBinding::new(binding("a z")).display(KeyBindingDisplay::Windows)) + .child(KeyBinding::new(binding("a z")).platform_style(PlatformStyle::Windows)) .child(Story::label("Chord with Modifier (Windows)")) - .child(KeyBinding::new(binding("ctrl-a shift-z")).display(KeyBindingDisplay::Windows)) - .child(KeyBinding::new(binding("fn-s")).display(KeyBindingDisplay::Windows)) + .child(KeyBinding::new(binding("ctrl-a shift-z")).platform_style(PlatformStyle::Windows)) + .child(KeyBinding::new(binding("fn-s")).platform_style(PlatformStyle::Windows)) } } diff --git a/crates/ui/src/components/stories/platform_titlebar.rs b/crates/ui/src/components/stories/platform_titlebar.rs deleted file mode 100644 index d2eec79b80..0000000000 --- a/crates/ui/src/components/stories/platform_titlebar.rs +++ /dev/null @@ -1,46 +0,0 @@ -use gpui::Render; -use story::{StoryContainer, StoryItem, StorySection}; - -use crate::{prelude::*, PlatformStyle, PlatformTitlebar}; - -pub struct PlatformTitlebarStory; - -impl Render for PlatformTitlebarStory { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - StoryContainer::new( - "Platform Titlebar", - "crates/ui/src/components/stories/platform_titlebar.rs", - ) - .child( - StorySection::new().child( - StoryItem::new( - "Default (macOS)", - PlatformTitlebar::new("macos").platform_style(PlatformStyle::MacOs), - ) - .description("") - .usage(""), - ), - ) - .child( - StorySection::new().child( - StoryItem::new( - "Default (Linux)", - PlatformTitlebar::new("linux").platform_style(PlatformStyle::Linux), - ) - .description("") - .usage(""), - ), - ) - .child( - StorySection::new().child( - StoryItem::new( - "Default (Windows)", - PlatformTitlebar::new("windows").platform_style(PlatformStyle::Windows), - ) - .description("") - .usage(""), - ), - ) - .into_element() - } -} diff --git a/crates/ui/src/components/stories/title_bar.rs b/crates/ui/src/components/stories/title_bar.rs new file mode 100644 index 0000000000..78e0d85e30 --- /dev/null +++ b/crates/ui/src/components/stories/title_bar.rs @@ -0,0 +1,56 @@ +use gpui::Render; +use story::{StoryContainer, StoryItem, StorySection}; + +use crate::{prelude::*, PlatformStyle, TitleBar}; + +pub struct TitleBarStory; + +impl Render for TitleBarStory { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + fn add_sample_children(titlebar: TitleBar) -> TitleBar { + titlebar + .child(div().size_2().bg(gpui::red())) + .child(div().size_2().bg(gpui::blue())) + .child(div().size_2().bg(gpui::green())) + } + + StoryContainer::new("TitleBar", "crates/ui/src/components/stories/title_bar.rs") + .child( + StorySection::new().child( + StoryItem::new( + "Default (macOS)", + TitleBar::new("macos") + .platform_style(PlatformStyle::Mac) + .map(add_sample_children), + ) + .description("") + .usage(""), + ), + ) + .child( + StorySection::new().child( + StoryItem::new( + "Default (Linux)", + TitleBar::new("linux") + .platform_style(PlatformStyle::Linux) + .map(add_sample_children), + ) + .description("") + .usage(""), + ), + ) + .child( + StorySection::new().child( + StoryItem::new( + "Default (Windows)", + TitleBar::new("windows") + .platform_style(PlatformStyle::Windows) + .map(add_sample_children), + ) + .description("") + .usage(""), + ), + ) + .into_element() + } +} diff --git a/crates/ui/src/components/title_bar.rs b/crates/ui/src/components/title_bar.rs new file mode 100644 index 0000000000..2eb181f9b2 --- /dev/null +++ b/crates/ui/src/components/title_bar.rs @@ -0,0 +1,4 @@ +mod title_bar; +mod windows_window_controls; + +pub use title_bar::*; diff --git a/crates/ui/src/components/title_bar/title_bar.rs b/crates/ui/src/components/title_bar/title_bar.rs new file mode 100644 index 0000000000..d6b3463783 --- /dev/null +++ b/crates/ui/src/components/title_bar/title_bar.rs @@ -0,0 +1,96 @@ +use gpui::{AnyElement, Interactivity, Stateful}; +use smallvec::SmallVec; + +use crate::components::title_bar::windows_window_controls::WindowsWindowControls; +use crate::prelude::*; + +#[derive(IntoElement)] +pub struct TitleBar { + platform_style: PlatformStyle, + content: Stateful
, + children: SmallVec<[AnyElement; 2]>, +} + +impl TitleBar { + pub fn height(cx: &mut WindowContext) -> Pixels { + (1.75 * cx.rem_size()).max(px(32.)) + } + + pub fn new(id: impl Into) -> Self { + Self { + platform_style: PlatformStyle::platform(), + content: div().id(id.into()), + children: SmallVec::new(), + } + } + + /// Sets the platform style. + pub fn platform_style(mut self, style: PlatformStyle) -> Self { + self.platform_style = style; + self + } + + fn top_padding(&self, cx: &WindowContext) -> Pixels { + if self.platform_style == PlatformStyle::Windows && cx.is_maximized() { + // todo(windows): get padding from win32 api, need HWND from window context somehow + // should be GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) * 2 + px(8.) + } else { + px(0.) + } + } +} + +impl InteractiveElement for TitleBar { + fn interactivity(&mut self) -> &mut Interactivity { + self.content.interactivity() + } +} + +impl StatefulInteractiveElement for TitleBar {} + +impl ParentElement for TitleBar { + fn extend(&mut self, elements: impl Iterator) { + self.children.extend(elements) + } +} + +impl RenderOnce for TitleBar { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let height = Self::height(cx); + let top_padding = self.top_padding(cx); + + h_flex() + .id("titlebar") + .w_full() + .pt(top_padding) + .h(height) + .map(|this| { + if cx.is_fullscreen() { + this.pl_2() + } else if self.platform_style == PlatformStyle::Mac { + // Use pixels here instead of a rem-based size because the macOS traffic + // lights are a static size, and don't scale with the rest of the UI. + this.pl(px(80.)) + } else { + this.pl_2() + } + }) + .bg(cx.theme().colors().title_bar_background) + .content_stretch() + .child( + self.content + .id("titlebar-content") + .flex() + .flex_row() + .justify_between() + .w_full() + .children(self.children), + ) + .when(self.platform_style == PlatformStyle::Windows, |title_bar| { + let button_height = Self::height(cx) - top_padding; + + title_bar.child(WindowsWindowControls::new(button_height)) + }) + } +} diff --git a/crates/ui/src/components/title_bar/windows_window_controls.rs b/crates/ui/src/components/title_bar/windows_window_controls.rs new file mode 100644 index 0000000000..504a849d20 --- /dev/null +++ b/crates/ui/src/components/title_bar/windows_window_controls.rs @@ -0,0 +1,127 @@ +use gpui::{prelude::*, Rgba, WindowAppearance}; + +use crate::prelude::*; + +#[derive(IntoElement)] +pub struct WindowsWindowControls { + button_height: Pixels, +} + +impl WindowsWindowControls { + pub fn new(button_height: Pixels) -> Self { + Self { button_height } + } +} + +impl RenderOnce for WindowsWindowControls { + 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("windows-window-controls") + .flex() + .flex_row() + .justify_center() + .content_stretch() + .max_h(self.button_height) + .min_h(self.button_height) + .font("Segoe Fluent Icons") + .text_size(px(10.0)) + .child(WindowsCaptionButton::new( + "minimize", + WindowsCaptionButtonIcon::Minimize, + button_hover_color, + )) + .child(WindowsCaptionButton::new( + "maximize-or-restore", + if cx.is_maximized() { + WindowsCaptionButtonIcon::Restore + } else { + WindowsCaptionButtonIcon::Maximize + }, + button_hover_color, + )) + .child(WindowsCaptionButton::new( + "close", + WindowsCaptionButtonIcon::Close, + close_button_hover_color, + )) + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +enum WindowsCaptionButtonIcon { + Minimize, + Restore, + Maximize, + Close, +} + +#[derive(IntoElement)] +struct WindowsCaptionButton { + id: ElementId, + icon: WindowsCaptionButtonIcon, + hover_background_color: Rgba, +} + +impl WindowsCaptionButton { + pub fn new( + id: impl Into, + icon: WindowsCaptionButtonIcon, + hover_background_color: Rgba, + ) -> Self { + Self { + id: id.into(), + icon, + hover_background_color, + } + } +} + +impl RenderOnce for WindowsCaptionButton { + fn render(self, _cx: &mut WindowContext) -> impl IntoElement { + // todo(windows): get padding from win32 api, need HWND from window context somehow + // should be GetSystemMetricsForDpi(SM_CXSIZE, dpi) + let width = px(36.0); + + 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(match self.icon { + WindowsCaptionButtonIcon::Minimize => "\u{e921}", + WindowsCaptionButtonIcon::Restore => "\u{e923}", + WindowsCaptionButtonIcon::Maximize => "\u{e922}", + WindowsCaptionButtonIcon::Close => "\u{e8bb}", + }) + } +} diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index dd69e914cf..5cf56bc58d 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -11,7 +11,7 @@ pub use crate::clickable::*; pub use crate::disableable::*; pub use crate::fixed::*; pub use crate::selectable::*; -pub use crate::styles::{rems_from_px, vh, vw}; +pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle}; pub use crate::visible_on_hover::*; pub use crate::{h_flex, v_flex}; pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; diff --git a/crates/ui/src/styles.rs b/crates/ui/src/styles.rs index 0b16cb95f3..ad02ae9756 100644 --- a/crates/ui/src/styles.rs +++ b/crates/ui/src/styles.rs @@ -1,9 +1,11 @@ mod color; mod elevation; +mod platform; mod typography; mod units; pub use color::*; pub use elevation::*; +pub use platform::*; pub use typography::*; pub use units::*; diff --git a/crates/ui/src/styles/platform.rs b/crates/ui/src/styles/platform.rs new file mode 100644 index 0000000000..bb2c3ea47b --- /dev/null +++ b/crates/ui/src/styles/platform.rs @@ -0,0 +1,25 @@ +/// The platform style to use when rendering UI. +/// +/// This can be used to abstract over platform differences. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub enum PlatformStyle { + /// Display in macOS style. + Mac, + /// Display in Linux style. + Linux, + /// Display in Windows style. + Windows, +} + +impl PlatformStyle { + /// Returns the [`PlatformStyle`] for the current platform. + pub const fn platform() -> Self { + if cfg!(target_os = "linux") { + Self::Linux + } else if cfg!(target_os = "windows") { + Self::Windows + } else { + Self::Mac + } + } +} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 16dc059435..6fdbe936ea 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4809,7 +4809,7 @@ impl Element for DisconnectedOverlay { .bg(background) .absolute() .left_0() - .top(ui::titlebar_height(cx)) + .top(ui::TitleBar::height(cx)) .size_full() .flex() .items_center()