Skip to main content

gpui_component/
window_ext.rs

1use crate::{
2    Placement, Root,
3    dialog::{AlertDialog, Dialog},
4    input::AnyInputState,
5    notification::Notification,
6    sheet::Sheet,
7};
8use gpui::{App, ElementId, Entity, Window};
9use std::rc::Rc;
10
11/// Extension trait for [`Window`] to add dialog, sheet .. functionality.
12pub trait WindowExt: Sized {
13    /// Opens a Sheet at right placement.
14    fn open_sheet<F>(&mut self, cx: &mut App, build: F)
15    where
16        F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static;
17
18    /// Opens a Sheet at the given placement.
19    fn open_sheet_at<F>(&mut self, placement: Placement, cx: &mut App, build: F)
20    where
21        F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static;
22
23    /// Return true, if there is an active Sheet.
24    fn has_active_sheet(&mut self, cx: &mut App) -> bool;
25
26    /// Closes the active Sheet.
27    fn close_sheet(&mut self, cx: &mut App);
28
29    /// Opens a Dialog.
30    fn open_dialog<F>(&mut self, cx: &mut App, build: F)
31    where
32        F: Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static;
33
34    /// Opens an AlertDialog.
35    ///
36    /// This is a convenience method for opening an alert dialog with opinionated defaults.
37    /// The footer buttons are center-aligned and include an icon based on the variant.
38    ///
39    /// # Examples
40    ///
41    /// ```ignore
42    /// use gpui_kit::component::{AlertDialog, alert::AlertVariant};
43    ///
44    /// window.open_alert_dialog(cx, |alert, _, _| {
45    ///     alert.warning()
46    ///         .title("Unsaved Changes")
47    ///         .description("You have unsaved changes. Are you sure you want to leave?")
48    ///         .show_cancel(true)
49    /// });
50    /// ```
51    fn open_alert_dialog<F>(&mut self, cx: &mut App, build: F)
52    where
53        F: Fn(AlertDialog, &mut Window, &mut App) -> AlertDialog + 'static;
54
55    /// Return true, if there is an active Dialog.
56    fn has_active_dialog(&mut self, cx: &mut App) -> bool;
57
58    /// Closes the last active Dialog.
59    fn close_dialog(&mut self, cx: &mut App);
60
61    /// Closes all active Dialogs.
62    fn close_all_dialogs(&mut self, cx: &mut App);
63
64    /// Pushes a notification to the notification list.
65    fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App);
66
67    /// Removes all notifications whose id matches `T`, including ones registered with
68    /// either `Notification::id` or `Notification::id1` (any key).
69    fn remove_notification<T: Sized + 'static>(&mut self, cx: &mut App);
70
71    /// Removes a single notification matching the given type `T` and `key` (paired with `Notification::id1`).
72    fn remove_notification1<T: Sized + 'static>(&mut self, key: impl Into<ElementId>, cx: &mut App);
73
74    /// Clears all notifications.
75    fn clear_notifications(&mut self, cx: &mut App);
76
77    /// Returns number of notifications.
78    fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>>;
79
80    /// Return the currently focused input state.
81    ///
82    /// Covers `Input`, `Textarea`, `Editor` and `OtpInput`, use
83    /// [`AnyInputState::as_input`] and friends to get the concrete state.
84    /// A registration whose focus handle is no longer focused (e.g. the input
85    /// was removed from the tree while focused) is treated as `None`.
86    fn focused_input(&mut self, cx: &mut App) -> Option<AnyInputState>;
87    /// Returns true if there is a focused Input entity.
88    fn has_focused_input(&mut self, cx: &mut App) -> bool;
89
90    /// Returns the merged selected text across registered selectable regions
91    /// in this window, in logical document order and joined with `\n`.
92    #[deprecated(note = "use gpui_base::TextSelection::selected_text instead")]
93    fn selected_text(&mut self, cx: &mut App) -> String;
94
95    /// Returns true if any registered region has an active text selection in
96    /// this window, including renderer-local selections such as select-all.
97    #[deprecated(note = "use gpui_base::TextSelection::has_selection instead")]
98    fn has_text_selection(&mut self, cx: &mut App) -> bool;
99
100    /// Clears the window text selection and all registered renderer-local selections.
101    #[deprecated(note = "use gpui_base::TextSelection::clear instead")]
102    fn clear_text_selection(&mut self, cx: &mut App);
103
104    /// Ends the in-progress window-level text selection drag (if any).
105    #[deprecated(note = "use gpui_base::TextSelection::end instead")]
106    fn end_text_selection(&mut self, cx: &mut App);
107}
108
109impl WindowExt for Window {
110    #[inline]
111    fn open_sheet<F>(&mut self, cx: &mut App, build: F)
112    where
113        F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static,
114    {
115        self.open_sheet_at(Placement::Right, cx, build)
116    }
117
118    #[inline]
119    fn open_sheet_at<F>(&mut self, placement: Placement, cx: &mut App, build: F)
120    where
121        F: Fn(Sheet, &mut Window, &mut App) -> Sheet + 'static,
122    {
123        Root::update(self, cx, move |root, window, cx| {
124            root.open_sheet_at(placement, build, window, cx);
125        })
126    }
127
128    #[inline]
129    fn has_active_sheet(&mut self, cx: &mut App) -> bool {
130        Root::read(self, cx).active_sheet.is_some()
131    }
132
133    #[inline]
134    fn close_sheet(&mut self, cx: &mut App) {
135        Root::update(self, cx, |root, window, cx| {
136            root.close_sheet(window, cx);
137        })
138    }
139
140    #[inline]
141    fn open_dialog<F>(&mut self, cx: &mut App, build: F)
142    where
143        F: Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static,
144    {
145        Root::update(self, cx, move |root, window, cx| {
146            root.open_dialog(build, window, cx);
147        })
148    }
149
150    #[inline]
151    fn open_alert_dialog<F>(&mut self, cx: &mut App, build: F)
152    where
153        F: Fn(AlertDialog, &mut Window, &mut App) -> AlertDialog + 'static,
154    {
155        self.open_dialog(cx, move |_, window, cx| {
156            build(AlertDialog::new(cx), window, cx).build_surface(window, cx)
157        })
158    }
159
160    #[inline]
161    fn has_active_dialog(&mut self, cx: &mut App) -> bool {
162        Root::read(self, cx).active_dialogs.len() > 0
163    }
164
165    #[inline]
166    fn close_dialog(&mut self, cx: &mut App) {
167        Root::update(self, cx, |root, window, cx| {
168            root.close_dialog(window, cx);
169        })
170    }
171
172    #[inline]
173    fn close_all_dialogs(&mut self, cx: &mut App) {
174        Root::update(self, cx, |root, window, cx| {
175            root.close_all_dialogs(window, cx);
176        })
177    }
178
179    #[inline]
180    fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App) {
181        let note = note.into();
182        Root::update(self, cx, |root, window, cx| {
183            root.push_notification(note, window, cx);
184        })
185    }
186
187    #[inline]
188    fn remove_notification<T: Sized + 'static>(&mut self, cx: &mut App) {
189        Root::update(self, cx, |root, window, cx| {
190            root.remove_notification::<T>(window, cx);
191        })
192    }
193
194    #[inline]
195    fn remove_notification1<T: Sized + 'static>(
196        &mut self,
197        key: impl Into<ElementId>,
198        cx: &mut App,
199    ) {
200        let key = key.into();
201        Root::update(self, cx, |root, window, cx| {
202            root.remove_notification1::<T>(key, window, cx);
203        })
204    }
205
206    #[inline]
207    fn clear_notifications(&mut self, cx: &mut App) {
208        Root::update(self, cx, |root, window, cx| {
209            root.clear_notifications(window, cx);
210        })
211    }
212
213    #[inline]
214    fn notifications(&mut self, cx: &mut App) -> Rc<Vec<Entity<Notification>>> {
215        Rc::new(Root::read(self, cx).notification.read(cx).notifications())
216    }
217
218    #[inline]
219    fn has_focused_input(&mut self, cx: &mut App) -> bool {
220        self.focused_input(cx).is_some()
221    }
222
223    fn focused_input(&mut self, cx: &mut App) -> Option<AnyInputState> {
224        let state = Root::read(self, cx).focused_input.clone()?;
225        if state.focus_handle(cx).is_focused(self) {
226            return Some(state);
227        }
228
229        // An input removed from the tree while focused never re-renders to
230        // unregister itself; drop the stale registration lazily.
231        Root::try_update(self, cx, |root, _, cx| {
232            if root.focused_input.as_ref() == Some(&state) {
233                root.focused_input = None;
234                cx.notify();
235            }
236        });
237        None
238    }
239
240    #[inline]
241    fn selected_text(&mut self, cx: &mut App) -> String {
242        gpui_base::TextSelection::selected_text(self, cx)
243    }
244
245    #[inline]
246    fn has_text_selection(&mut self, cx: &mut App) -> bool {
247        gpui_base::TextSelection::has_selection(self, cx)
248    }
249
250    #[inline]
251    fn clear_text_selection(&mut self, cx: &mut App) {
252        gpui_base::TextSelection::clear(self, cx);
253    }
254
255    #[inline]
256    fn end_text_selection(&mut self, cx: &mut App) {
257        gpui_base::TextSelection::end(self, cx);
258    }
259}