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}