ui: Refine TitleBar component (#9415)

This PR continues the refinements to the `TitleBar` component.

Here are the notable changes:

- `KeyBindingDisplay` and `PlatformStyle` have been unified into a
single `PlatformStyle`.
- This provides us a consistent way for adapting UI to different
platform styles.
- `PlatformTitlebar` has been renamed to `TitleBar`.
  - The `Platform` prefix was irrelevant.
- The Windows window controls have been factored out into a separate
module and have been componentized.

<img width="1283" alt="Screenshot 2024-03-15 at 3 34 38 PM"
src="https://github.com/zed-industries/zed/assets/1486634/07da391f-828b-48bf-8849-58863f4ccce7">

> I'm missing the Segoe Fluent Icons font, so that's why the aren't
rendering properly.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-03-15 15:48:07 -04:00 committed by GitHub
parent dd33330648
commit c1f1c5e75f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 369 additions and 335 deletions

View File

@ -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| {

View File

@ -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(),

View File

@ -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")]

View File

@ -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("+"))
}
})

View File

@ -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<Div>,
children: SmallVec<[AnyElement; 2]>,
}
impl PlatformTitlebar {
pub fn new(id: impl Into<ElementId>) -> 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<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
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<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 {
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<Item = AnyElement>) {
self.children.extend(elements)
}
}

View File

@ -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::*;

View File

@ -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))
}
}

View File

@ -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<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()
}
}

View File

@ -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<Self>) -> 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()
}
}

View File

@ -0,0 +1,4 @@
mod title_bar;
mod windows_window_controls;
pub use title_bar::*;

View File

@ -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<Div>,
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<ElementId>) -> 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<Item = AnyElement>) {
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))
})
}
}

View File

@ -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<ElementId>,
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}",
})
}
}

View File

@ -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};

View File

@ -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::*;

View File

@ -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
}
}
}

View File

@ -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()