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