Skip to content

Commit

Permalink
Fix up dropdown (#302)
Browse files Browse the repository at this point in the history
* Fix up dropdown

* Change dropdown on_select to an on_accept

* add call to accept on list item click

* Add on_open callback
  • Loading branch information
jrmoulton authored Feb 3, 2024
1 parent bc6c523 commit 0de4e90
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 64 deletions.
61 changes: 27 additions & 34 deletions examples/widget-gallery/src/dropdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -33,43 +30,39 @@ const CHEVRON_DOWN: &str = r##"<svg xmlns="http://www.w3.org/2000/svg" xml:space
</svg>"##;

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))
}),)
})
}
23 changes: 1 addition & 22 deletions examples/widget-gallery/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -31,19 +28,6 @@ use floem::{
EventPropagation,
};

type PopOver = Trigger;
pub fn follow_popover(visible_state: RwSignal<bool>) {
let pop_over = use_context::<PopOver>().unwrap();
create_effect(move |_| {
pop_over.track();
visible_state.set(false);
});
}
pub fn popover_notify() {
let pop_over = use_context::<PopOver>().unwrap();
pop_over.notify();
}

fn app_view() -> impl View {
let tabs: im::Vector<&str> = vec![
"Label",
Expand All @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -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();
Expand Down
20 changes: 19 additions & 1 deletion src/views/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use floem_reactive::{create_rw_signal, RwSignal};
enum ListUpdate {
SelectionChanged,
ScrollToSelected,
Accept,
}

pub(crate) struct Item {
Expand All @@ -27,6 +28,7 @@ pub(crate) struct Item {
pub struct List {
data: ViewData,
selection: RwSignal<Option<usize>>,
onaccept: Option<Box<dyn Fn(Option<usize>)>>,
child: Stack,
}

Expand All @@ -42,6 +44,11 @@ impl List {
});
self
}

pub fn on_accept(mut self, on_accept: impl Fn(Option<usize>) + 'static) -> Self {
self.onaccept = Some(Box::new(on_accept));
self
}
}

pub fn list<V>(iterator: impl IntoIterator<Item = V>) -> List
Expand All @@ -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);
}
})
}))
Expand All @@ -73,6 +81,7 @@ where
data: ViewData::new(id),
selection,
child: stack,
onaccept: None,
}
.keyboard_navigatable()
.on_event(EventListener::KeyDown, move |e| {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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());
}
}
}
}
}
Expand Down
67 changes: 60 additions & 7 deletions src/widgets/dropdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -33,11 +34,15 @@ pub struct DropDown<T: 'static> {
list_style: Style,
overlay_id: Option<Id>,
window_origin: Option<Point>,
on_accept: Option<Box<dyn Fn(T)>>,
on_open: Option<Box<dyn Fn(bool)>>,
}

enum Message {
OpenState(bool),
ActiveElement(Box<dyn Any>),
ListFocusLost,
ListSelect(Box<dyn Any>),
}

impl<T: 'static> View for DropDown<T> {
Expand Down Expand Up @@ -101,6 +106,14 @@ impl<T: 'static> Widget for DropDown<T> {
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::<T>() {
if let Some(on_select) = &self.on_accept {
on_select(*val);
}
}
}
Message::ActiveElement(val) => {
if let Ok(val) = val.downcast::<T>() {
let old_child_scope = self.main_view_scope;
Expand All @@ -123,15 +136,15 @@ impl<T: 'static> Widget for DropDown<T> {
&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()
Expand All @@ -142,19 +155,38 @@ impl<T: 'static> Widget for DropDown<T> {
}
}

pub fn dropdown<MF, I, T, V2, AIF>(main_view: MF, iterator: I, active_item: AIF) -> DropDown<T>
pub fn dropdown<MF, I, T, LF, AIF>(
active_item: AIF,
main_view: MF,
iterator: I,
list_item_fn: LF,
) -> DropDown<T>
where
MF: Fn(T) -> AnyView + 'static,
I: IntoIterator<Item = V2> + Clone + 'static,
V2: View + 'static,
I: IntoIterator<Item = T> + Clone + 'static,
LF: Fn(T) -> AnyView + Clone + 'static,
T: Clone + 'static,
AIF: Fn() -> T + 'static,
{
let dropdown_id = Id::next();

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| {
Expand All @@ -174,6 +206,8 @@ where
list_style: Style::new(),
overlay_id: None,
window_origin: None,
on_accept: None,
on_open: None,
}
.class(DropDownClass)
}
Expand All @@ -188,6 +222,16 @@ impl<T> DropDown<T> {
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));
Expand All @@ -199,18 +243,27 @@ impl<T> DropDown<T> {

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);
}
}
}
}

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);
}
}
}

Expand Down

0 comments on commit 0de4e90

Please sign in to comment.