diff --git a/crates/story/src/dropdown_story.rs b/crates/story/src/dropdown_story.rs index ae0fa8b5..1b2daa69 100644 --- a/crates/story/src/dropdown_story.rs +++ b/crates/story/src/dropdown_story.rs @@ -1,70 +1,53 @@ -use std::rc::Rc; +use std::borrow::Cow; use gpui::{ - px, IntoElement, ParentElement, Render, Styled, View, ViewContext, VisualContext, WindowContext, + px, IntoElement, ParentElement, Render, SharedString, Styled, View, ViewContext, VisualContext, + WindowContext, }; use ui::{ - dropdown::{Dropdown, DropdownDelegate, DropdownItem, StringDropdownDelegate}, + dropdown::{Dropdown, DropdownItem}, h_flex, theme::ActiveTheme, v_flex, Selection, }; struct Country { - name: &'static str, - code: &'static str, + name: String, + code: String, } impl Country { - pub fn new(name: &'static str, code: &'static str) -> Self { - Self { name, code } + pub fn new(name: &str, code: &str) -> Self { + Self { + name: name.to_string(), + code: code.to_string(), + } } } -impl DropdownItem for &Country { - fn title(&self) -> &str { - self.name +impl DropdownItem for Country { + type Value = String; + + fn title(&self) -> Cow<'_, str> { + self.name.as_str().into() } - fn value(&self) -> &str { - self.code + fn value(&self) -> &Self::Value { + &self.code } } -struct CounterDelegate(Vec); -struct FuritDelegate(Vec); - pub struct DropdownStory { - country_dropdown: View>, - furit_dropdown: View>, - simple_dropdown1: View>, - simple_dropdown2: View>, -} - -impl DropdownDelegate for CounterDelegate { - fn len(&self) -> usize { - self.0.len() - } - - fn get(&self, ix: usize) -> Option { - self.0.get(ix) - } -} - -impl DropdownDelegate for FuritDelegate { - fn len(&self) -> usize { - self.0.len() - } - - fn get(&self, ix: usize) -> Option { - self.0.get(ix) - } + country_dropdown: View>>, + furit_dropdown: View>>, + simple_dropdown1: View>>, + simple_dropdown2: View>>, } impl DropdownStory { pub fn new(cx: &mut WindowContext) -> View { - let countries = CounterDelegate(vec![ + let countries = vec![ Country::new("United States", "US"), Country::new("Canada", "CA"), Country::new("Mexico", "MX"), @@ -76,26 +59,22 @@ impl DropdownStory { Country::new("Colombia", "CO"), Country::new("Venezuela", "VE"), Country::new("Ecuador", "EC"), - ]); + ]; let country_dropdown = cx.new_view(|cx| Dropdown::new("dropdown-country", countries, Some(6), cx)); - let furits = FuritDelegate( - [ - "Apple", - "Orange", - "Banana", - "Grape", - "Pineapple", - "Watermelon", - "Avocado", - ] - .iter() - .map(|s| s.to_string()) - .collect(), - ); - let furit_dropdown = cx.new_view(|cx| Dropdown::new("dropdown-furits", furits, None, cx)); + let furits = vec![ + "Apple", + "Orange", + "Banana", + "Grape", + "Pineapple", + "Watermelon", + "Avocado", + ]; + let furit_dropdown = + cx.new_view(|cx| Dropdown::string_list("dropdown-furits", furits, None, cx)); cx.new_view(|cx| Self { country_dropdown, @@ -103,12 +82,7 @@ impl DropdownStory { simple_dropdown1: cx.new_view(|cx| { Dropdown::string_list( "string-list1", - Rc::new(vec![ - "QPUI".into(), - "Iced".into(), - "QT".into(), - "Cocoa".into(), - ]), + vec!["QPUI", "Iced", "QT", "Cocoa"], Some(0), cx, ) @@ -119,12 +93,7 @@ impl DropdownStory { simple_dropdown2: cx.new_view(|cx| { Dropdown::string_list( "string-list2", - Rc::new(vec![ - "Rust".into(), - "Go".into(), - "C++".into(), - "JavaScript".into(), - ]), + vec!["Rust", "Go", "C++", "JavaScript"], None, cx, ) diff --git a/crates/ui/src/dropdown.rs b/crates/ui/src/dropdown.rs index 9bea8ffd..3d35897a 100644 --- a/crates/ui/src/dropdown.rs +++ b/crates/ui/src/dropdown.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::borrow::Cow; use gpui::{ actions, deferred, div, prelude::FluentBuilder as _, px, rems, AnyElement, AppContext, @@ -31,36 +31,73 @@ use crate::{ /// A trait for items that can be displayed in a dropdown. pub trait DropdownItem { - fn title(&self) -> &str; - fn value(&self) -> &str; + type Value: Clone; + fn title(&self) -> Cow<'_, str>; + fn value(&self) -> &Self::Value; } -impl DropdownItem for &String { - fn title(&self) -> &str { - self +impl DropdownItem for String { + type Value = Self; + + fn title(&self) -> Cow<'_, str> { + self.as_str().into() } - fn value(&self) -> &str { - self + fn value(&self) -> &Self::Value { + &self } } -impl DropdownItem for &SharedString { - fn title(&self) -> &str { - self.as_ref() +impl DropdownItem for SharedString { + type Value = Self; + + fn title(&self) -> Cow<'_, str> { + self.as_ref().into() } - fn value(&self) -> &str { - self.as_ref() + fn value(&self) -> &Self::Value { + &self } } pub trait DropdownDelegate { + type Item: DropdownItem; + fn len(&self) -> usize; + fn is_empty(&self) -> bool { self.len() == 0 } - fn get(&self, ix: usize) -> Option; + + fn get(&self, ix: usize) -> Option<&Self::Item>; + + fn position(&self, value: &V) -> Option + where + Self::Item: DropdownItem, + V: PartialEq, + { + (0..self.len()).find(|&i| self.get(i).map_or(false, |item| item.value() == value)) + } +} + +impl DropdownDelegate for Vec { + type Item = T; + + fn len(&self) -> usize { + self.len() + } + + fn get(&self, ix: usize) -> Option<&Self::Item> { + self.as_slice().get(ix) + } + + fn position(&self, value: &V) -> Option + where + Self::Item: DropdownItem, + V: PartialEq, + { + self.iter().position(|v| v.value() == value) + } } struct DropdownListDelegate { @@ -121,7 +158,7 @@ where view.selected_value = self .selected_index .and_then(|ix| self.delegate.get(ix)) - .map(|item| item.value().to_string().into()); + .map(|item| item.value().clone()); view.open = false; }); } @@ -132,20 +169,6 @@ where } } -pub struct StringDropdownDelegate { - items: Rc>, -} - -impl DropdownDelegate for StringDropdownDelegate { - fn len(&self) -> usize { - self.items.len() - } - - fn get(&self, ix: usize) -> Option { - self.items.get(ix) - } -} - pub struct Dropdown { id: ElementId, focus_handle: FocusHandle, @@ -155,7 +178,7 @@ pub struct Dropdown { cleanable: bool, placeholder: SharedString, title_prefix: Option, - selected_value: Option, + selected_value: Option<::Value>, } impl Dropdown @@ -228,6 +251,18 @@ where self.update_selected_value(cx); } + pub fn set_selected_value( + &mut self, + selected_value: &::Value, + cx: &mut ViewContext, + ) where + <::Item as DropdownItem>::Value: PartialEq, + { + let delegate = self.list.read(cx).delegate(); + let selected_index = delegate.delegate.position(selected_value); + self.set_selected_index(selected_index, cx); + } + pub fn selected_index(&self, cx: &WindowContext) -> Option { self.list.read(cx).selected_index() } @@ -236,11 +271,11 @@ where self.selected_value = self .selected_index(cx) .and_then(|ix| self.list.read(cx).delegate().delegate.get(ix)) - .map(|item| item.value().to_string().into()); + .map(|item| item.value().clone()); } - pub fn selected_value(&self) -> Option { - self.selected_value.clone() + pub fn selected_value(&self) -> Option<&::Value> { + self.selected_value.as_ref() } fn up(&mut self, _: &Up, cx: &mut ViewContext) { @@ -326,18 +361,15 @@ where } } -impl Dropdown { +impl Dropdown> { pub fn string_list( id: impl Into, - items: Rc>, + items: Vec>, selected_index: Option, cx: &mut ViewContext, ) -> Self { - let delegate = StringDropdownDelegate { - items: items.clone(), - }; - - Self::new(id, delegate, selected_index, cx) + let items = items.into_iter().map(Into::into).collect(); + Self::new(id, items, selected_index, cx) } }