raui_core/widget/component/
mod.rs

1pub 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    /// (relative to id)
114    Register(WidgetId),
115    Unregister,
116    /// (outer box size, inner box rect)
117    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    // TODO: when user will change widget IDs after mounting, we might want to re-register
132    // this widget with new IDs.
133
134    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}