raui_core/widget/component/containers/
float_box.rs1use crate::{
2 MessageData, PropsData, Scalar, make_widget, pre_hooks,
3 widget::{
4 WidgetId, WidgetIdOrRef,
5 component::{
6 containers::content_box::{ContentBoxProps, content_box},
7 interactive::navigation::{
8 NavContainerActive, NavItemActive, NavJumpActive, use_nav_container_active,
9 use_nav_item, use_nav_jump_direction_active,
10 },
11 },
12 context::{WidgetContext, WidgetMountOrChangeContext},
13 node::WidgetNode,
14 unit::content::ContentBoxContentReposition,
15 utils::Vec2,
16 },
17};
18use serde::{Deserialize, Serialize};
19
20#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
21#[props_data(crate::props::PropsData)]
22#[prefab(crate::Prefab)]
23pub struct FloatBoxProps {
24 #[serde(default)]
25 pub bounds_left: Option<Scalar>,
26 #[serde(default)]
27 pub bounds_right: Option<Scalar>,
28 #[serde(default)]
29 pub bounds_top: Option<Scalar>,
30 #[serde(default)]
31 pub bounds_bottom: Option<Scalar>,
32}
33
34#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
35#[props_data(crate::props::PropsData)]
36#[prefab(crate::Prefab)]
37pub struct FloatBoxNotifyProps(
38 #[serde(default)]
39 #[serde(skip_serializing_if = "WidgetIdOrRef::is_none")]
40 pub WidgetIdOrRef,
41);
42
43#[derive(PropsData, Debug, Copy, Clone, Serialize, Deserialize)]
44#[props_data(crate::props::PropsData)]
45#[prefab(crate::Prefab)]
46pub struct FloatBoxState {
47 #[serde(default)]
48 pub position: Vec2,
49 #[serde(default = "FloatBoxState::default_zoom")]
50 pub zoom: Scalar,
51}
52
53impl Default for FloatBoxState {
54 fn default() -> Self {
55 Self {
56 position: Default::default(),
57 zoom: Self::default_zoom(),
58 }
59 }
60}
61
62impl FloatBoxState {
63 fn default_zoom() -> Scalar {
64 1.0
65 }
66}
67
68#[derive(MessageData, Debug, Default, Clone)]
69#[message_data(crate::messenger::MessageData)]
70pub struct FloatBoxNotifyMessage {
71 pub sender: WidgetId,
72 pub state: FloatBoxState,
73 pub prev: FloatBoxState,
74}
75
76#[derive(MessageData, Debug, Clone)]
77#[message_data(crate::messenger::MessageData)]
78pub struct FloatBoxChangeMessage {
79 pub sender: WidgetId,
80 pub change: FloatBoxChange,
81}
82
83#[derive(Debug, Clone)]
84pub enum FloatBoxChange {
85 Absolute(FloatBoxState),
86 RelativePosition(Vec2),
87 RelativeZoom(Scalar),
88}
89
90pub fn use_float_box(context: &mut WidgetContext) {
91 fn notify(context: &WidgetMountOrChangeContext, data: FloatBoxNotifyMessage) {
92 if let Ok(FloatBoxNotifyProps(notify)) = context.props.read()
93 && let Some(to) = notify.read()
94 {
95 context.messenger.write(to, data);
96 }
97 }
98
99 context.life_cycle.mount(|context| {
100 let props = context.props.read_cloned_or_default::<FloatBoxProps>();
101 let mut data = context.props.read_cloned_or_default::<FloatBoxState>();
102 if let Some(limit) = props.bounds_left {
103 data.position.x = data.position.x.max(limit);
104 }
105 if let Some(limit) = props.bounds_right {
106 data.position.x = data.position.x.min(limit);
107 }
108 if let Some(limit) = props.bounds_top {
109 data.position.y = data.position.y.max(limit);
110 }
111 if let Some(limit) = props.bounds_bottom {
112 data.position.y = data.position.y.min(limit);
113 }
114 notify(
115 &context,
116 FloatBoxNotifyMessage {
117 sender: context.id.to_owned(),
118 state: data,
119 prev: data,
120 },
121 );
122 let _ = context.state.write_with(data);
123 });
124
125 context.life_cycle.change(|context| {
126 let props = context.props.read_cloned_or_default::<FloatBoxProps>();
127 let mut dirty = false;
128 let mut data = context.state.read_cloned_or_default::<FloatBoxState>();
129 let prev = data;
130 for msg in context.messenger.messages {
131 if let Some(msg) = msg.as_any().downcast_ref::<FloatBoxChangeMessage>() {
132 match msg.change {
133 FloatBoxChange::Absolute(value) => {
134 data = value;
135 dirty = true;
136 }
137 FloatBoxChange::RelativePosition(delta) => {
138 data.position.x -= delta.x;
139 data.position.y -= delta.y;
140 dirty = true;
141 }
142 FloatBoxChange::RelativeZoom(delta) => {
143 data.zoom *= delta;
144 dirty = true;
145 }
146 }
147 }
148 }
149 if dirty {
150 if let Some(limit) = props.bounds_left {
151 data.position.x = data.position.x.max(limit);
152 }
153 if let Some(limit) = props.bounds_right {
154 data.position.x = data.position.x.min(limit);
155 }
156 if let Some(limit) = props.bounds_top {
157 data.position.y = data.position.y.max(limit);
158 }
159 if let Some(limit) = props.bounds_bottom {
160 data.position.y = data.position.y.min(limit);
161 }
162 notify(
163 &context,
164 FloatBoxNotifyMessage {
165 sender: context.id.to_owned(),
166 state: data.to_owned(),
167 prev,
168 },
169 );
170 let _ = context.state.write_with(data);
171 }
172 });
173}
174
175#[pre_hooks(use_nav_container_active, use_nav_jump_direction_active, use_nav_item)]
176pub fn nav_float_box(mut context: WidgetContext) -> WidgetNode {
177 let WidgetContext {
178 key,
179 props,
180 listed_slots,
181 ..
182 } = context;
183
184 let props = props
185 .clone()
186 .without::<NavContainerActive>()
187 .without::<NavJumpActive>()
188 .without::<NavItemActive>();
189
190 make_widget!(float_box)
191 .key(key)
192 .merge_props(props)
193 .listed_slots(listed_slots)
194 .into()
195}
196
197#[pre_hooks(use_float_box)]
198pub fn float_box(mut context: WidgetContext) -> WidgetNode {
199 let WidgetContext {
200 key,
201 props,
202 state,
203 mut listed_slots,
204 ..
205 } = context;
206
207 let mut props = props.read_cloned_or_default::<ContentBoxProps>();
208 let state = state.read_cloned_or_default::<FloatBoxState>();
209 props.content_reposition = ContentBoxContentReposition {
210 offset: Vec2 {
211 x: -state.position.x,
212 y: -state.position.y,
213 },
214 scale: Vec2 {
215 x: state.zoom,
216 y: state.zoom,
217 },
218 };
219
220 for item in listed_slots.iter_mut() {
221 if let Some(p) = item.props_mut() {
222 p.write(state);
223 if !p.has::<FloatBoxNotifyProps>() {
224 p.write(FloatBoxNotifyProps(context.id.to_owned().into()));
225 }
226 }
227 }
228
229 make_widget!(content_box)
230 .key(key)
231 .with_props(props)
232 .listed_slots(listed_slots)
233 .into()
234}