floem/views/
checkbox.rs

1#![deny(missing_docs)]
2//! A checkbox view for boolean selection.
3
4use crate::{
5    style_class,
6    view::IntoView,
7    views::{
8        self, create_value_container_signals, h_stack, svg, value_container, Decorators,
9        ValueContainer,
10    },
11};
12use floem_reactive::{SignalGet, SignalUpdate};
13use std::fmt::Display;
14
15style_class!(
16    /// The style class that is applied to the checkbox.
17    pub CheckboxClass
18);
19
20style_class!(
21    /// The style class that is applied to the labeled checkbox stack.
22    pub LabeledCheckboxClass
23);
24
25/// The default checkbox SVG
26pub const DEFAULT_CHECKBOX_SVG: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 16 16"><polygon points="5.19,11.83 0.18,7.44 1.82,5.56 4.81,8.17 10,1.25 12,2.75" /></svg>"#;
27
28fn checkbox_svg(
29    checked: impl SignalGet<bool> + 'static,
30    check_svg: impl Into<String> + 'static,
31) -> impl IntoView {
32    let check_svg: String = check_svg.into();
33    let update_svg = {
34        let check_svg = check_svg.clone();
35        move || {
36            if checked.get() {
37                check_svg.clone()
38            } else {
39                "".to_string()
40            }
41        }
42    };
43    svg(check_svg)
44        .update_value(update_svg)
45        .class(CheckboxClass)
46        .keyboard_navigable()
47}
48
49/// # A customizable checkbox view for boolean selection.
50///
51/// The `Checkbox` struct provides several constructors, each offering different levels of
52/// customization and ease of use. The simplest is the [`Checkbox::new_rw`] constructor, which gets direct access to a signal and will update it when the checkbox is clicked.
53///
54/// Choose the constructor that best fits your needs based on whether you require labeling
55/// and how you prefer to manage the checkbox's state (via closure or direct signal manipulation).
56pub struct Checkbox;
57
58impl Checkbox {
59    /// Creates a new checkbox with a closure that determines its checked state.
60    ///
61    /// This method is useful when you want to create a checkbox whose state is determined by a closure.
62    /// The state can be dynamically updated by the closure, and the checkbox will reflect these changes.
63    ///
64    /// You can add an `on_update` handler to the returned [`ValueContainer`] to handle changes.
65    #[allow(clippy::new_ret_no_self)]
66    #[inline]
67    pub fn new(checked: impl Fn() -> bool + 'static) -> ValueContainer<bool> {
68        Self::new_custom(checked, DEFAULT_CHECKBOX_SVG)
69    }
70
71    /// Creates a new checkbox with a closure that determines its checked state and accepts a custom SVG
72    ///
73    /// The semantics are the same as [`Checkbox::new`].
74    ///
75    /// You can add an `on_update` handler to the returned [`ValueContainer`] to handle changes.
76    pub fn new_custom(
77        checked: impl Fn() -> bool + 'static,
78        custom_check: impl Into<String> + Clone + 'static,
79    ) -> ValueContainer<bool> {
80        let (inbound_signal, outbound_signal) = create_value_container_signals(checked);
81
82        value_container(
83            checkbox_svg(inbound_signal.read_only(), custom_check).on_click_stop(move |_| {
84                let checked = inbound_signal.get_untracked();
85                outbound_signal.set(!checked);
86            }),
87            move || outbound_signal.get(),
88        )
89    }
90
91    /// Creates a new checkbox with a signal that provides and updates its checked state.
92    ///
93    /// This method is ideal when you need a checkbox that not only reflects a signal's state but also updates it.
94    /// Clicking the checkbox will toggle its state and update the signal accordingly.
95    #[inline]
96    pub fn new_rw(
97        checked: impl SignalGet<bool> + SignalUpdate<bool> + Copy + 'static,
98    ) -> impl IntoView {
99        Self::new_rw_custom(checked, DEFAULT_CHECKBOX_SVG)
100    }
101
102    /// Creates a new checkbox with a signal that provides and updates its checked state and accepts a custom SVG for the symbol.
103    ///
104    /// The semantics are the same as [`Checkbox::new_rw`].
105    pub fn new_rw_custom(
106        checked: impl SignalGet<bool> + SignalUpdate<bool> + Copy + 'static,
107        custom_check: impl Into<String> + Clone + 'static,
108    ) -> impl IntoView {
109        checkbox_svg(checked, custom_check).on_click_stop(move |_| {
110            checked.update(|val| *val = !*val);
111        })
112    }
113
114    /// Creates a new labeled checkbox with a closure that determines its checked state.
115    ///
116    /// This method is useful when you want a labeled checkbox whose state is determined by a closure.
117    /// The label is also provided by a closure, allowing for dynamic updates.
118    #[inline]
119    pub fn labeled<S: Display + 'static>(
120        checked: impl Fn() -> bool + 'static,
121        label: impl Fn() -> S + 'static,
122    ) -> ValueContainer<bool> {
123        Self::custom_labeled(checked, label, DEFAULT_CHECKBOX_SVG)
124    }
125
126    /// Creates a new labeled checkbox with a closure that determines its checked state and accepts a custom SVG for the symbol.
127    ///
128    /// The semantics are the same as [`Checkbox::labeled`].
129    pub fn custom_labeled<S: Display + 'static>(
130        checked: impl Fn() -> bool + 'static,
131        label: impl Fn() -> S + 'static,
132        custom_check: impl Into<String> + Clone + 'static,
133    ) -> ValueContainer<bool> {
134        let (inbound_signal, outbound_signal) = create_value_container_signals(checked);
135
136        value_container(
137            h_stack((
138                checkbox_svg(inbound_signal.read_only(), custom_check).on_click_stop(move |_| {
139                    let checked = inbound_signal.get_untracked();
140                    outbound_signal.set(!checked);
141                }),
142                views::label(label),
143            ))
144            .class(LabeledCheckboxClass)
145            .on_click_stop(move |_| {
146                let checked = inbound_signal.get_untracked();
147                outbound_signal.set(!checked);
148            })
149            .style(|s| s.items_center()),
150            move || outbound_signal.get(),
151        )
152    }
153
154    /// Creates a new labeled checkbox with a signal that provides and updates its checked state.
155    ///
156    /// This method is ideal when you need a labeled checkbox that not only reflects a signal's state but also updates it.
157    /// Clicking the checkbox will toggle its state and update the signal accordingly.
158    #[inline]
159    pub fn labeled_rw<S: Display + 'static>(
160        checked: impl SignalGet<bool> + SignalUpdate<bool> + Copy + 'static,
161        label: impl Fn() -> S + 'static,
162    ) -> impl IntoView {
163        Self::custom_labeled_rw(checked, label, DEFAULT_CHECKBOX_SVG)
164    }
165
166    /// Creates a new labeled checkbox with a signal that provides and updates its checked state and accepts a custom SVG.
167    ///
168    /// The semantics are the same as [`Checkbox::labeled_rw`].
169    pub fn custom_labeled_rw<S: Display + 'static>(
170        checked: impl SignalGet<bool> + SignalUpdate<bool> + Copy + 'static,
171        label: impl Fn() -> S + 'static,
172        custom_check: impl Into<String> + Clone + 'static,
173    ) -> impl IntoView {
174        h_stack((
175            checkbox_svg(checked, custom_check).on_click_stop(move |_| {
176                checked.update(|val| *val = !*val);
177            }),
178            views::label(label),
179        ))
180        .on_click_stop(move |_| {
181            checked.update(|val| *val = !*val);
182        })
183        .class(LabeledCheckboxClass)
184        .style(|s| s.items_center())
185    }
186}
187
188/// Renders a checkbox the provided checked signal. See also [`Checkbox::new`] and [`Checkbox::new_rw`].
189pub fn checkbox(checked: impl Fn() -> bool + 'static) -> ValueContainer<bool> {
190    Checkbox::new(checked)
191}
192
193/// Renders a checkbox using a `checked` signal and custom SVG. See also [`Checkbox::new_rw`] and
194pub fn custom_checkbox(
195    checked: impl Fn() -> bool + 'static,
196    custom_check: impl Into<String> + Clone + 'static,
197) -> ValueContainer<bool> {
198    Checkbox::new_custom(checked, custom_check)
199}
200
201/// Renders a checkbox using the provided checked signal. See also [`Checkbox::labeled`] and [`Checkbox::labeled_rw`].
202pub fn labeled_checkbox<S: Display + 'static>(
203    checked: impl Fn() -> bool + 'static,
204    label: impl Fn() -> S + 'static,
205) -> ValueContainer<bool> {
206    Checkbox::labeled(checked, label)
207}
208
209/// Renders a checkbox using the a `checked` signal and a custom SVG. See also [`Checkbox::custom_labeled_rw`]
210pub fn custom_labeled_checkbox<S: Display + 'static>(
211    checked: impl Fn() -> bool + 'static,
212    label: impl Fn() -> S + 'static,
213    custom_check: impl Into<String> + Clone + 'static,
214) -> ValueContainer<bool> {
215    Checkbox::custom_labeled(checked, label, custom_check)
216}