title_bar.rs

  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}