xgpui_shell/
app_titlebar.rs1use super::ShellOptions;
2use gpui::{prelude::*, *};
3
4pub(super) fn configure_window(options: &mut WindowOptions) {
5 configure_for_platform(
6 options,
7 if cfg!(target_os = "macos") {
8 OperatingSystem::Mac
9 } else if cfg!(target_os = "windows") {
10 OperatingSystem::Windows
11 } else {
12 OperatingSystem::Linux
13 },
14 );
15}
16
17fn configure_for_platform(options: &mut WindowOptions, os: OperatingSystem) {
18 if matches!(os, OperatingSystem::Mac | OperatingSystem::Windows) {
19 let titlebar = options
20 .titlebar
21 .get_or_insert_with(TitlebarOptions::default);
22 titlebar.appears_transparent = true;
23 if os == OperatingSystem::Mac {
24 titlebar.traffic_light_position = Some(point(px(9.), px(12.)));
25 options.app_owns_titlebar_drag = true;
26 }
27 }
28}
29
30#[derive(IntoElement)]
35pub struct ShellDragRegion {
36 id: ElementId,
37 content: AnyElement,
38 options: ShellOptions,
39}
40impl ShellDragRegion {
41 pub fn new(id: impl Into<ElementId>, content: impl IntoElement) -> Self {
42 Self {
43 id: id.into(),
44 content: content.into_any_element(),
45 options: ShellOptions::default(),
46 }
47 }
48 pub fn options(mut self, options: ShellOptions) -> Self {
50 self.options = options;
51 self
52 }
53}
54struct DragState(Option<Point<Pixels>>);
55impl Render for DragState {
56 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
57 div()
58 }
59}
60impl RenderOnce for ShellDragRegion {
61 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
62 let actions = self.options.resolved_preferences(&cx.desktop_preferences());
63 let state = window.use_state(cx, |_, _| DragState(None));
64 let mac = cx.platform_info().os == OperatingSystem::Mac;
65 let native_double_click = mac && self.options.double_click_action.is_none();
66 let windows = cx.platform_info().os == OperatingSystem::Windows;
67 div()
68 .id(self.id)
69 .flex_1()
70 .min_w_0()
71 .h_full()
72 .when(windows, |s| s.window_control_area(WindowControlArea::Drag))
73 .when(!windows, |s| {
74 s.on_mouse_down(
75 MouseButton::Left,
76 window.listener_for(
77 &state,
78 move |state, event: &MouseDownEvent, window, cx| {
79 cx.stop_propagation();
80 if event.click_count == 2 {
81 state.0 = None;
82 if native_double_click {
83 window.titlebar_double_click();
84 } else {
85 actions
86 .double_click_action
87 .perform_at(window, event.position);
88 }
89 } else {
90 state.0 = Some(event.position);
91 }
92 },
93 ),
94 )
95 .on_mouse_move(window.listener_for(
96 &state,
97 |state, event: &MouseMoveEvent, window, _| {
98 if !event.dragging() {
99 state.0 = None;
100 return;
101 }
102 if let Some(origin) = state.0 {
103 let delta = event.position - origin;
104 if delta.x.abs() >= px(4.) || delta.y.abs() >= px(4.) {
105 state.0 = None;
106 window.start_window_move();
107 }
108 }
109 },
110 ))
111 .on_mouse_up(
112 MouseButton::Left,
113 window.listener_for(&state, |state, _, _, _| state.0 = None),
114 )
115 .on_mouse_up_out(
116 MouseButton::Left,
117 window.listener_for(&state, |state, _, _, _| state.0 = None),
118 )
119 .on_mouse_down_out(window.listener_for(&state, |state, _, _, _| state.0 = None))
120 .on_mouse_down(MouseButton::Middle, move |event, window, cx| {
121 cx.stop_propagation();
122 actions
123 .middle_click_action
124 .perform_at(window, event.position);
125 })
126 .on_mouse_down(MouseButton::Right, move |event, window, cx| {
127 cx.stop_propagation();
128 actions
129 .right_click_action
130 .perform_at(window, event.position);
131 })
132 })
133 .child(self.content)
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 #[::core::prelude::v1::test]
141 fn mac_composition_keeps_native_controls_and_title_while_claiming_drag() {
142 let mut options = WindowOptions {
143 titlebar: Some(TitlebarOptions {
144 title: Some("App title".into()),
145 ..Default::default()
146 }),
147 ..Default::default()
148 };
149 configure_for_platform(&mut options, OperatingSystem::Mac);
150 let titlebar = options.titlebar.unwrap();
151 assert_eq!(titlebar.title.as_deref(), Some("App title"));
152 assert!(titlebar.appears_transparent);
153 assert!(titlebar.traffic_light_position.is_some());
154 assert!(options.app_owns_titlebar_drag);
155 }
156 #[::core::prelude::v1::test]
157 fn linux_preserves_decoration_negotiation_and_windows_requests_custom_chrome() {
158 let mut linux = WindowOptions {
159 window_decorations: Some(WindowDecorations::Server),
160 ..Default::default()
161 };
162 configure_for_platform(&mut linux, OperatingSystem::Linux);
163 assert_eq!(linux.window_decorations, Some(WindowDecorations::Server));
164 assert!(!linux.titlebar.unwrap().appears_transparent);
165 let mut windows = WindowOptions::default();
166 configure_for_platform(&mut windows, OperatingSystem::Windows);
167 assert!(windows.titlebar.unwrap().appears_transparent);
168 assert!(!windows.app_owns_titlebar_drag);
169 }
170}