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    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    /// (relative to id)
122    Register(WidgetId),
123    Unregister,
124    /// (outer box size, inner box rect)
125    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    // TODO: when user will change widget IDs after mounting, we might want to re-register
140    // this widget with new IDs.
141
142    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}