raui_core/widget/component/interactive/
scroll_view.rs1use crate::{
2    messenger::MessageData,
3    pre_hooks,
4    widget::{
5        component::interactive::navigation::{use_nav_scroll_view, NavJump, NavScroll, NavSignal},
6        context::{WidgetContext, WidgetMountOrChangeContext},
7        utils::Vec2,
8        WidgetId, WidgetIdOrRef,
9    },
10    MessageData, PropsData,
11};
12use serde::{Deserialize, Serialize};
13
14fn is_zero(v: &Vec2) -> bool {
15    v.x.abs() < 1.0e-6 && v.y.abs() < 1.0e-6
16}
17
18#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
19#[props_data(crate::props::PropsData)]
20#[prefab(crate::Prefab)]
21pub struct ScrollViewState {
22    #[serde(default)]
23    pub value: Vec2,
24    #[serde(default)]
25    pub size_factor: Vec2,
26}
27
28#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]
29#[props_data(crate::props::PropsData)]
30#[prefab(crate::Prefab)]
31pub struct ScrollViewRange {
32    #[serde(default)]
33    #[serde(skip_serializing_if = "is_zero")]
34    pub from: Vec2,
35    #[serde(default)]
36    #[serde(skip_serializing_if = "is_zero")]
37    pub to: Vec2,
38}
39
40impl Default for ScrollViewRange {
41    fn default() -> Self {
42        Self {
43            from: Vec2 { x: 0.0, y: 0.0 },
44            to: Vec2 { x: 1.0, y: 1.0 },
45        }
46    }
47}
48
49#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
50#[props_data(crate::props::PropsData)]
51#[prefab(crate::Prefab)]
52pub struct ScrollViewNotifyProps(
53    #[serde(default)]
54    #[serde(skip_serializing_if = "WidgetIdOrRef::is_none")]
55    pub WidgetIdOrRef,
56);
57
58#[derive(MessageData, Debug, Clone)]
59#[message_data(crate::messenger::MessageData)]
60pub struct ScrollViewNotifyMessage {
61    pub sender: WidgetId,
62    pub state: ScrollViewState,
63}
64
65pub fn use_scroll_view_notified_state(context: &mut WidgetContext) {
66    context.life_cycle.change(|context| {
67        for msg in context.messenger.messages {
68            if let Some(msg) = msg.as_any().downcast_ref::<ScrollViewNotifyMessage>() {
69                let _ = context.state.write_with(msg.state.clone());
70            }
71        }
72    });
73}
74
75#[pre_hooks(use_nav_scroll_view)]
76pub fn use_scroll_view(context: &mut WidgetContext) {
77    fn notify<T>(context: &WidgetMountOrChangeContext, data: T)
78    where
79        T: 'static + MessageData,
80    {
81        if let Ok(notify) = context.props.read::<ScrollViewNotifyProps>() {
82            if let Some(to) = notify.0.read() {
83                context.messenger.write(to, data);
84            }
85        }
86    }
87
88    context.life_cycle.mount(|context| {
89        notify(
90            &context,
91            ScrollViewNotifyMessage {
92                sender: context.id.to_owned(),
93                state: ScrollViewState::default(),
94            },
95        );
96        let _ = context.state.write_with(ScrollViewState::default());
97    });
98
99    context.life_cycle.change(|context| {
100        let mut dirty = false;
101        let mut data = context.state.read_cloned_or_default::<ScrollViewState>();
102        let range = context.props.read::<ScrollViewRange>();
103        for msg in context.messenger.messages {
104            if let Some(NavSignal::Jump(NavJump::Scroll(NavScroll::Change(
105                value,
106                factor,
107                relative,
108            )))) = msg.as_any().downcast_ref()
109            {
110                if *relative {
111                    data.value.x += value.x;
112                    data.value.y += value.y;
113                } else {
114                    data.value = *value;
115                }
116                if factor.x <= 1.0 {
117                    data.value.x = 0.0;
118                }
119                if factor.y <= 1.0 {
120                    data.value.y = 0.0;
121                }
122                if let Ok(range) = &range {
123                    data.value.x = data.value.x.max(range.from.x).min(range.to.x);
124                    data.value.y = data.value.y.max(range.from.y).min(range.to.y);
125                }
126                data.size_factor = *factor;
127                dirty = true;
128            }
129        }
130        if dirty {
131            notify(
132                &context,
133                ScrollViewNotifyMessage {
134                    sender: context.id.to_owned(),
135                    state: data.clone(),
136                },
137            );
138            let _ = context.state.write_with(data);
139        }
140    });
141}