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}