ui: Clean up PlatformTitlebar
implementation (#9413)
This PR cleans up the implementation of the `PlatformTitlebar` component to better match our conventions for building UI components. Release Notes: - N/A
This commit is contained in:
parent
db43479be8
commit
123d3ee282
5 changed files with 159 additions and 123 deletions
|
@ -13,8 +13,8 @@ use rpc::proto;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{
|
use ui::{
|
||||||
h_flex, platform_titlebar, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator,
|
h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike,
|
||||||
Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, Tooltip,
|
ButtonStyle, ContextMenu, Icon, IconButton, IconName, PlatformTitlebar, TintColor, Tooltip,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
|
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 project_id = self.project.read(cx).remote_id();
|
||||||
let workspace = self.workspace.upgrade();
|
let workspace = self.workspace.upgrade();
|
||||||
|
|
||||||
platform_titlebar("collab-titlebar")
|
PlatformTitlebar::new("collab-titlebar")
|
||||||
.titlebar_bg(cx.theme().colors().title_bar_background)
|
.background(cx.theme().colors().title_bar_background)
|
||||||
// note: on windows titlebar behaviour is handled by the platform implementation
|
// note: on windows titlebar behaviour is handled by the platform implementation
|
||||||
.when(cfg!(not(windows)), |this| {
|
.when(cfg!(not(windows)), |this| {
|
||||||
this.on_click(|event, cx| {
|
this.on_click(|event, cx| {
|
||||||
|
@ -68,7 +68,6 @@ impl Render for CollabTitlebarItem {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.justify_between()
|
|
||||||
// left side
|
// left side
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|
|
@ -29,6 +29,7 @@ pub enum ComponentStory {
|
||||||
ListHeader,
|
ListHeader,
|
||||||
ListItem,
|
ListItem,
|
||||||
OverflowScroll,
|
OverflowScroll,
|
||||||
|
PlatformTitlebar,
|
||||||
Scroll,
|
Scroll,
|
||||||
Tab,
|
Tab,
|
||||||
TabBar,
|
TabBar,
|
||||||
|
@ -60,6 +61,7 @@ impl ComponentStory {
|
||||||
Self::ListHeader => cx.new_view(|_| ui::ListHeaderStory).into(),
|
Self::ListHeader => cx.new_view(|_| ui::ListHeaderStory).into(),
|
||||||
Self::ListItem => cx.new_view(|_| ui::ListItemStory).into(),
|
Self::ListItem => cx.new_view(|_| ui::ListItemStory).into(),
|
||||||
Self::OverflowScroll => cx.new_view(|_| crate::stories::OverflowScrollStory).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::Scroll => ScrollStory::view(cx).into(),
|
||||||
Self::Text => TextStory::view(cx).into(),
|
Self::Text => TextStory::view(cx).into(),
|
||||||
Self::Tab => cx.new_view(|_| ui::TabStory).into(),
|
Self::Tab => cx.new_view(|_| ui::TabStory).into(),
|
||||||
|
|
|
@ -1,18 +1,7 @@
|
||||||
// allowing due to multiple platform conditional code
|
use gpui::{transparent_black, AnyElement, Fill, Interactivity, Rgba, Stateful, WindowAppearance};
|
||||||
#![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 smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::h_flex;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub enum PlatformStyle {
|
pub enum PlatformStyle {
|
||||||
Linux,
|
Linux,
|
||||||
|
@ -47,27 +36,38 @@ impl PlatformStyle {
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct PlatformTitlebar {
|
pub struct PlatformTitlebar {
|
||||||
platform: PlatformStyle,
|
platform: PlatformStyle,
|
||||||
titlebar_bg: Fill,
|
background: Fill,
|
||||||
content: Stateful<Div>,
|
content: Stateful<Div>,
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Styled for PlatformTitlebar {
|
|
||||||
fn style(&mut self) -> &mut StyleRefinement {
|
|
||||||
self.content.style()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlatformTitlebar {
|
impl PlatformTitlebar {
|
||||||
/// Change the platform style used
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||||
pub fn with_platform_style(self, style: PlatformStyle) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
platform: style,
|
platform: PlatformStyle::platform(),
|
||||||
..self
|
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() {
|
if self.platform.windows() && cx.is_maximized() {
|
||||||
// todo(windows): get padding from win32 api, need HWND from window context somehow
|
// todo(windows): get padding from win32 api, need HWND from window context somehow
|
||||||
// should be GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) * 2
|
// should be GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) * 2
|
||||||
|
@ -84,106 +84,91 @@ impl PlatformTitlebar {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_window_controls_right(&self, cx: &mut WindowContext) -> impl Element {
|
fn render_window_controls_right(&self, cx: &mut WindowContext) -> impl Element {
|
||||||
if self.platform.windows() {
|
if !self.platform.windows() {
|
||||||
let btn_height = titlebar_height(cx) - self.titlebar_top_padding(cx);
|
return div().id("caption-buttons-windows");
|
||||||
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()
|
|
||||||
.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")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the background color of titlebar.
|
let button_height = titlebar_height(cx) - self.top_padding(cx);
|
||||||
pub fn titlebar_bg<F>(mut self, fill: F) -> Self
|
let close_button_hover_color = Rgba {
|
||||||
where
|
r: 232.0 / 255.0,
|
||||||
F: Into<Fill>,
|
g: 17.0 / 255.0,
|
||||||
Self: Sized,
|
b: 32.0 / 255.0,
|
||||||
{
|
a: 1.0,
|
||||||
self.titlebar_bg = fill.into();
|
};
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn platform_titlebar(id: impl Into<ElementId>) -> PlatformTitlebar {
|
let button_hover_color = match cx.appearance() {
|
||||||
let id = id.into();
|
WindowAppearance::Light | WindowAppearance::VibrantLight => Rgba {
|
||||||
PlatformTitlebar {
|
r: 0.1,
|
||||||
platform: PlatformStyle::platform(),
|
g: 0.1,
|
||||||
titlebar_bg: transparent_black().into(),
|
b: 0.1,
|
||||||
content: div().id(id.clone()),
|
a: 0.2,
|
||||||
children: SmallVec::new(),
|
},
|
||||||
|
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_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 {
|
impl RenderOnce for PlatformTitlebar {
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
let titlebar_height = titlebar_height(cx);
|
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 window_controls_right = self.render_window_controls_right(cx);
|
||||||
let macos = self.platform.macos();
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("titlebar")
|
.id("titlebar")
|
||||||
.w_full()
|
.w_full()
|
||||||
|
@ -192,7 +177,7 @@ impl RenderOnce for PlatformTitlebar {
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if cx.is_fullscreen() {
|
if cx.is_fullscreen() {
|
||||||
this.pl_2()
|
this.pl_2()
|
||||||
} else if macos {
|
} else if self.platform.macos() {
|
||||||
// Use pixels here instead of a rem-based size because the macOS traffic
|
// 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.
|
// lights are a static size, and don't scale with the rest of the UI.
|
||||||
this.pl(px(80.))
|
this.pl(px(80.))
|
||||||
|
@ -200,14 +185,15 @@ impl RenderOnce for PlatformTitlebar {
|
||||||
this.pl_2()
|
this.pl_2()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.bg(self.titlebar_bg)
|
.bg(self.background)
|
||||||
.content_stretch()
|
.content_stretch()
|
||||||
.child(
|
.child(
|
||||||
self.content
|
self.content
|
||||||
|
.id("titlebar-content")
|
||||||
.flex()
|
.flex()
|
||||||
.flex_row()
|
.flex_row()
|
||||||
|
.justify_between()
|
||||||
.w_full()
|
.w_full()
|
||||||
.id("titlebar-content")
|
|
||||||
.children(self.children),
|
.children(self.children),
|
||||||
)
|
)
|
||||||
.child(window_controls_right)
|
.child(window_controls_right)
|
||||||
|
@ -219,6 +205,7 @@ impl InteractiveElement for PlatformTitlebar {
|
||||||
self.content.interactivity()
|
self.content.interactivity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatefulInteractiveElement for PlatformTitlebar {}
|
impl StatefulInteractiveElement for PlatformTitlebar {}
|
||||||
|
|
||||||
impl ParentElement for PlatformTitlebar {
|
impl ParentElement for PlatformTitlebar {
|
||||||
|
|
|
@ -10,6 +10,7 @@ mod label;
|
||||||
mod list;
|
mod list;
|
||||||
mod list_header;
|
mod list_header;
|
||||||
mod list_item;
|
mod list_item;
|
||||||
|
mod platform_titlebar;
|
||||||
mod tab;
|
mod tab;
|
||||||
mod tab_bar;
|
mod tab_bar;
|
||||||
mod toggle_button;
|
mod toggle_button;
|
||||||
|
@ -26,6 +27,7 @@ pub use label::*;
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
pub use list_header::*;
|
pub use list_header::*;
|
||||||
pub use list_item::*;
|
pub use list_item::*;
|
||||||
|
pub use platform_titlebar::*;
|
||||||
pub use tab::*;
|
pub use tab::*;
|
||||||
pub use tab_bar::*;
|
pub use tab_bar::*;
|
||||||
pub use toggle_button::*;
|
pub use toggle_button::*;
|
||||||
|
|
46
crates/ui/src/components/stories/platform_titlebar.rs
Normal file
46
crates/ui/src/components/stories/platform_titlebar.rs
Normal file
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue