1use gpui::{AnyElement, Interactivity, Stateful};
2use smallvec::SmallVec;
3
4use crate::components::title_bar::windows_window_controls::WindowsWindowControls;
5use crate::prelude::*;
6
7#[derive(IntoElement)]
8pub struct TitleBar {
9 platform_style: PlatformStyle,
10 content: Stateful<Div>,
11 children: SmallVec<[AnyElement; 2]>,
12}
13
14impl TitleBar {
15 #[cfg(not(target_os = "windows"))]
16 pub fn height(cx: &mut WindowContext) -> Pixels {
17 (1.75 * cx.rem_size()).max(px(34.))
18 }
19
20 #[cfg(target_os = "windows")]
21 pub fn height(_cx: &mut WindowContext) -> Pixels {
22 // todo(windows) instead of hard coded size report the actual size to the Windows platform API
23 px(32.)
24 }
25
26 #[cfg(not(target_os = "windows"))]
27 fn top_padding(_cx: &WindowContext) -> Pixels {
28 px(0.)
29 }
30
31 #[cfg(target_os = "windows")]
32 fn top_padding(cx: &WindowContext) -> Pixels {
33 use windows::Win32::UI::{
34 HiDpi::GetSystemMetricsForDpi,
35 WindowsAndMessaging::{SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI},
36 };
37
38 // This top padding is not dependent on the title bar style and is instead a quirk of maximized windows on Windows:
39 // https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543
40 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
41 if cx.is_maximized() {
42 px((padding * 2) as f32)
43 } else {
44 px(0.)
45 }
46 }
47
48 pub fn new(id: impl Into<ElementId>) -> Self {
49 Self {
50 platform_style: PlatformStyle::platform(),
51 content: div().id(id.into()),
52 children: SmallVec::new(),
53 }
54 }
55
56 /// Sets the platform style.
57 pub fn platform_style(mut self, style: PlatformStyle) -> Self {
58 self.platform_style = style;
59 self
60 }
61}
62
63impl InteractiveElement for TitleBar {
64 fn interactivity(&mut self) -> &mut Interactivity {
65 self.content.interactivity()
66 }
67}
68
69impl StatefulInteractiveElement for TitleBar {}
70
71impl ParentElement for TitleBar {
72 fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
73 self.children.extend(elements)
74 }
75}
76
77impl RenderOnce for TitleBar {
78 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
79 let height = Self::height(cx);
80 h_flex()
81 .id("titlebar")
82 .w_full()
83 .pt(Self::top_padding(cx))
84 .h(height + Self::top_padding(cx))
85 .map(|this| {
86 if cx.is_fullscreen() {
87 this.pl_2()
88 } else if self.platform_style == PlatformStyle::Mac {
89 // Use pixels here instead of a rem-based size because the macOS traffic
90 // lights are a static size, and don't scale with the rest of the UI.
91 //
92 // Magic number: There is one extra pixel of padding on the left side due to
93 // the 1px border around the window on macOS apps.
94 this.pl(px(71.))
95 } else {
96 this.pl_2()
97 }
98 })
99 .bg(cx.theme().colors().title_bar_background)
100 .content_stretch()
101 .child(
102 self.content
103 .id("titlebar-content")
104 .flex()
105 .flex_row()
106 .justify_between()
107 .w_full()
108 .children(self.children),
109 )
110 .when(
111 self.platform_style == PlatformStyle::Windows && !cx.is_fullscreen(),
112 |title_bar| title_bar.child(WindowsWindowControls::new(height)),
113 )
114 }
115}