diff --git a/examples/widget-gallery/src/dropdown.rs b/examples/widget-gallery/src/dropdown.rs index 4ce9e3ec..ba179629 100644 --- a/examples/widget-gallery/src/dropdown.rs +++ b/examples/widget-gallery/src/dropdown.rs @@ -9,10 +9,7 @@ use floem::{ widgets::dropdown::dropdown, }; -use crate::{ - follow_popover, - form::{self, form_item}, -}; +use crate::form::{self, form_item}; #[derive(strum::EnumIter, Debug, PartialEq, Clone, Copy)] enum Values { @@ -33,43 +30,39 @@ const CHEVRON_DOWN: &str = r##""##; pub fn dropdown_view() -> impl View { - let show_list = create_rw_signal(false); - follow_popover(show_list); - let driving_signal = create_rw_signal(Values::Three); + let show_dropdown = create_rw_signal(false); + + let main_drop_view = move |item| { + stack(( + label(move || item), + container( + svg(|| String::from(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()) + .any() + }; form::form({ (form_item("Dropdown".to_string(), 120.0, move || { dropdown( + // drivign function + move || Values::Three, // main view - |item| { - stack(( - label(move || item), - container( - svg(|| String::from(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()) - .any() - }, + main_drop_view, // iterator to build list in dropdown - Values::iter().map(move |item| { - label(move || item) - .on_click_stop(move |_| { - driving_signal.set(item); - println!("Selected {item:?}!") - }) - .style(|s| s.size_full()) - }), - move || driving_signal.get(), + Values::iter(), + // view for each item in the list + |item| label(move || item).any(), ) - .show_list(move || show_list.get()) + .show_list(move || show_dropdown.get()) + .on_accept(move |_val| show_dropdown.set(false)) }),) }) } diff --git a/examples/widget-gallery/src/main.rs b/examples/widget-gallery/src/main.rs index 3dcfb262..aa38556f 100644 --- a/examples/widget-gallery/src/main.rs +++ b/examples/widget-gallery/src/main.rs @@ -16,10 +16,7 @@ use floem::{ event::{Event, EventListener}, keyboard::{Key, NamedKey}, peniko::Color, - reactive::{ - create_effect, create_signal, create_trigger, provide_context, use_context, RwSignal, - Trigger, - }, + reactive::create_signal, style::{Background, CursorStyle, Transition}, unit::UnitExt, view::View, @@ -31,19 +28,6 @@ use floem::{ EventPropagation, }; -type PopOver = Trigger; -pub fn follow_popover(visible_state: RwSignal) { - let pop_over = use_context::().unwrap(); - create_effect(move |_| { - pop_over.track(); - visible_state.set(false); - }); -} -pub fn popover_notify() { - let pop_over = use_context::().unwrap(); - pop_over.notify(); -} - fn app_view() -> impl View { let tabs: im::Vector<&str> = vec![ "Label", @@ -65,9 +49,6 @@ fn app_view() -> impl View { let (active_tab, set_active_tab) = create_signal(0); - let pop_over: PopOver = create_trigger(); - provide_context(pop_over); - let list = scroll({ virtual_stack( VirtualDirection::Vertical, @@ -82,7 +63,6 @@ fn app_view() -> impl View { .unwrap(); stack((label(move || item).style(|s| s.font_size(18.0)),)) .on_click_stop(move |_| { - popover_notify(); set_active_tab.update(|v: &mut usize| { *v = tabs .get_untracked() @@ -194,7 +174,6 @@ fn app_view() -> impl View { let view = h_stack((left, tab)) .style(|s| s.padding(5.0).width_full().height_full().gap(5.0, 0.0)) - .on_click_stop(move |_| popover_notify()) .window_title(|| "Widget Gallery".to_owned()); let id = view.id(); diff --git a/src/views/list.rs b/src/views/list.rs index 72c468a2..aa775f20 100644 --- a/src/views/list.rs +++ b/src/views/list.rs @@ -15,6 +15,7 @@ use floem_reactive::{create_rw_signal, RwSignal}; enum ListUpdate { SelectionChanged, ScrollToSelected, + Accept, } pub(crate) struct Item { @@ -27,6 +28,7 @@ pub(crate) struct Item { pub struct List { data: ViewData, selection: RwSignal>, + onaccept: Option)>>, child: Stack, } @@ -42,6 +44,11 @@ impl List { }); self } + + pub fn on_accept(mut self, on_accept: impl Fn(Option) + 'static) -> Self { + self.onaccept = Some(Box::new(on_accept)); + self + } } pub fn list(iterator: impl IntoIterator) -> List @@ -63,7 +70,8 @@ where } .on_click_stop(move |_| { if selection.get_untracked() != Some(index) { - selection.set(Some(index)) + selection.set(Some(index)); + id.update_state(ListUpdate::Accept); } }) })) @@ -73,6 +81,7 @@ where data: ViewData::new(id), selection, child: stack, + onaccept: None, } .keyboard_navigatable() .on_event(EventListener::KeyDown, move |e| { @@ -110,6 +119,10 @@ where } EventPropagation::Stop } + Key::Named(NamedKey::Enter) => { + id.update_state(ListUpdate::Accept); + EventPropagation::Stop + } Key::Named(NamedKey::ArrowDown) => { let current = selection.get_untracked(); match current { @@ -189,6 +202,11 @@ impl Widget for List { self.child.children[index].view_data().id().scroll_to(None); } } + ListUpdate::Accept => { + if let Some(on_accept) = &self.onaccept { + on_accept(self.selection.get_untracked()); + } + } } } } diff --git a/src/widgets/dropdown.rs b/src/widgets/dropdown.rs index 5a94ecb8..ffb2c4ca 100644 --- a/src/widgets/dropdown.rs +++ b/src/widgets/dropdown.rs @@ -6,6 +6,7 @@ use kurbo::{Point, Rect}; use crate::{ action::{add_overlay, remove_overlay}, + event::{Event, EventListener}, id::Id, style::{Style, StyleClass, Width}, style_class, @@ -33,11 +34,15 @@ pub struct DropDown { list_style: Style, overlay_id: Option, window_origin: Option, + on_accept: Option>, + on_open: Option>, } enum Message { OpenState(bool), ActiveElement(Box), + ListFocusLost, + ListSelect(Box), } impl View for DropDown { @@ -101,6 +106,14 @@ impl Widget for DropDown { match *state { Message::OpenState(true) => self.open_dropdown(cx), Message::OpenState(false) => self.close_dropdown(), + Message::ListFocusLost => self.close_dropdown(), + Message::ListSelect(val) => { + if let Ok(val) = val.downcast::() { + if let Some(on_select) = &self.on_accept { + on_select(*val); + } + } + } Message::ActiveElement(val) => { if let Ok(val) = val.downcast::() { let old_child_scope = self.main_view_scope; @@ -123,15 +136,15 @@ impl Widget for DropDown { &mut self, cx: &mut crate::context::EventCx, id_path: Option<&[Id]>, - event: crate::event::Event, + event: Event, ) -> crate::EventPropagation { #[allow(clippy::single_match)] match event { - crate::event::Event::PointerDown(_) => { + Event::PointerDown(_) => { self.swap_state(); return EventPropagation::Stop; } - crate::event::Event::KeyUp(ref key_event) + Event::KeyUp(ref key_event) if key_event.key.logical_key == Key::Named(NamedKey::Enter) => { self.swap_state() @@ -142,11 +155,16 @@ impl Widget for DropDown { } } -pub fn dropdown(main_view: MF, iterator: I, active_item: AIF) -> DropDown +pub fn dropdown( + active_item: AIF, + main_view: MF, + iterator: I, + list_item_fn: LF, +) -> DropDown where MF: Fn(T) -> AnyView + 'static, - I: IntoIterator + Clone + 'static, - V2: View + 'static, + I: IntoIterator + Clone + 'static, + LF: Fn(T) -> AnyView + Clone + 'static, T: Clone + 'static, AIF: Fn() -> T + 'static, { @@ -154,7 +172,21 @@ where let list_view = Rc::new(move || { let iterator = iterator.clone(); - list(iterator).any().keyboard_navigatable() + let iter_clone = iterator.clone(); + let list_item_fn = list_item_fn.clone(); + list(iterator.into_iter().map(list_item_fn)) + .on_accept(move |opt_idx| { + if let Some(idx) = opt_idx { + let val = iter_clone.clone().into_iter().nth(idx).unwrap(); + dropdown_id.update_state(Message::ActiveElement(Box::new(val.clone()))); + dropdown_id.update_state(Message::ListSelect(Box::new(val))); + } + }) + .any() + .keyboard_navigatable() + .on_event_stop(EventListener::FocusLost, move |_| { + dropdown_id.update_state(Message::ListFocusLost); + }) }); let initial = create_updater(active_item, move |new_state| { @@ -174,6 +206,8 @@ where list_style: Style::new(), overlay_id: None, window_origin: None, + on_accept: None, + on_open: None, } .class(DropDownClass) } @@ -188,6 +222,16 @@ impl DropDown { self } + pub fn on_accept(mut self, on_accept: impl Fn(T) + 'static) -> Self { + self.on_accept = Some(Box::new(on_accept)); + self + } + + pub fn on_open(mut self, on_open: impl Fn(bool) + 'static) -> Self { + self.on_open = Some(Box::new(on_open)); + self + } + fn swap_state(&self) { if self.overlay_id.is_some() { self.id().update_state(Message::OpenState(false)); @@ -199,11 +243,17 @@ impl DropDown { fn open_dropdown(&mut self, cx: &mut crate::context::UpdateCx) { if self.overlay_id.is_none() { + self.id().request_layout(); + cx.app_state.compute_layout(); if let Some(layout) = cx.app_state.get_layout(self.id()) { self.update_list_style(layout.size.width as f64); let point = self.window_origin.unwrap_or_default() + (0., layout.size.height as f64); self.create_overlay(point); + + if let Some(on_open) = &self.on_open { + on_open(true); + } } } } @@ -211,6 +261,9 @@ impl DropDown { fn close_dropdown(&mut self) { if let Some(id) = self.overlay_id.take() { remove_overlay(id); + if let Some(on_open) = &self.on_open { + on_open(false); + } } }