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