raui_core/widget/component/interactive/
button.rs

1use crate::{
2    pre_hooks, unpack_named_slots,
3    widget::{
4        component::interactive::navigation::{
5            use_nav_button, use_nav_item, use_nav_tracking, use_nav_tracking_self, NavSignal,
6        },
7        context::{WidgetContext, WidgetMountOrChangeContext},
8        node::WidgetNode,
9        unit::area::AreaBoxNode,
10        WidgetId, WidgetIdOrRef,
11    },
12    MessageData, PropsData,
13};
14use serde::{Deserialize, Serialize};
15
16fn is_false(v: &bool) -> bool {
17    !*v
18}
19
20#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
21#[props_data(crate::props::PropsData)]
22#[prefab(crate::Prefab)]
23pub struct ButtonProps {
24    #[serde(default)]
25    #[serde(skip_serializing_if = "is_false")]
26    pub selected: bool,
27    #[serde(default)]
28    #[serde(skip_serializing_if = "is_false")]
29    pub trigger: bool,
30    #[serde(default)]
31    #[serde(skip_serializing_if = "is_false")]
32    pub context: bool,
33}
34
35#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
36#[props_data(crate::props::PropsData)]
37#[prefab(crate::Prefab)]
38pub struct ButtonNotifyProps(
39    #[serde(default)]
40    #[serde(skip_serializing_if = "WidgetIdOrRef::is_none")]
41    pub WidgetIdOrRef,
42);
43
44#[derive(MessageData, Debug, Default, Clone)]
45#[message_data(crate::messenger::MessageData)]
46pub struct ButtonNotifyMessage {
47    pub sender: WidgetId,
48    pub state: ButtonProps,
49    pub prev: ButtonProps,
50}
51
52impl ButtonNotifyMessage {
53    pub fn select_start(&self) -> bool {
54        !self.prev.selected && self.state.selected
55    }
56
57    pub fn select_stop(&self) -> bool {
58        self.prev.selected && !self.state.selected
59    }
60
61    pub fn select_changed(&self) -> bool {
62        self.prev.selected != self.state.selected
63    }
64
65    pub fn trigger_start(&self) -> bool {
66        !self.prev.trigger && self.state.trigger
67    }
68
69    pub fn trigger_stop(&self) -> bool {
70        self.prev.trigger && !self.state.trigger
71    }
72
73    pub fn trigger_changed(&self) -> bool {
74        self.prev.trigger != self.state.trigger
75    }
76
77    pub fn context_start(&self) -> bool {
78        !self.prev.context && self.state.context
79    }
80
81    pub fn context_stop(&self) -> bool {
82        self.prev.context && !self.state.context
83    }
84
85    pub fn context_changed(&self) -> bool {
86        self.prev.context != self.state.context
87    }
88}
89
90pub fn use_button_notified_state(context: &mut WidgetContext) {
91    context.life_cycle.change(|context| {
92        for msg in context.messenger.messages {
93            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {
94                let _ = context.state.write_with(msg.state);
95            }
96        }
97    });
98}
99
100#[pre_hooks(use_nav_item, use_nav_button)]
101pub fn use_button(context: &mut WidgetContext) {
102    fn notify(context: &WidgetMountOrChangeContext, data: ButtonNotifyMessage) {
103        if let Ok(ButtonNotifyProps(notify)) = context.props.read() {
104            if let Some(to) = notify.read() {
105                context.messenger.write(to, data);
106            }
107        }
108    }
109
110    context.life_cycle.mount(|context| {
111        notify(
112            &context,
113            ButtonNotifyMessage {
114                sender: context.id.to_owned(),
115                state: Default::default(),
116                prev: Default::default(),
117            },
118        );
119        let _ = context.state.write_with(ButtonProps::default());
120    });
121
122    context.life_cycle.change(|context| {
123        let mut dirty = false;
124        let mut data = context.state.read_cloned_or_default::<ButtonProps>();
125        let prev = data;
126        for msg in context.messenger.messages {
127            if let Some(msg) = msg.as_any().downcast_ref() {
128                match msg {
129                    NavSignal::Select(_) => {
130                        data.selected = true;
131                        dirty = true;
132                    }
133                    NavSignal::Unselect => {
134                        data.selected = false;
135                        dirty = true;
136                    }
137                    NavSignal::Accept(v) => {
138                        data.trigger = *v;
139                        dirty = true;
140                    }
141                    NavSignal::Context(v) => {
142                        data.context = *v;
143                        dirty = true;
144                    }
145                    _ => {}
146                }
147            }
148        }
149        if dirty {
150            notify(
151                &context,
152                ButtonNotifyMessage {
153                    sender: context.id.to_owned(),
154                    state: data.to_owned(),
155                    prev,
156                },
157            );
158            let _ = context.state.write_with(data);
159        }
160    });
161}
162
163#[pre_hooks(use_button)]
164pub fn button(mut context: WidgetContext) -> WidgetNode {
165    let WidgetContext {
166        id,
167        state,
168        named_slots,
169        ..
170    } = context;
171    unpack_named_slots!(named_slots => content);
172
173    if let Some(p) = content.props_mut() {
174        p.write(state.read_cloned_or_default::<ButtonProps>());
175    }
176
177    AreaBoxNode {
178        id: id.to_owned(),
179        slot: Box::new(content),
180    }
181    .into()
182}
183
184#[pre_hooks(use_nav_tracking)]
185pub fn tracked_button(mut context: WidgetContext) -> WidgetNode {
186    button(context)
187}
188
189#[pre_hooks(use_nav_tracking_self)]
190pub fn self_tracked_button(mut context: WidgetContext) -> WidgetNode {
191    button(context)
192}