diff --git a/examples/widget-gallery/src/dropdown.rs b/examples/widget-gallery/src/dropdown.rs index 7eed6a05..639bc559 100644 --- a/examples/widget-gallery/src/dropdown.rs +++ b/examples/widget-gallery/src/dropdown.rs @@ -1,10 +1,8 @@ use strum::IntoEnumIterator; use floem::{ - peniko::{Brush, Color}, reactive::{create_effect, RwSignal, SignalGet}, - unit::UnitExt, - views::{container, dropdown::Dropdown, label, stack, svg, Decorators, SvgColor}, + views::{dropdown::Dropdown, Decorators}, IntoView, }; @@ -24,29 +22,7 @@ impl std::fmt::Display for Values { } } -const CHEVRON_DOWN: &str = r##" - -"##; - pub fn dropdown_view() -> impl IntoView { - let main_drop_view = move |item| { - stack(( - label(move || item), - container( - svg(CHEVRON_DOWN) - .style(|s| s.size(12, 12).set(SvgColor, Brush::Solid(Color::BLACK))), - ) - .style(|s| { - s.items_center() - .padding(3.) - .border_radius(7.pct()) - .hover(move |s| s.background(Color::LIGHT_GRAY)) - }), - )) - .style(|s| s.items_center().justify_between().size_full()) - .into_any() - }; - let dropdown_active_item = RwSignal::new(Values::Three); create_effect(move |_| { @@ -56,17 +32,7 @@ pub fn dropdown_view() -> impl IntoView { form::form({ (form_item("Dropdown".to_string(), 120.0, move || { - Dropdown::new_rw( - // state - dropdown_active_item, - // main view function - main_drop_view, - // iterator to build list in dropdown - Values::iter(), - // view for each item in the list - |item| label(move || item).into_any(), - ) - .keyboard_navigable() + Dropdown::basic_rw(dropdown_active_item, Values::iter()).keyboard_navigatable() }),) }) } diff --git a/src/id.rs b/src/id.rs index 6044f48a..d5fa6857 100644 --- a/src/id.rs +++ b/src/id.rs @@ -338,6 +338,8 @@ impl ViewId { } /// Set the sytem popout menu that should be shown when this view is clicked + /// + /// Adds a primary-click context menu, which opens below the view. pub fn update_popout_menu(&self, menu: impl Fn() -> Menu + 'static) { self.state().borrow_mut().popout_menu = Some(Rc::new(menu)); } diff --git a/src/style.rs b/src/style.rs index a09cdf8a..a34ce6af 100644 --- a/src/style.rs +++ b/src/style.rs @@ -794,6 +794,7 @@ macro_rules! prop { ) => { $(#[$meta])* #[derive(Default, Copy, Clone)] + #[allow(missing_docs)] $v struct $name; impl $crate::style::StyleProp for $name { type Type = $ty; diff --git a/src/views/dropdown.rs b/src/views/dropdown.rs index 2bad8da5..8a967d8d 100644 --- a/src/views/dropdown.rs +++ b/src/views/dropdown.rs @@ -4,7 +4,10 @@ use floem_reactive::{ as_child_of_current_scope, create_effect, create_updater, Scope, SignalGet, SignalUpdate, }; use floem_winit::keyboard::{Key, NamedKey}; -use peniko::kurbo::{Point, Rect}; +use peniko::{ + kurbo::{Point, Rect}, + Color, +}; use crate::{ action::{add_overlay, remove_overlay}, @@ -13,9 +16,10 @@ use crate::{ prop, prop_extractor, style::{CustomStylable, Style, StyleClass, Width}, style_class, - unit::PxPctAuto, + unit::{PxPctAuto, UnitExt}, view::{default_compute_layout, IntoView, View}, - views::{scroll, Decorators}, + views::{container, scroll, stack, svg, text, Decorators}, + AnyView, }; use super::list; @@ -23,7 +27,6 @@ use super::list; type ChildFn = dyn Fn(T) -> (Box, Scope); style_class!(pub DropdownClass); -style_class!(pub DropdownScrollClass); prop!(pub CloseOnAccept: bool {} = true); prop_extractor!(DropdownStyle { @@ -59,13 +62,12 @@ where /// # use floem::views::empty; /// # use floem::views::Decorators; /// // root view -/// empty() -/// .style(|s| -/// s.class(dropdown::DropdownClass, |s| { -/// s.set(dropdown::CloseOnAccept, false) -/// }) -/// ); -///``` +/// empty().style(|s| { +/// s.class(dropdown::DropdownClass, |s| { +/// s.set(dropdown::CloseOnAccept, false) +/// }) +/// }); +/// ``` pub struct Dropdown { id: ViewId, main_view: ViewId, @@ -162,7 +164,10 @@ impl View for Dropdown { return EventPropagation::Stop; } Event::KeyUp(ref key_event) - if key_event.key.logical_key == Key::Named(NamedKey::Enter) => + if matches!( + key_event.key.logical_key, + Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Space) + ) => { self.swap_state() } @@ -174,6 +179,31 @@ impl View for Dropdown { } impl Dropdown { + pub fn default_main_view(item: T) -> AnyView + where + T: std::fmt::Display, + { + const CHEVRON_DOWN: &str = r##" + + + + "##; + + stack(( + text(item), + container(svg(CHEVRON_DOWN).style(|s| s.size(12, 12).color(Color::BLACK))).style(|s| { + s.items_center() + .padding(3.) + .border_radius(7.pct()) + .hover(move |s| s.background(Color::LIGHT_GRAY)) + }), + )) + .style(|s| s.items_center().justify_between().size_full()) + .into_any() + } + /// Creates a new dropdown pub fn new( active_item: AIF, @@ -213,7 +243,6 @@ impl Dropdown { .on_event_stop(EventListener::FocusGained, move |_| { inner_list_id.request_focus(); }) - .class(DropdownScrollClass) .into_any() }); @@ -244,22 +273,131 @@ impl Dropdown { .class(DropdownClass) } - pub fn new_rw( - active_item: impl SignalGet + SignalUpdate + Copy + 'static, + /// Creates a basic dropdown with a read-only function for the active item. + /// + /// # Example + /// ```rust + /// # use floem::{*, views::*, reactive::*}; + /// # use floem::views::dropdown::*; + /// let active_item = RwSignal::new(3); + /// + /// Dropdown::basic(move || active_item.get(), 1..=5).on_accept(move |val| active_item.set(val))); + /// ``` + /// + /// This function is a convenience wrapper around `Dropdown::new` that uses default views + /// for the main and list items. + /// + /// See also [Dropdown::basic_rw]. + /// + /// # Arguments + /// + /// * `active_item` - A function that returns the currently selected item. + /// * `AIF` - The type of the active item function of type `T`. + /// * `T` - The type of items in the dropdown. Must implement `Clone` and `std::fmt::Display`. + /// + /// * `iterator` - An iterator that provides the items to be displayed in the dropdown list. + /// It must be `Clone` and iterate over items of type `T`. + /// * `I` - The type of the iterator. + pub fn basic(active_item: AIF, iterator: I) -> Dropdown + where + AIF: Fn() -> T + 'static, + I: IntoIterator + Clone + 'static, + T: Clone + std::fmt::Display + 'static, + { + Self::new(active_item, Self::default_main_view, iterator, |v| { + crate::views::text(v).into_any() + }) + } + + /// Creates a new dropdown with a read-write signal for the active item. + /// + /// # Example + /// ```rust + /// # use floem::{*, views::*, reactive::*}; + /// # use floem::{views::dropdown::*}; + /// let active_item = RwSignal::new(3); + /// + /// Dropdown::new_rw( + /// active_item, + /// |item| text(item).into_any(), + /// 1..=5, + /// |item| text(item).into_any(), + /// ); + /// ``` + /// + /// This function allows for more customization compared to `basic_rw` by letting you specify + /// custom view functions for both the main dropdown display and the list items. + /// + /// # Arguments + /// + /// * `active_item` - A read-write signal representing the currently selected item. + /// It must implement both `SignalGet` and `SignalUpdate`. + /// * `T` - The type of items in the dropdown. Must implement `Clone`. + /// * `AI` - The type of the active item signal. + /// + /// * `main_view` - A function that takes a value of type `T` and returns an `AnyView` + /// to be used as the main dropdown display. + /// * `iterator` - An iterator that provides the items to be displayed in the dropdown list. + /// It must be `Clone` and iterate over items of type `T`. + /// * `list_item_fn` - A function that takes a value of type `T` and returns an `AnyView` + /// to be used for each item in the dropdown list. + /// + /// # Type Parameters + /// + /// * `MF` - The type of the main view function. + /// * `I` - The type of the iterator. + /// * `LF` - The type of the list item function. + pub fn new_rw( + active_item: AI, main_view: MF, iterator: I, list_item_fn: LF, ) -> Dropdown where - MF: Fn(T) -> Box + 'static, + AI: SignalGet + SignalUpdate + Copy + 'static, + MF: Fn(T) -> AnyView + 'static, I: IntoIterator + Clone + 'static, - LF: Fn(T) -> Box + Clone + 'static, + LF: Fn(T) -> AnyView + Clone + 'static, T: Clone + 'static, { Self::new(move || active_item.get(), main_view, iterator, list_item_fn) .on_accept(move |nv| active_item.set(nv)) } + /// Creates a basic dropdown with a read-write signal for the active item. + /// + /// # Example: + /// ```rust + /// # use floem::{*, views::*, reactive::*}; + /// # use floem::{views::dropdown::*}; + /// let dropdown_active_item = RwSignal::new(3); + /// + /// Dropdown::basic_rw(dropdown_active_item, 1..=5); + /// ``` + /// + /// This function is a convenience wrapper around `Dropdown::new_rw` that uses default views + /// for the main and list items. + /// + /// # Arguments + /// + /// * `active_item` - A read-write signal representing the currently selected item. + /// It must implement `SignalGet` and `SignalUpdate`. + /// * `T` - The type of items in the dropdown. Must implement `Clone` and `std::fmt::Display`. + /// * `AI` - The type of the active item signal. + /// * `iterator` - An iterator that provides the items to be displayed in the dropdown list. + /// It must be `Clone` and iterate over items of type `T`. + /// * `I` - The type of the iterator. + pub fn basic_rw(active_item: AI, iterator: I) -> Dropdown + where + AI: SignalGet + SignalUpdate + Copy + 'static, + I: IntoIterator + Clone + 'static, + T: Clone + std::fmt::Display + 'static, + { + Self::new_rw(active_item, Self::default_main_view, iterator, |v| { + text(v).into_any() + }) + } + pub fn show_list(self, show: impl Fn() -> bool + 'static) -> Self { let id = self.id(); create_effect(move |_| { diff --git a/src/views/list.rs b/src/views/list.rs index c4948ecf..299c0569 100644 --- a/src/views/list.rs +++ b/src/views/list.rs @@ -63,7 +63,11 @@ impl List { /// ## Example /// ```rust /// use floem::views::*; -/// list(vec![1,1,2,2,3,4,5,6,7,8,9].iter().map(|val| text(val))); +/// list( +/// vec![1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9] +/// .iter() +/// .map(|val| text(val)), +/// ); /// ``` pub fn list(iterator: impl IntoIterator) -> List where @@ -139,7 +143,7 @@ where } EventPropagation::Stop } - Key::Named(NamedKey::Enter) => { + Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Space) => { list_id.update_state(ListUpdate::Accept); EventPropagation::Stop }