diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 4ef63c5a14..9ab4c94ff1 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -13,8 +13,8 @@ use rpc::proto; use std::sync::Arc; use theme::ActiveTheme; use ui::{ - h_flex, platform_titlebar, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, - Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, Tooltip, + h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, + ButtonStyle, ContextMenu, Icon, IconButton, IconName, PlatformTitlebar, TintColor, Tooltip, }; use util::ResultExt; use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; @@ -58,8 +58,8 @@ impl Render for CollabTitlebarItem { let project_id = self.project.read(cx).remote_id(); let workspace = self.workspace.upgrade(); - platform_titlebar("collab-titlebar") - .titlebar_bg(cx.theme().colors().title_bar_background) + PlatformTitlebar::new("collab-titlebar") + .background(cx.theme().colors().title_bar_background) // note: on windows titlebar behaviour is handled by the platform implementation .when(cfg!(not(windows)), |this| { this.on_click(|event, cx| { @@ -68,7 +68,6 @@ impl Render for CollabTitlebarItem { } }) }) - .justify_between() // left side .child( h_flex() diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index c8cda13018..bbda1119ec 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -29,6 +29,7 @@ pub enum ComponentStory { ListHeader, ListItem, OverflowScroll, + PlatformTitlebar, Scroll, Tab, TabBar, @@ -60,6 +61,7 @@ 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(), diff --git a/crates/ui/src/components/platform_titlebar.rs b/crates/ui/src/components/platform_titlebar.rs index 93a274c9b8..9d889d01df 100644 --- a/crates/ui/src/components/platform_titlebar.rs +++ b/crates/ui/src/components/platform_titlebar.rs @@ -1,18 +1,7 @@ -// allowing due to multiple platform conditional code -#![allow(unused_imports)] - -use gpui::{ - div, - prelude::FluentBuilder, - px, transparent_black, AnyElement, Div, Element, ElementId, Fill, InteractiveElement, - Interactivity, IntoElement, ParentElement, Pixels, RenderOnce, Rgba, Stateful, - StatefulInteractiveElement, StyleRefinement, Styled, - WindowAppearance::{Dark, Light, VibrantDark, VibrantLight}, - WindowContext, -}; +use gpui::{transparent_black, AnyElement, Fill, Interactivity, Rgba, Stateful, WindowAppearance}; use smallvec::SmallVec; -use crate::h_flex; +use crate::prelude::*; pub enum PlatformStyle { Linux, @@ -47,27 +36,38 @@ impl PlatformStyle { #[derive(IntoElement)] pub struct PlatformTitlebar { platform: PlatformStyle, - titlebar_bg: Fill, + background: Fill, content: Stateful
, children: SmallVec<[AnyElement; 2]>, } -impl Styled for PlatformTitlebar { - fn style(&mut self) -> &mut StyleRefinement { - self.content.style() - } -} - impl PlatformTitlebar { - /// Change the platform style used - pub fn with_platform_style(self, style: PlatformStyle) -> Self { + pub fn new(id: impl Into) -> Self { Self { - platform: style, - ..self + platform: PlatformStyle::platform(), + background: transparent_black().into(), + content: div().id(id.into()), + children: SmallVec::new(), } } - fn titlebar_top_padding(&self, cx: &WindowContext) -> Pixels { + /// 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 @@ -84,106 +84,91 @@ impl PlatformTitlebar { } fn render_window_controls_right(&self, cx: &mut WindowContext) -> impl Element { - if self.platform.windows() { - let btn_height = titlebar_height(cx) - self.titlebar_top_padding(cx); - let close_btn_hover_color = Rgba { - r: 232.0 / 255.0, - g: 17.0 / 255.0, - b: 32.0 / 255.0, - a: 1.0, - }; - - let btn_hover_color = match cx.appearance() { - Light | VibrantLight => Rgba { - r: 0.1, - g: 0.1, - b: 0.1, - a: 0.2, - }, - Dark | VibrantDark => Rgba { - r: 0.9, - g: 0.9, - b: 0.9, - a: 0.1, - }, - }; - - fn windows_caption_btn( - 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) - } - - div() - .id("caption-buttons-windows") - .flex() - .flex_row() - .justify_center() - .content_stretch() - .max_h(btn_height) - .min_h(btn_height) - .font("Segoe Fluent Icons") - .text_size(px(10.0)) - .children(vec![ - windows_caption_btn("minimize", "\u{e921}", btn_hover_color, cx), // minimize icon - windows_caption_btn( - "maximize", - if cx.is_maximized() { - "\u{e923}" // restore icon - } else { - "\u{e922}" // maximize icon - }, - btn_hover_color, - cx, - ), - windows_caption_btn("close", "\u{e8bb}", close_btn_hover_color, cx), // close icon - ]) - } else { - div().id("caption-buttons-windows") + if !self.platform.windows() { + return div().id("caption-buttons-windows"); } - } - /// Sets the background color of titlebar. - pub fn titlebar_bg(mut self, fill: F) -> Self - where - F: Into, - Self: Sized, - { - self.titlebar_bg = fill.into(); - self - } -} + 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, + }; -pub fn platform_titlebar(id: impl Into) -> PlatformTitlebar { - let id = id.into(); - PlatformTitlebar { - platform: PlatformStyle::platform(), - titlebar_bg: transparent_black().into(), - content: div().id(id.clone()), - children: SmallVec::new(), + 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.titlebar_top_padding(cx); + let titlebar_top_padding = self.top_padding(cx); let window_controls_right = self.render_window_controls_right(cx); - let macos = self.platform.macos(); + h_flex() .id("titlebar") .w_full() @@ -192,7 +177,7 @@ impl RenderOnce for PlatformTitlebar { .map(|this| { if cx.is_fullscreen() { this.pl_2() - } else if macos { + } 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.)) @@ -200,14 +185,15 @@ impl RenderOnce for PlatformTitlebar { this.pl_2() } }) - .bg(self.titlebar_bg) + .bg(self.background) .content_stretch() .child( self.content + .id("titlebar-content") .flex() .flex_row() + .justify_between() .w_full() - .id("titlebar-content") .children(self.children), ) .child(window_controls_right) @@ -219,6 +205,7 @@ impl InteractiveElement for PlatformTitlebar { self.content.interactivity() } } + impl StatefulInteractiveElement for PlatformTitlebar {} impl ParentElement for PlatformTitlebar { diff --git a/crates/ui/src/components/stories.rs b/crates/ui/src/components/stories.rs index f321a7525f..8764b39698 100644 --- a/crates/ui/src/components/stories.rs +++ b/crates/ui/src/components/stories.rs @@ -10,6 +10,7 @@ mod label; mod list; mod list_header; mod list_item; +mod platform_titlebar; mod tab; mod tab_bar; mod toggle_button; @@ -26,6 +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 toggle_button::*; diff --git a/crates/ui/src/components/stories/platform_titlebar.rs b/crates/ui/src/components/stories/platform_titlebar.rs new file mode 100644 index 0000000000..d2eec79b80 --- /dev/null +++ b/crates/ui/src/components/stories/platform_titlebar.rs @@ -0,0 +1,46 @@ +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() + } +}