Skip to content

Commit

Permalink
ui: Clean up PlatformTitlebar implementation (zed-industries#9413)
Browse files Browse the repository at this point in the history
This PR cleans up the implementation of the `PlatformTitlebar` component
to better match our conventions for building UI components.

Release Notes:

- N/A
  • Loading branch information
maxdeviant authored Mar 15, 2024
1 parent db43479 commit 123d3ee
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 122 deletions.
9 changes: 4 additions & 5 deletions crates/collab_ui/src/collab_titlebar_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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| {
Expand All @@ -68,7 +68,6 @@ impl Render for CollabTitlebarItem {
}
})
})
.justify_between()
// left side
.child(
h_flex()
Expand Down
2 changes: 2 additions & 0 deletions crates/storybook/src/story_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub enum ComponentStory {
ListHeader,
ListItem,
OverflowScroll,
PlatformTitlebar,
Scroll,
Tab,
TabBar,
Expand Down Expand Up @@ -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(),
Expand Down
221 changes: 104 additions & 117 deletions crates/ui/src/components/platform_titlebar.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -47,27 +36,38 @@ impl PlatformStyle {
#[derive(IntoElement)]
pub struct PlatformTitlebar {
platform: PlatformStyle,
titlebar_bg: Fill,
background: Fill,
content: Stateful<Div>,
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<ElementId>) -> 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<F>(mut self, fill: F) -> Self
where
F: Into<Fill>,
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
Expand All @@ -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<Div> {
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()
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<Div> {
let mut active_color = hover_color;
active_color.a *= 0.2;
h_flex()
.id(id)
.h_full()
.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")
.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)
}
}

/// Sets the background color of titlebar.
pub fn titlebar_bg<F>(mut self, fill: F) -> Self
where
F: Into<Fill>,
Self: Sized,
{
self.titlebar_bg = fill.into();
self
}
}
const MINIMIZE_ICON: &str = "\u{e921}";
const RESTORE_ICON: &str = "\u{e923}";
const MAXIMIZE_ICON: &str = "\u{e922}";
const CLOSE_ICON: &str = "\u{e8bb}";

pub fn platform_titlebar(id: impl Into<ElementId>) -> PlatformTitlebar {
let id = id.into();
PlatformTitlebar {
platform: PlatformStyle::platform(),
titlebar_bg: transparent_black().into(),
content: div().id(id.clone()),
children: SmallVec::new(),
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()
Expand All @@ -192,22 +177,23 @@ 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.))
} else {
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)
Expand All @@ -219,6 +205,7 @@ impl InteractiveElement for PlatformTitlebar {
self.content.interactivity()
}
}

impl StatefulInteractiveElement for PlatformTitlebar {}

impl ParentElement for PlatformTitlebar {
Expand Down
2 changes: 2 additions & 0 deletions crates/ui/src/components/stories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::*;
46 changes: 46 additions & 0 deletions crates/ui/src/components/stories/platform_titlebar.rs
Original file line number Diff line number Diff line change
@@ -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<Self>) -> 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()
}
}

0 comments on commit 123d3ee

Please sign in to comment.