Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:35:22.592Z 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

right_click_menu.rs

310 lines · 10.8 KB · rust
1use std::{cell::RefCell, rc::Rc};
2
3use gpui::{
4    Anchor, AnyElement, App, Bounds, DismissEvent, DispatchPhase, Element, ElementId, Entity,
5    Focusable as _, GlobalElementId, Hitbox, HitboxBehavior, InteractiveElement, IntoElement,
6    LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Window,
7    anchored, deferred, div, px,
8};
9
10pub struct RightClickMenu<M: ManagedView> {
11    id: ElementId,
12    child_builder: Option<Box<dyn FnOnce(bool, &mut Window, &mut App) -> AnyElement + 'static>>,
13    menu_builder: Option<Rc<dyn Fn(&mut Window, &mut App) -> Option<Entity<M>> + 'static>>,
14    anchor: Option<Anchor>,
15    attach: Option<Anchor>,
16}
17
18impl<M: ManagedView> RightClickMenu<M> {
19    pub fn menu(mut self, f: impl Fn(&mut Window, &mut App) -> Entity<M> + 'static) -> Self {
20        self.menu_builder = Some(Rc::new(move |window, cx| Some(f(window, cx))));
21        self
22    }
23
24    pub fn maybe_menu(
25        mut self,
26        f: impl Fn(&mut Window, &mut App) -> Option<Entity<M>> + 'static,
27    ) -> Self {
28        self.menu_builder = Some(Rc::new(f));
29        self
30    }
31
32    pub fn trigger<F, E>(mut self, e: F) -> Self
33    where
34        F: FnOnce(bool, &mut Window, &mut App) -> E + 'static,
35        E: IntoElement + 'static,
36    {
37        self.child_builder = Some(Box::new(move |is_menu_active, window, cx| {
38            e(is_menu_active, window, cx).into_any_element()
39        }));
40        self
41    }
42
43    /// anchor defines which corner of the menu to anchor to the attachment point
44    /// (by default the cursor position, but see attach)
45    pub fn anchor(mut self, anchor: Anchor) -> Self {
46        self.anchor = Some(anchor);
47        self
48    }
49
50    /// attach defines which corner of the handle to attach the menu's anchor to
51    pub fn attach(mut self, attach: Anchor) -> Self {
52        self.attach = Some(attach);
53        self
54    }
55
56    fn with_element_state<R>(
57        &mut self,
58        global_id: &GlobalElementId,
59        window: &mut Window,
60        cx: &mut App,
61        f: impl FnOnce(&mut Self, &mut MenuHandleElementState<M>, &mut Window, &mut App) -> R,
62    ) -> R {
63        window.with_optional_element_state::<MenuHandleElementState<M>, _>(
64            Some(global_id),
65            |element_state, window| {
66                let mut element_state = element_state.unwrap().unwrap_or_default();
67                let result = f(self, &mut element_state, window, cx);
68                (result, Some(element_state))
69            },
70        )
71    }
72}
73
74/// Creates a [`RightClickMenu`]
75pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> {
76    RightClickMenu {
77        id: id.into(),
78        child_builder: None,
79        menu_builder: None,
80        anchor: None,
81        attach: None,
82    }
83}
84
85pub struct MenuHandleElementState<M> {
86    menu: Rc<RefCell<Option<Entity<M>>>>,
87    position: Rc<RefCell<Point<Pixels>>>,
88}
89
90impl<M> Clone for MenuHandleElementState<M> {
91    fn clone(&self) -> Self {
92        Self {
93            menu: Rc::clone(&self.menu),
94            position: Rc::clone(&self.position),
95        }
96    }
97}
98
99impl<M> Default for MenuHandleElementState<M> {
100    fn default() -> Self {
101        Self {
102            menu: Rc::default(),
103            position: Rc::default(),
104        }
105    }
106}
107
108pub struct RequestLayoutState {
109    child_layout_id: Option<LayoutId>,
110    child_element: Option<AnyElement>,
111    menu_element: Option<AnyElement>,
112}
113
114pub struct PrepaintState {
115    hitbox: Hitbox,
116    child_bounds: Option<Bounds<Pixels>>,
117}
118
119impl<M: ManagedView> Element for RightClickMenu<M> {
120    type RequestLayoutState = RequestLayoutState;
121    type PrepaintState = PrepaintState;
122
123    fn id(&self) -> Option<ElementId> {
124        Some(self.id.clone())
125    }
126
127    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
128        None
129    }
130
131    fn request_layout(
132        &mut self,
133        id: Option<&GlobalElementId>,
134        _inspector_id: Option<&gpui::InspectorElementId>,
135        window: &mut Window,
136        cx: &mut App,
137    ) -> (gpui::LayoutId, Self::RequestLayoutState) {
138        self.with_element_state(
139            id.unwrap(),
140            window,
141            cx,
142            |this, element_state, window, cx| {
143                let mut menu_layout_id = None;
144
145                let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
146                    let mut anchored = anchored().snap_to_window_with_margin(px(8.));
147                    if let Some(anchor) = this.anchor {
148                        anchored = anchored.anchor(anchor);
149                    }
150                    anchored = anchored.position(*element_state.position.borrow());
151
152                    let mut element = deferred(anchored.child(div().occlude().child(menu.clone())))
153                        .with_priority(1)
154                        .into_any();
155
156                    menu_layout_id = Some(element.request_layout(window, cx));
157                    element
158                });
159
160                let mut child_element = this.child_builder.take().map(|child_builder| {
161                    (child_builder)(element_state.menu.borrow().is_some(), window, cx)
162                });
163
164                let child_layout_id = child_element
165                    .as_mut()
166                    .map(|child_element| child_element.request_layout(window, cx));
167
168                let layout_id = window.request_layout(
169                    gpui::Style::default(),
170                    menu_layout_id.into_iter().chain(child_layout_id),
171                    cx,
172                );
173
174                (
175                    layout_id,
176                    RequestLayoutState {
177                        child_element,
178                        child_layout_id,
179                        menu_element,
180                    },
181                )
182            },
183        )
184    }
185
186    fn prepaint(
187        &mut self,
188        _id: Option<&GlobalElementId>,
189        _inspector_id: Option<&gpui::InspectorElementId>,
190        bounds: Bounds<Pixels>,
191        request_layout: &mut Self::RequestLayoutState,
192        window: &mut Window,
193        cx: &mut App,
194    ) -> PrepaintState {
195        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
196
197        if let Some(child) = request_layout.child_element.as_mut() {
198            child.prepaint(window, cx);
199        }
200
201        if let Some(menu) = request_layout.menu_element.as_mut() {
202            menu.prepaint(window, cx);
203        }
204
205        PrepaintState {
206            hitbox,
207            child_bounds: request_layout
208                .child_layout_id
209                .map(|layout_id| window.layout_bounds(layout_id)),
210        }
211    }
212
213    fn paint(
214        &mut self,
215        id: Option<&GlobalElementId>,
216        _inspector_id: Option<&gpui::InspectorElementId>,
217        _bounds: Bounds<gpui::Pixels>,
218        request_layout: &mut Self::RequestLayoutState,
219        prepaint_state: &mut Self::PrepaintState,
220        window: &mut Window,
221        cx: &mut App,
222    ) {
223        self.with_element_state(
224            id.unwrap(),
225            window,
226            cx,
227            |this, element_state, window, cx| {
228                if let Some(mut child) = request_layout.child_element.take() {
229                    child.paint(window, cx);
230                }
231
232                if let Some(mut menu) = request_layout.menu_element.take() {
233                    menu.paint(window, cx);
234                }
235
236                let Some(builder) = this.menu_builder.take() else {
237                    return;
238                };
239
240                let attach = this.attach;
241                let menu = element_state.menu.clone();
242                let position = element_state.position.clone();
243                let child_bounds = prepaint_state.child_bounds;
244
245                let hitbox_id = prepaint_state.hitbox.id;
246                window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
247                    if phase == DispatchPhase::Bubble
248                        && event.button == MouseButton::Right
249                        && hitbox_id.is_hovered(window)
250                    {
251                        cx.stop_propagation();
252                        window.prevent_default();
253
254                        let Some(new_menu) = (builder)(window, cx) else {
255                            return;
256                        };
257                        let menu2 = menu.clone();
258                        let previous_focus_handle = window.focused(cx);
259
260                        window
261                            .subscribe(&new_menu, cx, move |modal, _: &DismissEvent, window, cx| {
262                                if modal.focus_handle(cx).contains_focused(window, cx)
263                                    && let Some(previous_focus_handle) =
264                                        previous_focus_handle.as_ref()
265                                {
266                                    window.focus(previous_focus_handle, cx);
267                                }
268                                *menu2.borrow_mut() = None;
269                                window.refresh();
270                            })
271                            .detach();
272
273                        // Since menus are rendered in a deferred fashion, their focus handles are
274                        // not linked in the dispatch tree until after the deferred draw callback
275                        // runs. We need to wait for that to happen before focusing it, so that
276                        // calling `contains_focused` on the parent's focus handle returns `true`
277                        // when the menu is focused. This prevents the pane's tab bar buttons from
278                        // flickering when opening menus.
279                        let focus_handle = new_menu.focus_handle(cx);
280                        window.on_next_frame(move |window, _cx| {
281                            window.on_next_frame(move |window, cx| {
282                                window.focus(&focus_handle, cx);
283                            });
284                        });
285                        *menu.borrow_mut() = Some(new_menu);
286                        *position.borrow_mut() = if let Some(child_bounds) = child_bounds {
287                            if let Some(attach) = attach {
288                                child_bounds.corner(attach)
289                            } else {
290                                window.mouse_position()
291                            }
292                        } else {
293                            window.mouse_position()
294                        };
295                        window.refresh();
296                    }
297                });
298            },
299        )
300    }
301}
302
303impl<M: ManagedView> IntoElement for RightClickMenu<M> {
304    type Element = Self;
305
306    fn into_element(self) -> Self::Element {
307        self
308    }
309}
310
Served at tenant.openagents/omega Member data and write actions are omitted.