Skip to main content

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