palette.rs

  1use std::marker::PhantomData;
  2
  3use crate::prelude::*;
  4use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
  5
  6#[derive(Element)]
  7pub struct Palette<S: 'static + Send + Sync> {
  8    id: ElementId,
  9    state_type: PhantomData<S>,
 10    input_placeholder: SharedString,
 11    empty_string: SharedString,
 12    items: Vec<PaletteItem<S>>,
 13    default_order: OrderMethod,
 14}
 15
 16impl<S: 'static + Send + Sync> Palette<S> {
 17    pub fn new(id: impl Into<ElementId>) -> Self {
 18        Self {
 19            id: id.into(),
 20            state_type: PhantomData,
 21            input_placeholder: "Find something...".into(),
 22            empty_string: "No items found.".into(),
 23            items: vec![],
 24            default_order: OrderMethod::default(),
 25        }
 26    }
 27
 28    pub fn items(mut self, items: Vec<PaletteItem<S>>) -> Self {
 29        self.items = items;
 30        self
 31    }
 32
 33    pub fn placeholder(mut self, input_placeholder: impl Into<SharedString>) -> Self {
 34        self.input_placeholder = input_placeholder.into();
 35        self
 36    }
 37
 38    pub fn empty_string(mut self, empty_string: impl Into<SharedString>) -> Self {
 39        self.empty_string = empty_string.into();
 40        self
 41    }
 42
 43    // TODO: Hook up sort order
 44    pub fn default_order(mut self, default_order: OrderMethod) -> Self {
 45        self.default_order = default_order;
 46        self
 47    }
 48
 49    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
 50        let color = ThemeColor::new(cx);
 51
 52        v_stack()
 53            .id(self.id.clone())
 54            .w_96()
 55            .rounded_lg()
 56            .bg(color.elevated_surface)
 57            .border()
 58            .border_color(color.border)
 59            .child(
 60                v_stack()
 61                    .gap_px()
 62                    .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child(
 63                        Label::new(self.input_placeholder.clone()).color(LabelColor::Placeholder),
 64                    )))
 65                    .child(div().h_px().w_full().bg(color.filled_element))
 66                    .child(
 67                        v_stack()
 68                            .id("items")
 69                            .py_0p5()
 70                            .px_1()
 71                            .grow()
 72                            .max_h_96()
 73                            .overflow_y_scroll()
 74                            .children(
 75                                vec![if self.items.is_empty() {
 76                                    Some(
 77                                        h_stack().justify_between().px_2().py_1().child(
 78                                            Label::new(self.empty_string.clone())
 79                                                .color(LabelColor::Muted),
 80                                        ),
 81                                    )
 82                                } else {
 83                                    None
 84                                }]
 85                                .into_iter()
 86                                .flatten(),
 87                            )
 88                            .children(self.items.drain(..).enumerate().map(|(index, item)| {
 89                                h_stack()
 90                                    .id(index)
 91                                    .justify_between()
 92                                    .px_2()
 93                                    .py_0p5()
 94                                    .rounded_lg()
 95                                    .hover(|style| style.bg(color.ghost_element_hover))
 96                                    .active(|style| style.bg(color.ghost_element_active))
 97                                    .child(item)
 98                            })),
 99                    ),
100            )
101    }
102}
103
104#[derive(Element)]
105pub struct PaletteItem<S: 'static + Send + Sync> {
106    pub label: SharedString,
107    pub sublabel: Option<SharedString>,
108    pub keybinding: Option<Keybinding<S>>,
109}
110
111impl<S: 'static + Send + Sync> PaletteItem<S> {
112    pub fn new(label: impl Into<SharedString>) -> Self {
113        Self {
114            label: label.into(),
115            sublabel: None,
116            keybinding: None,
117        }
118    }
119
120    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
121        self.label = label.into();
122        self
123    }
124
125    pub fn sublabel(mut self, sublabel: impl Into<Option<SharedString>>) -> Self {
126        self.sublabel = sublabel.into();
127        self
128    }
129
130    pub fn keybinding<K>(mut self, keybinding: K) -> Self
131    where
132        K: Into<Option<Keybinding<S>>>,
133    {
134        self.keybinding = keybinding.into();
135        self
136    }
137
138    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
139        let color = ThemeColor::new(cx);
140
141        div()
142            .flex()
143            .flex_row()
144            .grow()
145            .justify_between()
146            .child(
147                v_stack()
148                    .child(Label::new(self.label.clone()))
149                    .children(self.sublabel.clone().map(|sublabel| Label::new(sublabel))),
150            )
151            .children(self.keybinding.take())
152    }
153}
154
155use gpui2::ElementId;
156#[cfg(feature = "stories")]
157pub use stories::*;
158
159#[cfg(feature = "stories")]
160mod stories {
161    use crate::{ModifierKeys, Story};
162
163    use super::*;
164
165    #[derive(Element)]
166    pub struct PaletteStory<S: 'static + Send + Sync + Clone> {
167        state_type: PhantomData<S>,
168    }
169
170    impl<S: 'static + Send + Sync + Clone> PaletteStory<S> {
171        pub fn new() -> Self {
172            Self {
173                state_type: PhantomData,
174            }
175        }
176
177        fn render(
178            &mut self,
179            _view: &mut S,
180            cx: &mut ViewContext<S>,
181        ) -> impl Element<ViewState = S> {
182            Story::container(cx)
183                .child(Story::title_for::<_, Palette<S>>(cx))
184                .child(Story::label(cx, "Default"))
185                .child(Palette::new("palette-1"))
186                .child(Story::label(cx, "With Items"))
187                .child(
188                    Palette::new("palette-2")
189                        .placeholder("Execute a command...")
190                        .items(vec![
191                            PaletteItem::new("theme selector: toggle").keybinding(
192                                Keybinding::new_chord(
193                                    ("k".to_string(), ModifierKeys::new().command(true)),
194                                    ("t".to_string(), ModifierKeys::new().command(true)),
195                                ),
196                            ),
197                            PaletteItem::new("assistant: inline assist").keybinding(
198                                Keybinding::new(
199                                    "enter".to_string(),
200                                    ModifierKeys::new().command(true),
201                                ),
202                            ),
203                            PaletteItem::new("assistant: quote selection").keybinding(
204                                Keybinding::new(">".to_string(), ModifierKeys::new().command(true)),
205                            ),
206                            PaletteItem::new("assistant: toggle focus").keybinding(
207                                Keybinding::new("?".to_string(), ModifierKeys::new().command(true)),
208                            ),
209                            PaletteItem::new("auto update: check"),
210                            PaletteItem::new("auto update: view release notes"),
211                            PaletteItem::new("branches: open recent").keybinding(Keybinding::new(
212                                "b".to_string(),
213                                ModifierKeys::new().command(true).alt(true),
214                            )),
215                            PaletteItem::new("chat panel: toggle focus"),
216                            PaletteItem::new("cli: install"),
217                            PaletteItem::new("client: sign in"),
218                            PaletteItem::new("client: sign out"),
219                            PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
220                                "escape".to_string(),
221                                ModifierKeys::new(),
222                            )),
223                        ]),
224                )
225        }
226    }
227}