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