model_selector_components.rs

  1use gpui::{Action, FocusHandle, prelude::*};
  2use ui::{ElevationIndex, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
  3
  4enum ModelIcon {
  5    Name(IconName),
  6    Path(SharedString),
  7}
  8
  9#[derive(IntoElement)]
 10pub struct ModelSelectorHeader {
 11    title: SharedString,
 12    has_border: bool,
 13}
 14
 15impl ModelSelectorHeader {
 16    pub fn new(title: impl Into<SharedString>, has_border: bool) -> Self {
 17        Self {
 18            title: title.into(),
 19            has_border,
 20        }
 21    }
 22}
 23
 24impl RenderOnce for ModelSelectorHeader {
 25    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 26        div()
 27            .px_2()
 28            .pb_1()
 29            .when(self.has_border, |this| {
 30                this.mt_1()
 31                    .pt_2()
 32                    .border_t_1()
 33                    .border_color(cx.theme().colors().border_variant)
 34            })
 35            .child(
 36                Label::new(self.title)
 37                    .size(LabelSize::XSmall)
 38                    .color(Color::Muted),
 39            )
 40    }
 41}
 42
 43#[derive(IntoElement)]
 44pub struct ModelSelectorListItem {
 45    index: usize,
 46    title: SharedString,
 47    icon: Option<ModelIcon>,
 48    is_selected: bool,
 49    is_focused: bool,
 50    is_favorite: bool,
 51    on_toggle_favorite: Option<Box<dyn Fn(&App) + 'static>>,
 52}
 53
 54impl ModelSelectorListItem {
 55    pub fn new(index: usize, title: impl Into<SharedString>) -> Self {
 56        Self {
 57            index,
 58            title: title.into(),
 59            icon: None,
 60            is_selected: false,
 61            is_focused: false,
 62            is_favorite: false,
 63            on_toggle_favorite: None,
 64        }
 65    }
 66
 67    pub fn icon(mut self, icon: IconName) -> Self {
 68        self.icon = Some(ModelIcon::Name(icon));
 69        self
 70    }
 71
 72    pub fn icon_path(mut self, path: SharedString) -> Self {
 73        self.icon = Some(ModelIcon::Path(path));
 74        self
 75    }
 76
 77    pub fn is_selected(mut self, is_selected: bool) -> Self {
 78        self.is_selected = is_selected;
 79        self
 80    }
 81
 82    pub fn is_focused(mut self, is_focused: bool) -> Self {
 83        self.is_focused = is_focused;
 84        self
 85    }
 86
 87    pub fn is_favorite(mut self, is_favorite: bool) -> Self {
 88        self.is_favorite = is_favorite;
 89        self
 90    }
 91
 92    pub fn on_toggle_favorite(mut self, handler: impl Fn(&App) + 'static) -> Self {
 93        self.on_toggle_favorite = Some(Box::new(handler));
 94        self
 95    }
 96}
 97
 98impl RenderOnce for ModelSelectorListItem {
 99    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
100        let model_icon_color = if self.is_selected {
101            Color::Accent
102        } else {
103            Color::Muted
104        };
105
106        let is_favorite = self.is_favorite;
107
108        ListItem::new(self.index)
109            .inset(true)
110            .spacing(ListItemSpacing::Sparse)
111            .toggle_state(self.is_focused)
112            .child(
113                h_flex()
114                    .w_full()
115                    .gap_1p5()
116                    .when_some(self.icon, |this, icon| {
117                        this.child(
118                            match icon {
119                                ModelIcon::Name(icon_name) => Icon::new(icon_name),
120                                ModelIcon::Path(icon_path) => Icon::from_external_svg(icon_path),
121                            }
122                            .color(model_icon_color)
123                            .size(IconSize::Small),
124                        )
125                    })
126                    .child(Label::new(self.title).truncate()),
127            )
128            .end_slot(div().pr_2().when(self.is_selected, |this| {
129                this.child(Icon::new(IconName::Check).color(Color::Accent))
130            }))
131            .end_hover_slot(div().pr_1p5().when_some(self.on_toggle_favorite, {
132                |this, handle_click| {
133                    let (icon, color, tooltip) = if is_favorite {
134                        (IconName::StarFilled, Color::Accent, "Unfavorite Model")
135                    } else {
136                        (IconName::Star, Color::Default, "Favorite Model")
137                    };
138                    this.child(
139                        IconButton::new(("toggle-favorite", self.index), icon)
140                            .layer(ElevationIndex::ElevatedSurface)
141                            .icon_color(color)
142                            .icon_size(IconSize::Small)
143                            .tooltip(Tooltip::text(tooltip))
144                            .on_click(move |_, _, cx| (handle_click)(cx)),
145                    )
146                }
147            }))
148    }
149}
150
151#[derive(IntoElement)]
152pub struct ModelSelectorFooter {
153    action: Box<dyn Action>,
154    focus_handle: FocusHandle,
155}
156
157impl ModelSelectorFooter {
158    pub fn new(action: Box<dyn Action>, focus_handle: FocusHandle) -> Self {
159        Self {
160            action,
161            focus_handle,
162        }
163    }
164}
165
166impl RenderOnce for ModelSelectorFooter {
167    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
168        let action = self.action;
169        let focus_handle = self.focus_handle;
170
171        h_flex()
172            .w_full()
173            .p_1p5()
174            .border_t_1()
175            .border_color(cx.theme().colors().border_variant)
176            .child(
177                Button::new("configure", "Configure")
178                    .full_width()
179                    .style(ButtonStyle::Outlined)
180                    .key_binding(
181                        KeyBinding::for_action_in(action.as_ref(), &focus_handle, cx)
182                            .map(|kb| kb.size(rems_from_px(12.))),
183                    )
184                    .on_click(move |_, window, cx| {
185                        window.dispatch_action(action.boxed_clone(), cx);
186                    }),
187            )
188    }
189}