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#[derive(Clone, Debug, Default, PartialEq)]
14pub struct ButtonSpec {
15    pub interaction_source: Option<MutableInteractionSource>,
16}
17
18impl ButtonSpec {
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    pub fn interaction_source(mut self, interaction_source: MutableInteractionSource) -> Self {
24        self.interaction_source = Some(interaction_source);
25        self
26    }
27}
28
29fn button_modifier<F>(modifier: Modifier, spec: ButtonSpec, on_click: F) -> Modifier
30where
31    F: FnMut() + 'static,
32{
33    let on_click_rc: Rc<RefCell<dyn FnMut()>> = Rc::new(RefCell::new(on_click));
34    let modifier = if let Some(interaction_source) = spec.interaction_source {
35        modifier.press_interaction_source(interaction_source)
36    } else {
37        modifier
38    };
39
40    modifier.clickable(move |_point| {
41        (on_click_rc.borrow_mut())();
42    })
43}
44
45/// Calls `on_click` after a click and places content inside a button container.
46/// The modifier controls size and appearance. `spec` can supply an interaction source.
47///
48/// # Example
49///
50/// ```rust
51/// use cranpose_ui::*;
52///
53/// #[composable]
54/// fn SaveAction() {
55///     Button(
56///         Modifier::empty().padding(8.0),
57///         ButtonSpec::default(),
58///         || println!("Save requested"),
59///         || {
60///             Text("Save", Modifier::empty(), TextStyle::default());
61///         },
62///     );
63/// }
64/// ```
65#[composable]
66pub fn Button<F, G>(modifier: Modifier, spec: ButtonSpec, on_click: F, content: G) -> NodeId
67where
68    F: FnMut() + 'static,
69    G: FnMut() + 'static,
70{
71    Layout(
72        button_modifier(modifier, spec, on_click),
73        FlexMeasurePolicy::column(
74            LinearArrangement::Center,
75            HorizontalAlignment::CenterHorizontally,
76            crate::density::density().density(),
77        ),
78        content,
79    )
80}
81
82#[cfg(test)]
83#[path = "tests/button_tests.rs"]
84mod tests;