451 lines · 17.4 KB · Rust
RawDownload
use gpui::{KeyContext, canvas};use settings::Settings;use theme_settings::ThemeSettings;use ui::{    ActiveTheme, Color, Context, Disableable, DocumentationAside, DocumentationSide, FluentBuilder,    InteractiveElement, IntoElement, Label, LabelCommon, ListItem, ListItemSpacing, ParentElement,    Render, ScrollAxes, Scrollbars, Styled, StyledExt, Window, WithScrollbar, div, h_flex,    rems_from_px, utils::WithRemSize, v_flex,}; use crate::shape::Shape;use crate::{    ElementContainer, Picker, PickerDelegate, PickerEditorPosition, Preview, ToggleMultiSelect,    head::Head,    preview::Layout,    render::window_controls::{Bottom, Left, LeftCorner, Middle, Right, RightCorner},};use crate::{persistence, preview};use gpui::Action as _;use gpui::Focusable as _;use std::sync::Arc;use ui::{Divider, Tooltip, prelude::*};use ui_input::ErasedEditor; pub mod window_controls; impl<D: PickerDelegate> Render for Picker<D> {    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        self.finish_any_completed_resize(window, cx);        // toggle between BelowForced and Right based on whether it'd clamp if        // horizontal        let rendered_layout = self.preview_layout_rendered(window);        let content = match &self.preview {            Some(                preview @ Preview {                    layout: Layout::Below,                    ..                },            ) => self                .render_with_preview_below(preview, window, cx)                .into_any_element(),            // render sideways based on bounds            Some(                preview @ Preview {                    layout: Layout::Right,                    ..                },            ) if rendered_layout == Some(Layout::Below) => self                .render_with_preview_below(preview, window, cx)                .into_any_element(),            Some(                preview @ Preview {                    layout: Layout::Right,                    ..                },            ) => self                .render_with_preview_right(preview, window, cx)                .into_any_element(),            Some(Preview {                layout: Layout::Hidden,                ..            })            | None => self.render_results(window, cx).into_any_element(),        };         // The border, background and rounding wrap the whole picker (results and        // preview together). When there's a preview, clip the contents so its        // corners follow the rounded border. Avoid clipping otherwise, so a        // documentation aside (which is positioned outside the picker) isn't cut        // off.        let has_preview = self.preview.is_some();        let content = div()            .when(self.draws_own_container(), |this| this.elevation_3(cx))            .when(has_preview, |this| this.overflow_hidden())            .child(content);         let layout = self            .preview_layout_rendered(window)            .unwrap_or(Layout::Hidden);         div()            .relative()            .child(content)            .when(self.is_resizable(), |this| {                this.left(self.shape.horizontal_offset(window))                    .child(self.render_resize(Left, window, cx))                    .child(self.render_resize(Right(layout), window, cx))                    .child(self.render_resize(Bottom(layout), window, cx))                    .child(self.render_resize(LeftCorner(layout), window, cx))                    .child(self.render_resize(RightCorner(layout), window, cx))            })    }} impl<D: PickerDelegate> Picker<D> {    fn render_editor(        &self,        editor: &Arc<dyn ErasedEditor>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> gpui::Div {        if let Some(custom) = self.delegate.render_editor(editor, window, cx) {            return custom;        }        let editor_position = self.delegate.editor_position();         v_flex()            .when(editor_position == PickerEditorPosition::End, |this| {                this.child(Divider::horizontal())            })            .child(                h_flex()                    .h_9()                    .px_2p5()                    .flex_none()                    .overflow_hidden()                    .child(div().flex_1().child(editor.render(window, cx)))                    .children(self.delegate.searchbar_trailer(window, cx))                    .when(self.delegate.supports_multi_select(), |this| {                        this.child(self.render_multi_select_toggle(cx))                    }),            )            .when(editor_position == PickerEditorPosition::Start, |this| {                this.child(Divider::horizontal())            })    }     /// The multi-select toggle is picker-owned so it can reflect the mode,    /// which delegates don't know about.    fn render_multi_select_toggle(&self, cx: &mut Context<Self>) -> impl IntoElement {        let active = self.select_instead_of_open;        let focus_handle = self.focus_handle(cx);        IconButton::new("picker-multi-select-toggle", IconName::FileMultiple)            .icon_size(IconSize::Small)            .toggle_state(active)            .tooltip(move |_window, cx| {                Tooltip::for_action_in("Toggle Multi Select", &ToggleMultiSelect, &focus_handle, cx)            })            .on_click(cx.listener(|_, _, window, cx| {                window.dispatch_action(ToggleMultiSelect.boxed_clone(), cx);            }))    }     pub(crate) fn render_results(        &self,        window: &mut Window,        cx: &mut Context<Self>,    ) -> impl IntoElement {        let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);        let window_size = window.viewport_size();        let rem_size = window.rem_size();        let is_wide_window = window_size.width / rem_size > rems_from_px(800.).0;         let aside = self.delegate.documentation_aside(window, cx);         let editor_position = self.delegate.editor_position();        let picker_bounds = self.picker_bounds.clone();         let mut key_context = KeyContext::default();        key_context.add("Picker");        if self.preview.is_some() {            key_context.add("with_preview");        }         let menu = v_flex()            .key_context(key_context)            .relative()            .map(|this| {                self.shape.apply_results_size(                    self.preview_layout_rendered(window),                    &self.size_bounds,                    self.fill_height(),                    this,                    window,                )            })            .child(                canvas(                    move |bounds, _window, _cx| {                        picker_bounds.set(Some(bounds));                    },                    |_bounds, _state, _window, _cx| {},                )                .size_full()                .absolute()                .top_0()                .left_0(),            )            .on_action(cx.listener(Self::select_next))            .on_action(cx.listener(Self::select_previous))            .on_action(cx.listener(Self::editor_move_down))            .on_action(cx.listener(Self::editor_move_up))            .on_action(cx.listener(Self::select_first))            .on_action(cx.listener(Self::select_last))            .on_action(cx.listener(Self::cancel))            .on_action(cx.listener(Self::confirm))            .on_action(cx.listener(Self::secondary_confirm))            .on_action(cx.listener(Self::confirm_completion))            .on_action(cx.listener(Self::confirm_input))            .on_action(cx.listener(Self::toggle_preview))            .on_action(cx.listener(Self::set_preview_right))            .on_action(cx.listener(Self::set_preview_below))            .on_action(cx.listener(Self::set_preview_hidden))            .on_action(cx.listener(Self::toggle_actions_menu))            .on_action(cx.listener(Self::toggle_multi_select))            .on_action(cx.listener(Self::multi_select_next))            .children(match &self.head {                Head::Editor(editor) => {                    if editor_position == PickerEditorPosition::Start {                        let editor = editor.clone();                        Some(self.render_editor(&editor, window, cx))                    } else {                        None                    }                }                Head::Empty(empty_head) => Some(h_flex().child(empty_head.clone())),            })            .when(self.delegate.match_count() > 0, |el| {                el.child(                    v_flex()                        .id("element-container")                        .relative()                        .flex_grow_1()                        .min_h_0()                        .when_some(                            self.shape.results_max_height(                                &self.size_bounds,                                self.fill_height(),                                window,                            ),                            |this, max_height| this.max_h(max_height),                        )                        .overflow_hidden()                        .children(self.delegate.render_header(window, cx))                        .child(self.render_element_container(cx))                        .when(self.show_scrollbar, |this| {                            let base_scrollbar_config = Scrollbars::new(ScrollAxes::Vertical);                             this.map(|this| match &self.element_container {                                ElementContainer::List(state) => this.custom_scrollbars(                                    base_scrollbar_config.tracked_scroll_handle(state),                                    window,                                    cx,                                ),                                ElementContainer::UniformList(state) => this.custom_scrollbars(                                    base_scrollbar_config.tracked_scroll_handle(state),                                    window,                                    cx,                                ),                            })                        }),                )            })            .when(self.delegate.match_count() == 0, |el| {                el.when_some(self.delegate.no_matches_text(window, cx), |el, text| {                    el.child(                        v_flex().flex_grow_1().py_2().child(                            ListItem::new("empty_state")                                .inset(true)                                .spacing(ListItemSpacing::Sparse)                                .disabled(true)                                .child(Label::new(text).color(Color::Muted)),                        ),                    )                })            })            .children(self.render_footer(window, cx))            .children(match &self.head {                Head::Editor(editor) => {                    if editor_position == PickerEditorPosition::End {                        let editor = editor.clone();                        Some(self.render_editor(&editor, window, cx))                    } else {                        None                    }                }                Head::Empty(empty_head) => Some(div().child(empty_head.clone())),            });         let Some(aside) = aside else {            return menu;        };         let render_aside = |aside: DocumentationAside, cx: &mut Context<Self>| {            WithRemSize::new(ui_font_size)                .occlude()                .elevation_2(cx)                .w_full()                .p_2()                .overflow_hidden()                .when(is_wide_window, |this| this.max_w_96())                .when(!is_wide_window, |this| this.max_w_48())                .child((aside.render)(cx))        };         if is_wide_window {            let aside_index = self.delegate.documentation_aside_index();            let picker_bounds = self.picker_bounds.get();            let item_bounds =                aside_index.and_then(|ix| self.item_bounds.borrow().get(&ix).copied());             let item_position = match (picker_bounds, item_bounds) {                (Some(picker_bounds), Some(item_bounds)) => {                    let relative_top = item_bounds.origin.y - picker_bounds.origin.y;                    let height = item_bounds.size.height;                    Some((relative_top, height))                }                _ => None,            };             div()                .relative()                .child(menu)                // Only render the aside once we have bounds to avoid flicker                .when_some(item_position, |this, (top, height)| {                    this.child(                        h_flex()                            .absolute()                            .when(aside.side == DocumentationSide::Left, |el| {                                el.right_full().mr_1()                            })                            .when(aside.side == DocumentationSide::Right, |el| {                                el.left_full().ml_1()                            })                            .top(top)                            .h(height)                            .child(render_aside(aside, cx)),                    )                })        } else {            v_flex()                .w_full()                .gap_1()                .justify_end()                .child(render_aside(aside, cx))                .child(menu)        }    }     fn render_with_preview_below(        &self,        preview: &Preview,        window: &mut Window,        cx: &mut Context<Self>,    ) -> impl IntoElement {        h_flex()            .relative()            .child(                v_flex()                    .h(self                        .shape                        .height(Some(preview::Layout::Below), &self.size_bounds, window))                    .child(                        div()                            .h(self.shape.results_height(                                preview::Layout::Below,                                &self.size_bounds,                                window,                            ))                            .overflow_hidden()                            .child(self.render_results(window, cx)),                    )                    .child(                        div()                            .h(self.shape.preview_height(                                preview::Layout::Below,                                &self.size_bounds,                                window,                            ))                            .border_t_1()                            .border_color(cx.theme().colors().border_variant)                            .child(preview.render(cx)),                    ),            )            .when(self.is_resizable(), |this| {                this.child(self.render_resize(                    window_controls::Middle(preview::Layout::Below),                    window,                    cx,                ))            })    }     pub(crate) fn render_with_preview_right(        &self,        preview: &Preview,        window: &mut Window,        cx: &mut Context<Self>,    ) -> impl IntoElement {        v_flex()            .relative()            .child(                h_flex()                    .h(self                        .shape                        .height(Some(Layout::Right), &self.size_bounds, window))                    .child(                        div()                            .flex_1()                            .h_full()                            .overflow_hidden()                            .child(self.render_results(window, cx)),                    )                    .child(                        div()                            .w(self                                .shape                                .preview_width(Layout::Right, &self.size_bounds, window))                            .map(|this| {                                self.shape.apply_height(                                    Some(Layout::Right),                                    &self.size_bounds,                                    this,                                    window,                                )                            })                            .border_l_1()                            .border_color(cx.theme().colors().border_variant)                            .overflow_hidden()                            .child(preview.render(cx)),                    ),            )            .when(self.is_resizable(), |this| {                this.child(self.render_resize(Middle(Layout::Right), window, cx))            })    }     fn finish_any_completed_resize(        &mut self,        window: &mut Window,        cx: &mut Context<'_, Picker<D>>,    ) {        if let Shape::Resizing(pos) = self.shape            && !cx.has_active_drag()        {            let centered =                Shape::centered_and_relative(pos, self.preview_layout_rendered(window), window);            persistence::store_shape_for_this_layout(                D::name(),                self.preview_layout(),                centered,                window,                cx,            );            self.shape = Shape::HorizontallyCentered(centered);            if let Some(preview) = &mut self.preview {                preview.adjust_to_new_size(window, cx);            }        }    }}