Skip to main content

gpui_component/
window_ext.rs

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