platform_title_bar.rs

  1use gpui::{
  2    AnyElement, Context, Decorations, 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::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 title_bar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
 41        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
 42            if window.is_window_active() && !self.should_move {
 43                cx.theme().colors().title_bar_background
 44            } else {
 45                cx.theme().colors().title_bar_inactive_background
 46            }
 47        } else {
 48            cx.theme().colors().title_bar_background
 49        }
 50    }
 51
 52    pub fn set_children<T>(&mut self, children: T)
 53    where
 54        T: IntoIterator<Item = AnyElement>,
 55    {
 56        self.children = children.into_iter().collect();
 57    }
 58}
 59
 60impl Render for PlatformTitleBar {
 61    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 62        let supported_controls = window.window_controls();
 63        let decorations = window.window_decorations();
 64        let height = Self::height(window);
 65        let titlebar_color = self.title_bar_color(window, cx);
 66        let close_action = Box::new(workspace::CloseWindow);
 67        let children = mem::take(&mut self.children);
 68
 69        h_flex()
 70            .window_control_area(WindowControlArea::Drag)
 71            .w_full()
 72            .h(height)
 73            .map(|this| {
 74                if window.is_fullscreen() {
 75                    this.pl_2()
 76                } else if self.platform_style == PlatformStyle::Mac {
 77                    this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING))
 78                } else {
 79                    this.pl_2()
 80                }
 81            })
 82            .map(|el| match decorations {
 83                Decorations::Server => el,
 84                Decorations::Client { tiling, .. } => el
 85                    .when(!(tiling.top || tiling.right), |el| {
 86                        el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 87                    })
 88                    .when(!(tiling.top || tiling.left), |el| {
 89                        el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 90                    })
 91                    // this border is to avoid a transparent gap in the rounded corners
 92                    .mt(px(-1.))
 93                    .border(px(1.))
 94                    .border_color(titlebar_color),
 95            })
 96            .bg(titlebar_color)
 97            .content_stretch()
 98            .child(
 99                div()
100                    .id(self.id.clone())
101                    .flex()
102                    .flex_row()
103                    .items_center()
104                    .justify_between()
105                    .w_full()
106                    // Note: On Windows the title bar behavior is handled by the platform implementation.
107                    .when(self.platform_style == PlatformStyle::Mac, |this| {
108                        this.on_click(|event, window, _| {
109                            if event.click_count() == 2 {
110                                window.titlebar_double_click();
111                            }
112                        })
113                    })
114                    .when(self.platform_style == PlatformStyle::Linux, |this| {
115                        this.on_click(|event, window, _| {
116                            if event.click_count() == 2 {
117                                window.zoom_window();
118                            }
119                        })
120                    })
121                    .children(children),
122            )
123            .when(!window.is_fullscreen(), |title_bar| {
124                match self.platform_style {
125                    PlatformStyle::Mac => title_bar,
126                    PlatformStyle::Linux => {
127                        if matches!(decorations, Decorations::Client { .. }) {
128                            title_bar
129                                .child(platform_linux::LinuxWindowControls::new(close_action))
130                                .when(supported_controls.window_menu, |titlebar| {
131                                    titlebar
132                                        .on_mouse_down(MouseButton::Right, move |ev, window, _| {
133                                            window.show_window_menu(ev.position)
134                                        })
135                                })
136                                .on_mouse_move(cx.listener(move |this, _ev, window, _| {
137                                    if this.should_move {
138                                        this.should_move = false;
139                                        window.start_window_move();
140                                    }
141                                }))
142                                .on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
143                                    this.should_move = false;
144                                }))
145                                .on_mouse_up(
146                                    MouseButton::Left,
147                                    cx.listener(move |this, _ev, _window, _cx| {
148                                        this.should_move = false;
149                                    }),
150                                )
151                                .on_mouse_down(
152                                    MouseButton::Left,
153                                    cx.listener(move |this, _ev, _window, _cx| {
154                                        this.should_move = true;
155                                    }),
156                                )
157                        } else {
158                            title_bar
159                        }
160                    }
161                    PlatformStyle::Windows => {
162                        title_bar.child(platform_windows::WindowsWindowControls::new(height))
163                    }
164                }
165            })
166    }
167}
168
169impl ParentElement for PlatformTitleBar {
170    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
171        self.children.extend(elements)
172    }
173}