Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:54:21.764Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

modal_layer.rs

328 lines · 11.5 KB · rust
1use gpui::{
2    AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Global, ManagedView,
3    MouseButton, Subscription, WeakFocusHandle,
4};
5use ui::prelude::*;
6
7#[derive(Debug)]
8pub enum DismissDecision {
9    Dismiss(bool),
10    Pending,
11}
12
13// A modal that hosts a picker forwards its own focus handle to the inner picker,
14// so the modal layer recognizes a reopenable picker by focus identity rather than
15// requiring each modal wrapper to implement its own opt-in.
16#[derive(Default)]
17struct ReopenablePickerRegistry {
18    handles: Vec<WeakFocusHandle>,
19}
20
21impl Global for ReopenablePickerRegistry {}
22
23pub fn register_reopenable_picker(focus_handle: &FocusHandle, cx: &mut App) {
24    let registry = cx.default_global::<ReopenablePickerRegistry>();
25    registry.handles.retain(|handle| handle.upgrade().is_some());
26    if !registry.handles.iter().any(|handle| handle == focus_handle) {
27        registry.handles.push(focus_handle.downgrade());
28    }
29}
30
31pub fn deregister_reopenable_picker(focus_handle: &FocusHandle, cx: &mut App) {
32    let registry = cx.default_global::<ReopenablePickerRegistry>();
33    registry
34        .handles
35        .retain(|handle| handle.upgrade().is_some() && handle != focus_handle);
36}
37
38fn focus_handle_is_reopenable(focus_handle: &FocusHandle, cx: &App) -> bool {
39    cx.try_global::<ReopenablePickerRegistry>()
40        .is_some_and(|registry| {
41            registry
42                .handles
43                .iter()
44                .filter_map(|handle| handle.upgrade())
45                .any(|handle| &handle == focus_handle)
46        })
47}
48
49pub trait ModalView: ManagedView {
50    fn on_before_dismiss(
51        &mut self,
52        _window: &mut Window,
53        _: &mut Context<Self>,
54    ) -> DismissDecision {
55        DismissDecision::Dismiss(true)
56    }
57
58    fn fade_out_background(&self) -> bool {
59        false
60    }
61
62    fn render_bare(&self) -> bool {
63        false
64    }
65}
66
67trait ModalViewHandle {
68    fn on_before_dismiss(&mut self, window: &mut Window, cx: &mut App) -> DismissDecision;
69    fn view(&self) -> AnyView;
70    fn focus_handle(&self, cx: &App) -> FocusHandle;
71    fn subscribe_dismiss(&self, window: &mut Window, cx: &mut Context<ModalLayer>) -> Subscription;
72    fn fade_out_background(&self, cx: &mut App) -> bool;
73    fn render_bare(&self, cx: &mut App) -> bool;
74}
75
76impl<V: ModalView> ModalViewHandle for Entity<V> {
77    fn on_before_dismiss(&mut self, window: &mut Window, cx: &mut App) -> DismissDecision {
78        self.update(cx, |this, cx| this.on_before_dismiss(window, cx))
79    }
80
81    fn view(&self) -> AnyView {
82        self.clone().into()
83    }
84
85    fn focus_handle(&self, cx: &App) -> FocusHandle {
86        self.read(cx).focus_handle(cx)
87    }
88
89    fn subscribe_dismiss(&self, window: &mut Window, cx: &mut Context<ModalLayer>) -> Subscription {
90        cx.subscribe_in(self, window, |this, _, _: &DismissEvent, window, cx| {
91            this.hide_modal(window, cx);
92        })
93    }
94
95    fn fade_out_background(&self, cx: &mut App) -> bool {
96        self.read(cx).fade_out_background()
97    }
98
99    fn render_bare(&self, cx: &mut App) -> bool {
100        self.read(cx).render_bare()
101    }
102}
103
104pub struct ActiveModal {
105    modal: Box<dyn ModalViewHandle>,
106    _subscriptions: [Subscription; 2],
107    previous_focus_handle: Option<FocusHandle>,
108    focus_handle: FocusHandle,
109}
110
111pub struct ModalLayer {
112    active_modal: Option<ActiveModal>,
113    // Kept alive (hidden) rather than dropped on dismissal so `ReopenLastPicker`
114    // can reveal it again with its exact prior state. Left intact across
115    // non-reopenable modals (e.g. the command palette), which may trigger the reopen.
116    stashed_modal: Option<Box<dyn ModalViewHandle>>,
117    // Set when a reveal was requested while another modal was still active (e.g. the
118    // which-key popup that is dismissed only after the reopen action fires). The reveal
119    // then happens once that modal closes, so it doesn't matter whether the triggering
120    // modal is dismissed before or after the action dispatches.
121    reveal_stash_when_free: bool,
122    dismiss_on_focus_lost: bool,
123}
124
125pub(crate) struct ModalOpenedEvent;
126
127impl EventEmitter<ModalOpenedEvent> for ModalLayer {}
128
129impl Default for ModalLayer {
130    fn default() -> Self {
131        Self::new()
132    }
133}
134
135impl ModalLayer {
136    pub fn new() -> Self {
137        Self {
138            active_modal: None,
139            stashed_modal: None,
140            reveal_stash_when_free: false,
141            dismiss_on_focus_lost: false,
142        }
143    }
144
145    /// Toggles a modal of type `V`. If a modal of the same type is currently active,
146    /// it will be hidden. If a different modal is active, it will be replaced with the new one.
147    /// If no modal is active, the new modal will be shown.
148    ///
149    /// If closing the current modal fails (e.g., due to `on_before_dismiss` returning
150    /// `DismissDecision::Dismiss(false)` or `DismissDecision::Pending`), the new modal
151    /// will not be shown.
152    pub fn toggle_modal<V, B>(&mut self, window: &mut Window, cx: &mut Context<Self>, build_view: B)
153    where
154        V: ModalView,
155        B: FnOnce(&mut Window, &mut Context<V>) -> V,
156    {
157        // Opening a modal explicitly supersedes any reveal that was waiting for the
158        // layer to become free.
159        self.reveal_stash_when_free = false;
160        if let Some(active_modal) = &self.active_modal {
161            let should_close = active_modal.modal.view().downcast::<V>().is_ok();
162            let did_close = self.hide_modal(window, cx);
163            if should_close || !did_close {
164                return;
165            }
166        }
167        let new_modal = cx.new(|cx| build_view(window, cx));
168        self.show_modal(Box::new(new_modal), window, cx);
169        cx.emit(ModalOpenedEvent);
170    }
171
172    /// Shows a modal and sets up subscriptions for dismiss events and focus tracking.
173    /// The modal is automatically focused after being shown.
174    fn show_modal(
175        &mut self,
176        modal: Box<dyn ModalViewHandle>,
177        window: &mut Window,
178        cx: &mut Context<Self>,
179    ) {
180        let focus_handle = cx.focus_handle();
181        let modal_focus_handle = modal.focus_handle(cx);
182        let dismiss_subscription = modal.subscribe_dismiss(window, cx);
183        self.active_modal = Some(ActiveModal {
184            modal,
185            _subscriptions: [
186                dismiss_subscription,
187                cx.on_focus_out(&focus_handle, window, |this, _event, window, cx| {
188                    if this.dismiss_on_focus_lost {
189                        this.hide_modal(window, cx);
190                    }
191                }),
192            ],
193            previous_focus_handle: window.focused(cx),
194            focus_handle,
195        });
196        cx.defer_in(window, move |_, window, cx| {
197            window.focus(&modal_focus_handle, cx);
198        });
199        cx.notify();
200    }
201
202    /// Reveals the most recently stashed reopenable modal, if any. Returns whether
203    /// a modal was revealed.
204    pub fn reveal_stashed_modal(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
205        if self.stashed_modal.is_none() {
206            return false;
207        }
208        if self.active_modal.is_some() {
209            // Another modal is still closing (e.g. the which-key popup that dismisses
210            // only after this action fires). Reveal once it is gone; see `hide_modal`.
211            self.reveal_stash_when_free = true;
212            return false;
213        }
214        let Some(modal) = self.stashed_modal.take() else {
215            return false;
216        };
217        self.show_modal(modal, window, cx);
218        cx.emit(ModalOpenedEvent);
219        true
220    }
221
222    /// Attempts to hide the currently active modal.
223    ///
224    /// The modal's `on_before_dismiss` method is called to determine if dismissal should proceed.
225    /// If dismissal is allowed, the modal is removed and focus is restored to the previously
226    /// focused element.
227    ///
228    /// Returns `true` if the modal was successfully hidden, `false` otherwise.
229    pub fn hide_modal(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
230        let Some(active_modal) = self.active_modal.as_mut() else {
231            self.dismiss_on_focus_lost = false;
232            return false;
233        };
234
235        match active_modal.modal.on_before_dismiss(window, cx) {
236            DismissDecision::Dismiss(should_dismiss) => {
237                if !should_dismiss {
238                    self.dismiss_on_focus_lost = !should_dismiss;
239                    return false;
240                }
241            }
242            DismissDecision::Pending => {
243                self.dismiss_on_focus_lost = false;
244                return false;
245            }
246        }
247
248        if let Some(active_modal) = self.active_modal.take() {
249            let reopenable = focus_handle_is_reopenable(&active_modal.modal.focus_handle(cx), cx);
250            if let Some(previous_focus) = active_modal.previous_focus_handle
251                && active_modal.focus_handle.contains_focused(window, cx)
252            {
253                previous_focus.focus(window, cx);
254            }
255            if reopenable {
256                self.stashed_modal = Some(active_modal.modal);
257            }
258            cx.notify();
259        }
260        self.dismiss_on_focus_lost = false;
261        // A reveal was requested while this modal was still active; now that the layer
262        // is free, honor it.
263        if self.reveal_stash_when_free && self.active_modal.is_none() {
264            self.reveal_stash_when_free = false;
265            self.reveal_stashed_modal(window, cx);
266        }
267        true
268    }
269
270    /// Returns the currently active modal if it is of type `V`.
271    pub fn active_modal<V>(&self) -> Option<Entity<V>>
272    where
273        V: 'static,
274    {
275        let active_modal = self.active_modal.as_ref()?;
276        active_modal.modal.view().downcast::<V>().ok()
277    }
278
279    pub fn has_active_modal(&self) -> bool {
280        self.active_modal.is_some()
281    }
282}
283
284impl Render for ModalLayer {
285    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
286        let Some(active_modal) = &self.active_modal else {
287            return div().into_any_element();
288        };
289
290        if active_modal.modal.render_bare(cx) {
291            return active_modal.modal.view().into_any_element();
292        }
293
294        div()
295            .absolute()
296            .size_full()
297            .inset_0()
298            .occlude()
299            .when(active_modal.modal.fade_out_background(cx), |this| {
300                let mut background = cx.theme().colors().elevated_surface_background;
301                background.fade_out(0.2);
302                this.bg(background)
303            })
304            .on_mouse_down(
305                MouseButton::Left,
306                cx.listener(|this, _, window, cx| {
307                    this.hide_modal(window, cx);
308                }),
309            )
310            .child(
311                v_flex()
312                    .h(px(0.0))
313                    .top_20()
314                    .items_center()
315                    .track_focus(&active_modal.focus_handle)
316                    .child(
317                        h_flex()
318                            .occlude()
319                            .child(active_modal.modal.view())
320                            .on_mouse_down(MouseButton::Left, |_, _, cx| {
321                                cx.stop_propagation();
322                            }),
323                    ),
324            )
325            .into_any_element()
326    }
327}
328
Served at tenant.openagents/omega Member data and write actions are omitted.