Skip to main content

cranpose_ui/widgets/
button.rs

1//! Button widget implementation
2
3use std::{cell::RefCell, rc::Rc};
4
5use cranpose_core::NodeId;
6use cranpose_ui_layout::{HorizontalAlignment, LinearArrangement};
7
8use crate::{
9    composable, interaction::MutableInteractionSource, layout::policies::FlexMeasurePolicy,
10    modifier::Modifier, widgets::Layout,
11};
12
13/// Optional settings for a [`Button`].
14#[derive(Clone, Debug, Default, PartialEq)]
15pub struct ButtonSpec {
16    /// Source that receives the button's press interactions, for example to draw a pressed state.
17    /// `None` records none.
18    pub interaction_source: Option<MutableInteractionSource>,
19}
20
21impl ButtonSpec {
22    /// Creates a spec with no interaction source.
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    /// Sets the source that receives the button's press interactions.
28    pub fn interaction_source(mut self, interaction_source: MutableInteractionSource) -> Self {
29        self.interaction_source = Some(interaction_source);
30        self
31    }
32}
33
34fn button_modifier<F>(modifier: Modifier, spec: ButtonSpec, on_click: F) -> Modifier
35where
36    F: FnMut() + 'static,
37{
38    let on_click_rc: Rc<RefCell<dyn FnMut()>> = Rc::new(RefCell::new(on_click));
39    let modifier = if let Some(interaction_source) = spec.interaction_source {
40        modifier.press_interaction_source(interaction_source)
41    } else {
42        modifier
43    };
44
45    modifier.clickable(move |_point| {
46        (on_click_rc.borrow_mut())();
47    })
48}
49
50/// Calls `on_click` after a click and places content inside a button container.
51/// The modifier controls size and appearance. `spec` can supply an interaction source.
52///
53/// # Example
54///
55/// ```rust
56/// use cranpose_ui::*;
57///
58/// #[composable]
59/// fn SaveAction() {
60///     Button(
61///         Modifier::empty().padding(8.0),
62///         ButtonSpec::default(),
63///         || println!("Save requested"),
64///         || {
65///             Text("Save", Modifier::empty(), TextStyle::default());
66///         },
67///     );
68/// }
69/// ```
70#[composable]
71pub fn Button<F, G>(modifier: Modifier, spec: ButtonSpec, on_click: F, content: G) -> NodeId
72where
73    F: FnMut() + 'static,
74    G: FnMut() + 'static,
75{
76    Layout(
77        button_modifier(modifier, spec, on_click),
78        FlexMeasurePolicy::column(
79            LinearArrangement::Center,
80            HorizontalAlignment::CenterHorizontally,
81            crate::density::density().density(),
82        ),
83        content,
84    )
85}
86
87#[cfg(test)]
88#[path = "tests/button_tests.rs"]
89mod tests;