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