platform_title_bar.rs

  1use gpui::{
  2    AnyElement, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement, MouseButton,
  3    ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
  4};
  5use smallvec::SmallVec;
  6use std::mem;
  7use ui::prelude::*;
  8
  9use crate::{
 10    platforms::{platform_linux, platform_mac, platform_windows},
 11    system_window_tabs::SystemWindowTabs,
 12};
 13
 14pub struct PlatformTitleBar {
 15    id: ElementId,
 16    platform_style: PlatformStyle,
 17    children: SmallVec<[AnyElement; 2]>,
 18    should_move: bool,
 19    system_window_tabs: Entity<SystemWindowTabs>,
 20}
 21
 22impl PlatformTitleBar {
 23    pub fn new(id: impl Into<ElementId>, cx: &mut Context<Self>) -> Self {
 24        let platform_style = PlatformStyle::platform();
 25        let system_window_tabs = cx.new(|_cx| SystemWindowTabs::new());
 26
 27        Self {
 28            id: id.into(),
 29            platform_style,
 30            children: SmallVec::new(),
 31            should_move: false,
 32            system_window_tabs,
 33        }
 34    }
 35
 36    #[cfg(not(target_os = "windows"))]
 37    pub fn height(window: &mut Window) -> Pixels {
 38        (1.75 * window.rem_size()).max(px(34.))
 39    }
 40
 41    #[cfg(target_os = "windows")]
 42    pub fn height(_window: &mut Window) -> Pixels {
 43        // todo(windows) instead of hard coded size report the actual size to the Windows platform API
 44        px(32.)
 45    }
 46
 47    pub fn title_bar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
 48        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
 49            if window.is_window_active() && !self.should_move {
 50                cx.theme().colors().title_bar_background
 51            } else {
 52                cx.theme().colors().title_bar_inactive_background
 53            }
 54        } else {
 55            cx.theme().colors().title_bar_background
 56        }
 57    }
 58
 59    pub fn set_children<T>(&mut self, children: T)
 60    where
 61        T: IntoIterator<Item = AnyElement>,
 62    {
 63        self.children = children.into_iter().collect();
 64    }
 65}
 66
 67impl Render for PlatformTitleBar {
 68    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 69        let supported_controls = window.window_controls();
 70        let decorations = window.window_decorations();
 71        let height = Self::height(window);
 72        let titlebar_color = self.title_bar_color(window, cx);
 73        let close_action = Box::new(workspace::CloseWindow);
 74        let children = mem::take(&mut self.children);
 75
 76        let title_bar = h_flex()
 77            .window_control_area(WindowControlArea::Drag)
 78            .w_full()
 79            .h(height)
 80            .map(|this| {
 81                this.on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
 82                    this.should_move = false;
 83                }))
 84                .on_mouse_up(
 85                    gpui::MouseButton::Left,
 86                    cx.listener(move |this, _ev, _window, _cx| {
 87                        this.should_move = false;
 88                    }),
 89                )
 90                .on_mouse_down(
 91                    gpui::MouseButton::Left,
 92                    cx.listener(move |this, _ev, _window, _cx| {
 93                        this.should_move = true;
 94                    }),
 95                )
 96                .on_mouse_move(cx.listener(move |this, _ev, window, _| {
 97                    if this.should_move {
 98                        this.should_move = false;
 99                        window.start_window_move();
100                    }
101                }))
102            })
103            .map(|this| {
104                // Note: On Windows the title bar behavior is handled by the platform implementation.
105                this.id(self.id.clone())
106                    .when(self.platform_style == PlatformStyle::Mac, |this| {
107                        this.on_click(|event, window, _| {
108                            if event.click_count() == 2 {
109                                window.titlebar_double_click();
110                            }
111                        })
112                    })
113                    .when(self.platform_style == PlatformStyle::Linux, |this| {
114                        this.on_click(|event, window, _| {
115                            if event.click_count() == 2 {
116                                window.zoom_window();
117                            }
118                        })
119                    })
120            })
121            .map(|this| {
122                if window.is_fullscreen() {
123                    this.pl_2()
124                } else if self.platform_style == PlatformStyle::Mac {
125                    this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING))
126                } else {
127                    this.pl_2()
128                }
129            })
130            .map(|el| match decorations {
131                Decorations::Server => el,
132                Decorations::Client { tiling, .. } => el
133                    .when(!(tiling.top || tiling.right), |el| {
134                        el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
135                    })
136                    .when(!(tiling.top || tiling.left), |el| {
137                        el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
138                    })
139                    // this border is to avoid a transparent gap in the rounded corners
140                    .mt(px(-1.))
141                    .mb(px(-1.))
142                    .border(px(1.))
143                    .border_color(titlebar_color),
144            })
145            .bg(titlebar_color)
146            .content_stretch()
147            .child(
148                div()
149                    .id(self.id.clone())
150                    .flex()
151                    .flex_row()
152                    .items_center()
153                    .justify_between()
154                    .overflow_x_hidden()
155                    .w_full()
156                    .children(children),
157            )
158            .when(!window.is_fullscreen(), |title_bar| {
159                match self.platform_style {
160                    PlatformStyle::Mac => title_bar,
161                    PlatformStyle::Linux => {
162                        if matches!(decorations, Decorations::Client { .. }) {
163                            title_bar
164                                .child(platform_linux::LinuxWindowControls::new(close_action))
165                                .when(supported_controls.window_menu, |titlebar| {
166                                    titlebar
167                                        .on_mouse_down(MouseButton::Right, move |ev, window, _| {
168                                            window.show_window_menu(ev.position)
169                                        })
170                                })
171                        } else {
172                            title_bar
173                        }
174                    }
175                    PlatformStyle::Windows => {
176                        title_bar.child(platform_windows::WindowsWindowControls::new(height))
177                    }
178                }
179            });
180
181        v_flex()
182            .w_full()
183            .child(title_bar)
184            .child(self.system_window_tabs.clone().into_any_element())
185    }
186}
187
188impl ParentElement for PlatformTitleBar {
189    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
190        self.children.extend(elements)
191    }
192}