Skip to content

Commit

Permalink
add value_container for provide on_update to checkbox (#290)
Browse files Browse the repository at this point in the history
Co-authored-by: Dongdong Zhou <[email protected]>
Co-authored-by: pieterdd <[email protected]>
  • Loading branch information
3 people authored Jan 24, 2024
1 parent ac05875 commit b8ee84c
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 26 deletions.
27 changes: 11 additions & 16 deletions examples/widget-gallery/src/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,29 @@ pub fn checkbox_view() -> impl View {
form({
(
form_item("Checkbox:".to_string(), width, move || {
checkbox(is_checked)
.style(|s| s.margin(5.0))
.on_click_stop(move |_| {
set_is_checked.update(|checked| *checked = !*checked);
checkbox(move || is_checked.get())
.on_update(move |checked| {
set_is_checked.set(checked);
})
.style(|s| s.margin(5.0))
}),
form_item("Disabled Checkbox:".to_string(), width, move || {
checkbox(is_checked)
checkbox(move || is_checked.get())
.style(|s| s.margin(5.0))
.on_click_stop(move |_| {
set_is_checked.update(|checked| *checked = !*checked);
})
.disabled(|| true)
}),
form_item("Labelled Checkbox:".to_string(), width, move || {
labeled_checkbox(is_checked, || "Check me!").on_click_stop(move |_| {
set_is_checked.update(|checked| *checked = !*checked);
})
labeled_checkbox(move || is_checked.get(), || "Check me!").on_update(
move |checked| {
set_is_checked.set(checked);
},
)
}),
form_item(
"Disabled Labelled Checkbox:".to_string(),
width,
move || {
labeled_checkbox(is_checked, || "Check me!")
.on_click_stop(move |_| {
set_is_checked.update(|checked| *checked = !*checked);
})
.disabled(|| true)
labeled_checkbox(move || is_checked.get(), || "Check me!").disabled(|| true)
},
),
)
Expand Down
2 changes: 1 addition & 1 deletion examples/widget-gallery/src/lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn enhanced_list() -> impl View {
container({
stack({
(
checkbox(is_checked).on_click_stop(move |_| {
checkbox(move || is_checked.get()).on_click_stop(move |_| {
set_is_checked.update(|checked: &mut bool| *checked = !*checked);
}),
label(move || item.to_string())
Expand Down
2 changes: 1 addition & 1 deletion src/views/container_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub struct ContainerBox {
/// let check = true;
///
/// if check == true {
/// container_box(checkbox(create_rw_signal(true).read_only()))
/// container_box(checkbox(|| true))
/// } else {
/// container_box(label(|| "no check".to_string()))
/// };
Expand Down
3 changes: 3 additions & 0 deletions src/views/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ pub use container_box::*;
mod dyn_container;
pub use dyn_container::*;

mod value_container;
pub use value_container::*;

mod decorator;
pub use decorator::*;

Expand Down
106 changes: 106 additions & 0 deletions src/views/value_container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::any::Any;

use floem_reactive::{create_effect, create_rw_signal, create_updater, RwSignal};

use crate::{
context::UpdateCx,
id::Id,
view::{View, ViewData},
};

/// A wrapper around another View that has value updates. See [`value_container`]
pub struct ValueContainer<T> {
data: ViewData,
child: Box<dyn View>,
on_update: Option<Box<dyn Fn(T)>>,
}

/// Creates two signals:
/// - The outbound signal enables a widget's internal input event handlers
/// to publish state changes via `ValueContainer::on_update`.
/// - The inbound signal propagates value changes in the producer function
/// into a widget's internals.
pub fn create_value_container_signals<T>(
producer: impl Fn() -> T + 'static,
) -> (RwSignal<T>, RwSignal<T>)
where
T: Copy + 'static,
{
let initial_value = producer();

let inbound_signal = create_rw_signal(initial_value);
create_effect(move |_| {
let checked = producer();
inbound_signal.set(checked);
});

let outbound_signal = create_rw_signal(initial_value);
create_effect(move |_| {
let checked = outbound_signal.get();
inbound_signal.set(checked);
});

(inbound_signal, outbound_signal)
}

/// A wrapper around another View that has value updates.
///
/// A [`ValueContainer`] is useful for wrapping another [View](crate::view::View).
/// This is to provide the `on_update` method which can notify when the view's
/// internal value was get changed
pub fn value_container<T: 'static, V: View + 'static>(
child: V,
value_update: impl Fn() -> T + 'static,
) -> ValueContainer<T> {
let id = Id::next();
create_updater(value_update, move |new_value| id.update_state(new_value));
ValueContainer {
data: ViewData::new(id),
child: Box::new(child),
on_update: None,
}
}

impl<T> ValueContainer<T> {
pub fn on_update(mut self, action: impl Fn(T) + 'static) -> Self {
self.on_update = Some(Box::new(action));
self
}
}

impl<T: 'static> View for ValueContainer<T> {
fn view_data(&self) -> &ViewData {
&self.data
}

fn view_data_mut(&mut self) -> &mut ViewData {
&mut self.data
}

fn update(&mut self, _cx: &mut UpdateCx, state: Box<dyn Any>) {
if let Ok(state) = state.downcast::<T>() {
if let Some(on_update) = self.on_update.as_ref() {
on_update(*state);
}
}
}

fn for_each_child<'a>(&'a self, for_each: &mut dyn FnMut(&'a dyn View) -> bool) {
for_each(&self.child);
}

fn for_each_child_mut<'a>(&'a mut self, for_each: &mut dyn FnMut(&'a mut dyn View) -> bool) {
for_each(&mut self.child);
}

fn for_each_child_rev_mut<'a>(
&'a mut self,
for_each: &mut dyn FnMut(&'a mut dyn View) -> bool,
) {
for_each(&mut self.child);
}

fn debug_name(&self) -> std::borrow::Cow<'static, str> {
"ValueContainer".into()
}
}
40 changes: 32 additions & 8 deletions src/widgets/checkbox.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::{
style_class,
view::View,
views::{self, h_stack, svg, Decorators},
views::{
self, create_value_container_signals, h_stack, svg, value_container, Decorators,
ValueContainer,
},
};
use floem_reactive::ReadSignal;
use std::fmt::Display;
Expand All @@ -17,18 +20,39 @@ fn checkbox_svg(checked: ReadSignal<bool>) -> impl View {
}

/// Renders a checkbox the provided checked signal.
/// Can be combined with a label and a stack with a click event (as in `examples/widget-gallery`).
pub fn checkbox(checked: ReadSignal<bool>) -> impl View {
checkbox_svg(checked).keyboard_navigatable()
pub fn checkbox(checked: impl Fn() -> bool + 'static) -> ValueContainer<bool> {
let (inbound_signal, outbound_signal) = create_value_container_signals(checked);

value_container(
checkbox_svg(inbound_signal.read_only())
.keyboard_navigatable()
.on_click_stop(move |_| {
let checked = inbound_signal.get_untracked();
outbound_signal.set(!checked);
}),
move || outbound_signal.get(),
)
}

/// Renders a checkbox using the provided checked signal.
pub fn labeled_checkbox<S: Display + 'static>(
checked: ReadSignal<bool>,
checked: impl Fn() -> bool + 'static,
label: impl Fn() -> S + 'static,
) -> impl View {
h_stack((checkbox_svg(checked), views::label(label)))
) -> ValueContainer<bool> {
let (inbound_signal, outbound_signal) = create_value_container_signals(checked);

value_container(
h_stack((
checkbox_svg(inbound_signal.read_only()),
views::label(label),
))
.class(LabeledCheckboxClass)
.style(|s| s.items_center().justify_center())
.keyboard_navigatable()
.on_click_stop(move |_| {
let checked = inbound_signal.get_untracked();
outbound_signal.set(!checked);
})
.style(|s| s.items_center().justify_center()),
move || outbound_signal.get(),
)
}

0 comments on commit b8ee84c

Please sign in to comment.