raui_core/widget/component/interactive/
button.rs1use 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}