platform_title_bar.rs

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