Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:42:03.384Z 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

render.rs

454 lines · 17.9 KB · rust
1use gpui::{KeyContext, canvas};
2use settings::Settings;
3use theme_settings::ThemeSettings;
4use ui::{
5    ActiveTheme, Color, Context, Disableable, DocumentationAside, DocumentationSide, FluentBuilder,
6    InteractiveElement, IntoElement, Label, LabelCommon, ListItem, ListItemSpacing, ParentElement,
7    Render, ScrollAxes, Scrollbars, Styled, StyledExt, Window, WithScrollbar, div, h_flex,
8    rems_from_px, utils::WithRemSize, v_flex,
9};
10
11use crate::shape::Shape;
12use crate::{
13    ElementContainer, Picker, PickerDelegate, PickerEditorPosition, Preview, ToggleMultiSelect,
14    head::Head,
15    preview::Layout,
16    render::window_controls::{Bottom, Left, LeftCorner, Middle, Right, RightCorner},
17};
18use crate::{persistence, preview};
19use gpui::Action as _;
20use gpui::Focusable as _;
21use std::sync::Arc;
22use ui::{Divider, Tooltip, prelude::*};
23use ui_input::ErasedEditor;
24
25pub mod window_controls;
26
27impl<D: PickerDelegate> Render for Picker<D> {
28    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29        self.finish_any_completed_resize(window, cx);
30        // toggle between BelowForced and Right based on whether it'd clamp if
31        // horizontal
32        let rendered_layout = self.preview_layout_rendered(window);
33        let content = match &self.preview {
34            Some(
35                preview @ Preview {
36                    layout: Layout::Below,
37                    ..
38                },
39            ) => self
40                .render_with_preview_below(preview, window, cx)
41                .into_any_element(),
42            // render sideways based on bounds
43            Some(
44                preview @ Preview {
45                    layout: Layout::Right,
46                    ..
47                },
48            ) if rendered_layout == Some(Layout::Below) => self
49                .render_with_preview_below(preview, window, cx)
50                .into_any_element(),
51            Some(
52                preview @ Preview {
53                    layout: Layout::Right,
54                    ..
55                },
56            ) => self
57                .render_with_preview_right(preview, window, cx)
58                .into_any_element(),
59            Some(Preview {
60                layout: Layout::Hidden,
61                ..
62            })
63            | None => self.render_results(window, cx).into_any_element(),
64        };
65
66        // The border, background and rounding wrap the whole picker (results and
67        // preview together). When there's a preview, clip the contents so its
68        // corners follow the rounded border. Avoid clipping otherwise, so a
69        // documentation aside (which is positioned outside the picker) isn't cut
70        // off.
71        let has_preview = self.preview.is_some();
72        let content = div()
73            .when(self.draws_own_container(), |this| this.elevation_3(cx))
74            .when(has_preview, |this| this.overflow_hidden())
75            .child(content);
76
77        let layout = self
78            .preview_layout_rendered(window)
79            .unwrap_or(Layout::Hidden);
80
81        div()
82            .relative()
83            .child(content)
84            .when(self.is_resizable(), |this| {
85                this.left(self.shape.horizontal_offset(window))
86                    .child(self.render_resize(Left, window, cx))
87                    .child(self.render_resize(Right(layout), window, cx))
88                    .child(self.render_resize(Bottom(layout), window, cx))
89                    .child(self.render_resize(LeftCorner(layout), window, cx))
90                    .child(self.render_resize(RightCorner(layout), window, cx))
91            })
92    }
93}
94
95impl<D: PickerDelegate> Picker<D> {
96    fn render_editor(
97        &self,
98        editor: &Arc<dyn ErasedEditor>,
99        window: &mut Window,
100        cx: &mut Context<Self>,
101    ) -> gpui::Div {
102        if let Some(custom) = self.delegate.render_editor(editor, window, cx) {
103            return custom;
104        }
105        let editor_position = self.delegate.editor_position();
106
107        v_flex()
108            .when(editor_position == PickerEditorPosition::End, |this| {
109                this.child(Divider::horizontal())
110            })
111            .child(
112                h_flex()
113                    .h_9()
114                    .px_2p5()
115                    .flex_none()
116                    .overflow_hidden()
117                    .child(div().flex_1().child(editor.render(window, cx)))
118                    .children(self.delegate.searchbar_trailer(window, cx))
119                    .when(self.delegate.supports_multi_select(), |this| {
120                        this.child(self.render_multi_select_toggle(cx))
121                    }),
122            )
123            .when(editor_position == PickerEditorPosition::Start, |this| {
124                this.child(Divider::horizontal())
125            })
126    }
127
128    /// The multi-select toggle is picker-owned so it can reflect the mode,
129    /// which delegates don't know about.
130    fn render_multi_select_toggle(&self, cx: &mut Context<Self>) -> impl IntoElement {
131        let active = self.select_instead_of_open;
132        let focus_handle = self.focus_handle(cx);
133        IconButton::new("picker-multi-select-toggle", IconName::FileMultiple)
134            .icon_size(IconSize::Small)
135            .toggle_state(active)
136            .tooltip(move |_window, cx| {
137                Tooltip::for_action_in("Toggle Multi Select", &ToggleMultiSelect, &focus_handle, cx)
138            })
139            .on_click(cx.listener(|_, _, window, cx| {
140                window.dispatch_action(ToggleMultiSelect.boxed_clone(), cx);
141            }))
142    }
143
144    pub(crate) fn render_results(
145        &self,
146        window: &mut Window,
147        cx: &mut Context<Self>,
148    ) -> impl IntoElement {
149        let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
150        let window_size = window.viewport_size();
151        let rem_size = window.rem_size();
152        let is_wide_window = window_size.width / rem_size > rems_from_px(800.).0;
153
154        let aside = self.delegate.documentation_aside(window, cx);
155
156        let editor_position = self.delegate.editor_position();
157        let picker_bounds = self.picker_bounds.clone();
158
159        let mut key_context = KeyContext::default();
160        key_context.add("Picker");
161        if self.preview.is_some() {
162            key_context.add("with_preview");
163        }
164
165        let menu = v_flex()
166            .key_context(key_context)
167            .relative()
168            .map(|this| {
169                self.shape.apply_results_size(
170                    self.preview_layout_rendered(window),
171                    &self.size_bounds,
172                    self.fill_height(),
173                    this,
174                    window,
175                )
176            })
177            .child(
178                canvas(
179                    move |bounds, _window, _cx| {
180                        picker_bounds.set(Some(bounds));
181                    },
182                    |_bounds, _state, _window, _cx| {},
183                )
184                .size_full()
185                .absolute()
186                .top_0()
187                .left_0(),
188            )
189            .on_action(cx.listener(Self::select_next))
190            .on_action(cx.listener(Self::select_previous))
191            .on_action(cx.listener(Self::editor_move_down))
192            .on_action(cx.listener(Self::editor_move_up))
193            .on_action(cx.listener(Self::select_first))
194            .on_action(cx.listener(Self::select_last))
195            .on_action(cx.listener(Self::select_child))
196            .on_action(cx.listener(Self::select_parent))
197            .on_action(cx.listener(Self::cancel))
198            .on_action(cx.listener(Self::confirm))
199            .on_action(cx.listener(Self::secondary_confirm))
200            .on_action(cx.listener(Self::confirm_completion))
201            .on_action(cx.listener(Self::confirm_input))
202            .on_action(cx.listener(Self::toggle_preview))
203            .on_action(cx.listener(Self::set_preview_right))
204            .on_action(cx.listener(Self::set_preview_below))
205            .on_action(cx.listener(Self::set_preview_hidden))
206            .on_action(cx.listener(Self::toggle_actions_menu))
207            .on_action(cx.listener(Self::toggle_multi_select))
208            .on_action(cx.listener(Self::multi_select_next))
209            .children(match &self.head {
210                Head::Editor(editor) => {
211                    if editor_position == PickerEditorPosition::Start {
212                        let editor = editor.clone();
213                        Some(self.render_editor(&editor, window, cx))
214                    } else {
215                        None
216                    }
217                }
218                Head::Empty(empty_head) => Some(h_flex().child(empty_head.clone())),
219            })
220            .when(self.delegate.match_count() > 0, |el| {
221                el.child(
222                    v_flex()
223                        .id("element-container")
224                        .relative()
225                        .flex_grow_1()
226                        .min_h_0()
227                        .when_some(
228                            self.shape.results_max_height(
229                                &self.size_bounds,
230                                self.fill_height(),
231                                window,
232                            ),
233                            |this, max_height| this.max_h(max_height),
234                        )
235                        .overflow_hidden()
236                        .children(self.delegate.render_header(window, cx))
237                        .child(self.render_element_container(cx))
238                        .when(self.show_scrollbar, |this| {
239                            let base_scrollbar_config = Scrollbars::new(ScrollAxes::Vertical);
240
241                            this.map(|this| match &self.element_container {
242                                ElementContainer::List(state) => this.custom_scrollbars(
243                                    base_scrollbar_config.tracked_scroll_handle(state),
244                                    window,
245                                    cx,
246                                ),
247                                ElementContainer::UniformList(state) => this.custom_scrollbars(
248                                    base_scrollbar_config.tracked_scroll_handle(state),
249                                    window,
250                                    cx,
251                                ),
252                            })
253                        }),
254                )
255            })
256            .when(self.delegate.match_count() == 0, |el| {
257                el.when_some(self.delegate.no_matches_text(window, cx), |el, text| {
258                    el.child(
259                        v_flex().flex_grow_1().py_2().child(
260                            ListItem::new("empty_state")
261                                .inset(true)
262                                .spacing(ListItemSpacing::Sparse)
263                                .disabled(true)
264                                .child(Label::new(text).color(Color::Muted)),
265                        ),
266                    )
267                })
268            })
269            .children(self.render_footer(window, cx))
270            .children(match &self.head {
271                Head::Editor(editor) => {
272                    if editor_position == PickerEditorPosition::End {
273                        let editor = editor.clone();
274                        Some(self.render_editor(&editor, window, cx))
275                    } else {
276                        None
277                    }
278                }
279                Head::Empty(empty_head) => Some(div().child(empty_head.clone())),
280            });
281
282        let Some(aside) = aside else {
283            return menu;
284        };
285
286        let render_aside = |aside: DocumentationAside, cx: &mut Context<Self>| {
287            WithRemSize::new(ui_font_size)
288                .occlude()
289                .elevation_2(cx)
290                .w_full()
291                .p_2()
292                .overflow_hidden()
293                .when(is_wide_window, |this| this.max_w_96())
294                .when(!is_wide_window, |this| this.max_w_48())
295                .child((aside.render)(cx))
296        };
297
298        if is_wide_window {
299            let aside_index = self.delegate.documentation_aside_index();
300            let picker_bounds = self.picker_bounds.get();
301            let item_bounds =
302                aside_index.and_then(|ix| self.item_bounds.borrow().get(&ix).copied());
303
304            let item_position = match (picker_bounds, item_bounds) {
305                (Some(picker_bounds), Some(item_bounds)) => {
306                    let relative_top = item_bounds.origin.y - picker_bounds.origin.y;
307                    let height = item_bounds.size.height;
308                    Some((relative_top, height))
309                }
310                _ => None,
311            };
312
313            div()
314                .relative()
315                .child(menu)
316                // Only render the aside once we have bounds to avoid flicker
317                .when_some(item_position, |this, (top, height)| {
318                    this.child(
319                        h_flex()
320                            .absolute()
321                            .when(aside.side == DocumentationSide::Left, |el| {
322                                el.right_full().mr_1()
323                            })
324                            .when(aside.side == DocumentationSide::Right, |el| {
325                                el.left_full().ml_1()
326                            })
327                            .top(top)
328                            .h(height)
329                            .child(render_aside(aside, cx)),
330                    )
331                })
332        } else {
333            v_flex()
334                .w_full()
335                .gap_1()
336                .justify_end()
337                .child(render_aside(aside, cx))
338                .child(menu)
339        }
340    }
341
342    fn render_with_preview_below(
343        &self,
344        preview: &Preview,
345        window: &mut Window,
346        cx: &mut Context<Self>,
347    ) -> impl IntoElement {
348        h_flex()
349            .relative()
350            .child(
351                v_flex()
352                    .h(self
353                        .shape
354                        .height(Some(preview::Layout::Below), &self.size_bounds, window))
355                    .child(
356                        div()
357                            .h(self.shape.results_height(
358                                preview::Layout::Below,
359                                &self.size_bounds,
360                                window,
361                            ))
362                            .overflow_hidden()
363                            .child(self.render_results(window, cx)),
364                    )
365                    .child(
366                        div()
367                            .h(self.shape.preview_height(
368                                preview::Layout::Below,
369                                &self.size_bounds,
370                                window,
371                            ))
372                            .border_t_1()
373                            .border_color(cx.theme().colors().border_variant)
374                            .child(preview.render(cx)),
375                    ),
376            )
377            .when(self.is_resizable(), |this| {
378                this.child(self.render_resize(
379                    window_controls::Middle(preview::Layout::Below),
380                    window,
381                    cx,
382                ))
383            })
384    }
385
386    pub(crate) fn render_with_preview_right(
387        &self,
388        preview: &Preview,
389        window: &mut Window,
390        cx: &mut Context<Self>,
391    ) -> impl IntoElement {
392        v_flex()
393            .relative()
394            .child(
395                h_flex()
396                    .h(self
397                        .shape
398                        .height(Some(Layout::Right), &self.size_bounds, window))
399                    .child(
400                        div()
401                            .flex_1()
402                            .h_full()
403                            .overflow_hidden()
404                            .child(self.render_results(window, cx)),
405                    )
406                    .child(
407                        div()
408                            .w(self
409                                .shape
410                                .preview_width(Layout::Right, &self.size_bounds, window))
411                            .map(|this| {
412                                self.shape.apply_height(
413                                    Some(Layout::Right),
414                                    &self.size_bounds,
415                                    this,
416                                    window,
417                                )
418                            })
419                            .border_l_1()
420                            .border_color(cx.theme().colors().border_variant)
421                            .overflow_hidden()
422                            .child(preview.render(cx)),
423                    ),
424            )
425            .when(self.is_resizable(), |this| {
426                this.child(self.render_resize(Middle(Layout::Right), window, cx))
427            })
428    }
429
430    fn finish_any_completed_resize(
431        &mut self,
432        window: &mut Window,
433        cx: &mut Context<'_, Picker<D>>,
434    ) {
435        if let Shape::Resizing(pos) = self.shape
436            && !cx.has_active_drag()
437        {
438            let centered =
439                Shape::centered_and_relative(pos, self.preview_layout_rendered(window), window);
440            persistence::store_shape_for_this_layout(
441                D::name(),
442                self.preview_layout(),
443                centered,
444                window,
445                cx,
446            );
447            self.shape = Shape::HorizontallyCentered(centered);
448            if let Some(preview) = &mut self.preview {
449                preview.adjust_to_new_size(window, cx);
450            }
451        }
452    }
453}
454
Served at tenant.openagents/omega Member data and write actions are omitted.