raui_core/widget/component/
mod.rs1pub mod containers;
2pub mod image_box;
3pub mod interactive;
4pub mod space_box;
5pub mod text_box;
6
7use crate::{
8 messenger::Message,
9 props::{Props, PropsData},
10 widget::{
11 context::WidgetContext,
12 node::{WidgetNode, WidgetNodePrefab},
13 utils::{Rect, Vec2},
14 FnWidget, WidgetId, WidgetIdOrRef, WidgetRef,
15 },
16 MessageData, PrefabValue, PropsData, Scalar,
17};
18use serde::{Deserialize, Serialize};
19use std::{any::TypeId, collections::HashMap, convert::TryFrom};
20
21fn is_false(v: &bool) -> bool {
22 !*v
23}
24
25#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
26#[props_data(crate::props::PropsData)]
27#[prefab(crate::Prefab)]
28pub struct MessageForwardProps {
29 #[serde(default)]
30 #[serde(skip_serializing_if = "WidgetIdOrRef::is_none")]
31 pub to: WidgetIdOrRef,
32 #[serde(default)]
33 #[serde(skip)]
34 pub types: Vec<TypeId>,
35 #[serde(default)]
36 #[serde(skip_serializing_if = "is_false")]
37 pub no_wrap: bool,
38}
39
40#[derive(MessageData, Debug, Clone)]
41#[message_data(crate::messenger::MessageData)]
42pub struct ForwardedMessage {
43 pub sender: WidgetId,
44 pub data: Message,
45}
46
47pub fn use_message_forward(context: &mut WidgetContext) {
48 context.life_cycle.change(|context| {
49 let (id, no_wrap, types) = match context.props.read::<MessageForwardProps>() {
50 Ok(forward) => match forward.to.read() {
51 Some(id) => (id, forward.no_wrap, &forward.types),
52 _ => return,
53 },
54 _ => match context.shared_props.read::<MessageForwardProps>() {
55 Ok(forward) => match forward.to.read() {
56 Some(id) => (id, forward.no_wrap, &forward.types),
57 _ => return,
58 },
59 _ => return,
60 },
61 };
62 for msg in context.messenger.messages {
63 let t = msg.as_any().type_id();
64 if types.contains(&t) {
65 if no_wrap {
66 context
67 .messenger
68 .write_raw(id.to_owned(), msg.clone_message());
69 } else {
70 context.messenger.write(
71 id.to_owned(),
72 ForwardedMessage {
73 sender: context.id.to_owned(),
74 data: msg.clone_message(),
75 },
76 );
77 }
78 }
79 }
80 });
81}
82
83#[derive(MessageData, Debug, Copy, Clone, PartialEq)]
84#[message_data(crate::messenger::MessageData)]
85pub enum ResizeListenerSignal {
86 Register,
87 Unregister,
88 Change(Vec2),
89}
90
91pub fn use_resize_listener(context: &mut WidgetContext) {
92 context.life_cycle.mount(|context| {
93 context.signals.write(ResizeListenerSignal::Register);
94 });
95
96 context.life_cycle.unmount(|context| {
97 context.signals.write(ResizeListenerSignal::Unregister);
98 });
99}
100
101#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
102#[props_data(crate::props::PropsData)]
103#[prefab(crate::Prefab)]
104pub struct RelativeLayoutProps {
105 #[serde(default)]
106 #[serde(skip_serializing_if = "WidgetIdOrRef::is_none")]
107 pub relative_to: WidgetIdOrRef,
108}
109
110#[derive(MessageData, Debug, Clone, PartialEq)]
111#[message_data(crate::messenger::MessageData)]
112pub enum RelativeLayoutListenerSignal {
113 Register(WidgetId),
115 Unregister,
116 Change(Vec2, Rect),
118}
119
120pub fn use_relative_layout_listener(context: &mut WidgetContext) {
121 context.life_cycle.mount(|context| {
122 if let Ok(props) = context.props.read::<RelativeLayoutProps>() {
123 if let Some(relative_to) = props.relative_to.read() {
124 context
125 .signals
126 .write(RelativeLayoutListenerSignal::Register(relative_to));
127 }
128 }
129 });
130
131 context.life_cycle.unmount(|context| {
135 context
136 .signals
137 .write(RelativeLayoutListenerSignal::Unregister);
138 });
139}
140
141#[derive(PropsData, Debug, Copy, Clone, Serialize, Deserialize)]
142#[props_data(crate::props::PropsData)]
143#[prefab(crate::Prefab)]
144pub struct WidgetAlpha(pub Scalar);
145
146impl Default for WidgetAlpha {
147 fn default() -> Self {
148 Self(1.0)
149 }
150}
151
152impl WidgetAlpha {
153 pub fn multiply(&mut self, alpha: Scalar) {
154 self.0 *= alpha;
155 }
156}
157
158#[derive(Clone)]
159pub struct WidgetComponent {
160 pub processor: FnWidget,
161 pub type_name: String,
162 pub key: Option<String>,
163 pub idref: Option<WidgetRef>,
164 pub props: Props,
165 pub shared_props: Option<Props>,
166 pub listed_slots: Vec<WidgetNode>,
167 pub named_slots: HashMap<String, WidgetNode>,
168}
169
170impl WidgetComponent {
171 pub fn new(processor: FnWidget, type_name: impl ToString) -> Self {
172 Self {
173 processor,
174 type_name: type_name.to_string(),
175 key: None,
176 idref: None,
177 props: Props::default(),
178 shared_props: None,
179 listed_slots: Vec::new(),
180 named_slots: HashMap::new(),
181 }
182 }
183
184 pub fn key<T>(mut self, v: T) -> Self
185 where
186 T: ToString,
187 {
188 self.key = Some(v.to_string());
189 self
190 }
191
192 pub fn idref<T>(mut self, v: T) -> Self
193 where
194 T: Into<WidgetRef>,
195 {
196 self.idref = Some(v.into());
197 self
198 }
199
200 pub fn maybe_idref<T>(mut self, v: Option<T>) -> Self
201 where
202 T: Into<WidgetRef>,
203 {
204 self.idref = v.map(|v| v.into());
205 self
206 }
207
208 pub fn with_props<T>(mut self, v: T) -> Self
209 where
210 T: 'static + PropsData,
211 {
212 self.props.write(v);
213 self
214 }
215
216 pub fn maybe_with_props<T>(self, v: Option<T>) -> Self
217 where
218 T: 'static + PropsData,
219 {
220 if let Some(v) = v {
221 self.with_props(v)
222 } else {
223 self
224 }
225 }
226
227 pub fn merge_props(mut self, v: Props) -> Self {
228 let props = std::mem::take(&mut self.props);
229 self.props = props.merge(v);
230 self
231 }
232
233 pub fn with_shared_props<T>(mut self, v: T) -> Self
234 where
235 T: 'static + PropsData,
236 {
237 if let Some(props) = &mut self.shared_props {
238 props.write(v);
239 } else {
240 self.shared_props = Some(Props::new(v));
241 }
242 self
243 }
244
245 pub fn maybe_with_shared_props<T>(self, v: Option<T>) -> Self
246 where
247 T: 'static + PropsData,
248 {
249 if let Some(v) = v {
250 self.with_shared_props(v)
251 } else {
252 self
253 }
254 }
255
256 pub fn merge_shared_props(mut self, v: Props) -> Self {
257 if let Some(props) = self.shared_props.take() {
258 self.shared_props = Some(props.merge(v));
259 } else {
260 self.shared_props = Some(v);
261 }
262 self
263 }
264
265 pub fn listed_slot<T>(mut self, v: T) -> Self
266 where
267 T: Into<WidgetNode>,
268 {
269 self.listed_slots.push(v.into());
270 self
271 }
272
273 pub fn maybe_listed_slot<T>(mut self, v: Option<T>) -> Self
274 where
275 T: Into<WidgetNode>,
276 {
277 if let Some(v) = v {
278 self.listed_slots.push(v.into());
279 }
280 self
281 }
282
283 pub fn listed_slots<I, T>(mut self, v: I) -> Self
284 where
285 I: IntoIterator<Item = T>,
286 T: Into<WidgetNode>,
287 {
288 self.listed_slots.extend(v.into_iter().map(|v| v.into()));
289 self
290 }
291
292 pub fn named_slot<T>(mut self, k: impl ToString, v: T) -> Self
293 where
294 T: Into<WidgetNode>,
295 {
296 self.named_slots.insert(k.to_string(), v.into());
297 self
298 }
299
300 pub fn maybe_named_slot<T>(mut self, k: impl ToString, v: Option<T>) -> Self
301 where
302 T: Into<WidgetNode>,
303 {
304 if let Some(v) = v {
305 self.named_slots.insert(k.to_string(), v.into());
306 }
307 self
308 }
309
310 pub fn named_slots<I, K, T>(mut self, v: I) -> Self
311 where
312 I: IntoIterator<Item = (K, T)>,
313 K: ToString,
314 T: Into<WidgetNode>,
315 {
316 self.named_slots
317 .extend(v.into_iter().map(|(k, v)| (k.to_string(), v.into())));
318 self
319 }
320
321 pub fn remap_props<F>(&mut self, mut f: F)
322 where
323 F: FnMut(Props) -> Props,
324 {
325 let props = std::mem::take(&mut self.props);
326 self.props = (f)(props);
327 }
328
329 pub fn remap_shared_props<F>(&mut self, mut f: F)
330 where
331 F: FnMut(Props) -> Props,
332 {
333 if let Some(shared_props) = &mut self.shared_props {
334 let props = std::mem::take(shared_props);
335 *shared_props = (f)(props);
336 } else {
337 self.shared_props = Some((f)(Default::default()));
338 }
339 }
340}
341
342impl std::fmt::Debug for WidgetComponent {
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 let mut s = f.debug_struct("WidgetComponent");
345 s.field("type_name", &self.type_name);
346 if let Some(key) = &self.key {
347 s.field("key", key);
348 }
349 s.field("props", &self.props);
350 s.field("shared_props", &self.shared_props);
351 if !self.listed_slots.is_empty() {
352 s.field("listed_slots", &self.listed_slots);
353 }
354 if !self.named_slots.is_empty() {
355 s.field("named_slots", &self.named_slots);
356 }
357 s.finish()
358 }
359}
360
361impl TryFrom<WidgetNode> for WidgetComponent {
362 type Error = ();
363
364 fn try_from(node: WidgetNode) -> Result<Self, Self::Error> {
365 if let WidgetNode::Component(v) = node {
366 Ok(v)
367 } else {
368 Err(())
369 }
370 }
371}
372
373#[derive(Debug, Default, Clone, Serialize, Deserialize)]
374pub(crate) struct WidgetComponentPrefab {
375 #[serde(default)]
376 pub type_name: String,
377 #[serde(default)]
378 #[serde(skip_serializing_if = "Option::is_none")]
379 pub key: Option<String>,
380 #[serde(default)]
381 pub props: PrefabValue,
382 #[serde(default)]
383 #[serde(skip_serializing_if = "Option::is_none")]
384 pub shared_props: Option<PrefabValue>,
385 #[serde(default)]
386 #[serde(skip_serializing_if = "Vec::is_empty")]
387 pub listed_slots: Vec<WidgetNodePrefab>,
388 #[serde(default)]
389 #[serde(skip_serializing_if = "HashMap::is_empty")]
390 pub named_slots: HashMap<String, WidgetNodePrefab>,
391}