makepad_widgets/
adaptive_view.rs1use std::collections::HashMap;
2
3use crate::{
4 makepad_derive_widget::*, makepad_draw::*, widget::*, widget_match_event::WidgetMatchEvent,
5 WindowAction,
6};
7
8live_design! {
9 link widgets;
10 use link::widgets::*;
11 use link::theme::*;
12
13 pub AdaptiveViewBase = {{AdaptiveView}} {}
14 pub AdaptiveView = <AdaptiveViewBase> {
15 width: Fill, height: Fill
16
17 Mobile = <View> {}
18 Desktop = <View> {}
19 }
20}
21
22#[derive(Live, LiveRegisterWidget, WidgetRef)]
67pub struct AdaptiveView {
68 #[rust]
69 area: Area,
70
71 #[walk]
73 walk: Walk,
74
75 #[live]
78 retain_unused_variants: bool,
79
80 #[rust]
83 previously_active_widgets: HashMap<LiveId, WidgetVariant>,
84
85 #[rust]
87 templates: ComponentMap<LiveId, LivePtr>,
88
89 #[rust]
91 active_widget: Option<WidgetVariant>,
92
93 #[rust]
95 variant_selector: Option<Box<VariantSelector>>,
96
97 #[rust]
99 should_reapply_selector: bool,
100
101 #[rust]
106 has_custom_templates: bool,
107}
108
109pub struct WidgetVariant {
110 pub template_id: LiveId,
111 pub widget_ref: WidgetRef,
112}
113
114impl WidgetNode for AdaptiveView {
115 fn walk(&mut self, cx: &mut Cx) -> Walk {
116 if let Some(active_widget) = self.active_widget.as_ref() {
117 active_widget.widget_ref.walk(cx)
118 } else {
119 self.walk
121 }
122 }
123
124 fn area(&self) -> Area {
125 self.area
126 }
127
128 fn redraw(&mut self, cx: &mut Cx) {
129 self.area.redraw(cx);
130 }
131
132 fn find_widgets(&self, path: &[LiveId], cached: WidgetCache, results: &mut WidgetSet) {
133 if let Some(active_widget) = self.active_widget.as_ref() {
134 active_widget.widget_ref.find_widgets(path, cached, results);
135 }
136 }
137
138 fn uid_to_widget(&self, uid: WidgetUid) -> WidgetRef {
139 if let Some(active_widget) = self.active_widget.as_ref() {
140 active_widget.widget_ref.uid_to_widget(uid)
141 } else {
142 WidgetRef::empty()
143 }
144 }
145}
146
147impl LiveHook for AdaptiveView {
148 fn before_apply(
149 &mut self,
150 _cx: &mut Cx,
151 apply: &mut Apply,
152 _index: usize,
153 _nodes: &[LiveNode],
154 ) {
155 if let ApplyFrom::UpdateFromDoc { .. } = apply.from {
156 self.templates.clear();
157 }
158 }
159
160 fn after_apply_from(&mut self, cx: &mut Cx, apply: &mut Apply) {
161 if let ApplyFrom::UpdateFromDoc { .. } = apply.from {
163 return;
164 };
165
166 if !self.has_custom_templates {
169 let template = self.templates.get(&live_id!(Desktop)).unwrap();
170 let widget_ref = WidgetRef::new_from_ptr(cx, Some(*template));
171 self.active_widget = Some(WidgetVariant {
172 template_id: live_id!(Desktop),
173 widget_ref: widget_ref.clone(),
174 });
175 }
176 self.set_default_variant_selector();
177 }
178
179 fn apply_value_instance(
180 &mut self,
181 cx: &mut Cx,
182 apply: &mut Apply,
183 index: usize,
184 nodes: &[LiveNode],
185 ) -> usize {
186 if nodes[index].is_instance_prop() {
187 if let Some(live_ptr) = apply.from.to_live_ptr(cx, index) {
188 let id = nodes[index].id;
189 self.templates.insert(id, live_ptr);
190
191 if id != live_id!(Desktop) && id != live_id!(Mobile) {
192 self.has_custom_templates = true;
193 }
194
195 if let Some(widget_variant) = self.active_widget.as_mut() {
196 if widget_variant.template_id == id {
197 widget_variant.widget_ref.apply(cx, apply, index, nodes);
198 }
199 }
200 }
201 } else {
202 cx.apply_error_no_matching_field(live_error_origin!(), index, nodes);
203 }
204 nodes.skip_node(index)
205 }
206}
207
208impl Widget for AdaptiveView {
209 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
210 self.widget_match_event(cx, event, scope);
211 if let Some(active_widget) = self.active_widget.as_mut() {
212 active_widget.widget_ref.handle_event(cx, event, scope);
213 }
214 }
215
216 fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
217 if self.should_reapply_selector {
218 let parent_size = cx.peek_walk_turtle(walk).size;
219 self.apply_selector(cx, &parent_size);
220 }
221
222 if let Some(active_widget) = self.active_widget.as_mut() {
223 active_widget.widget_ref.draw_walk(cx, scope, walk)?;
224 }
225
226 DrawStep::done()
227 }
228}
229
230impl WidgetMatchEvent for AdaptiveView {
231 fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions, _scope: &mut Scope) {
232 for action in actions {
233 if let WindowAction::WindowGeomChange(ce) = action.as_widget_action().cast() {
235 let event_id = cx.event_id();
236
237 if cx.display_context.updated_on_event_id == event_id { return }
239 if cx.display_context.screen_size != ce.new_geom.inner_size {
241 cx.display_context.updated_on_event_id = event_id;
242 cx.display_context.screen_size = ce.new_geom.inner_size;
243
244 self.should_reapply_selector = true;
245 }
246
247 cx.redraw_all();
248 }
249 }
250 }
251}
252
253impl AdaptiveView {
254 fn apply_selector(&mut self, cx: &mut Cx, parent_size: &DVec2) {
256 let Some(variant_selector) = self.variant_selector.as_mut() else {
257 return;
258 };
259
260 let template_id = variant_selector(cx, parent_size);
261
262 if let Some(active_widget) = self.active_widget.as_mut() {
264 if active_widget.template_id == template_id {
265 return;
266 }
267 }
268
269 if self.retain_unused_variants && self.previously_active_widgets.contains_key(&template_id)
271 {
272 let widget_variant = self.previously_active_widgets.remove(&template_id).unwrap();
273
274 self.walk = widget_variant.widget_ref.walk(cx);
275 self.active_widget = Some(widget_variant);
276 return;
277 }
278
279 cx.widget_query_invalidation_event = Some(cx.event_id());
284
285 let template = self.templates.get(&template_id).unwrap();
287 let widget_ref = WidgetRef::new_from_ptr(cx, Some(*template));
288
289 self.walk = widget_ref.walk(cx);
292
293 if let Some(active_widget) = self.active_widget.take() {
294 if self.retain_unused_variants {
295 self.previously_active_widgets
296 .insert(active_widget.template_id, active_widget);
297 }
298 }
299
300 self.active_widget = Some(WidgetVariant {
301 template_id,
302 widget_ref,
303 });
304 }
305
306 pub fn set_variant_selector(
309 &mut self,
310 selector: impl FnMut(&mut Cx, &DVec2) -> LiveId + 'static,
311 ) {
312 self.variant_selector = Some(Box::new(selector));
313 self.should_reapply_selector = true;
314 }
315
316 pub fn set_default_variant_selector(&mut self) {
317 self.set_variant_selector(|cx, _parent_size| {
319 if cx.display_context.is_desktop() {
320 live_id!(Desktop)
321 } else {
322 live_id!(Mobile)
323 }
324 });
325 }
326}
327
328impl AdaptiveViewRef {
329 pub fn set_variant_selector(
332 &self,
333 selector: impl FnMut(&mut Cx, &DVec2) -> LiveId + 'static,
334 ) {
335 let Some(mut inner) = self.borrow_mut() else {
336 return;
337 };
338 inner.set_variant_selector(selector);
339 }
340}
341
342pub type VariantSelector = dyn FnMut(&mut Cx, &ParentSize) -> LiveId;
344
345type ParentSize = DVec2;