iris_ui/
scene.rs

1use crate::geom::{Bounds, Point};
2use crate::gfx::DrawingContext;
3use crate::view::{View, ViewId};
4use crate::{Action, Callback, DrawEvent, EventType, GuiEvent, LayoutEvent, LayoutFn, Theme};
5use alloc::vec::Vec;
6use alloc::{format, vec};
7use hashbrown::HashMap;
8use log::{info, warn};
9
10#[derive(Debug)]
11pub struct Scene {
12    pub(crate) keys: HashMap<ViewId, View>,
13    children: HashMap<ViewId, Vec<ViewId>>,
14    parents: HashMap<ViewId, ViewId>,
15    pub(crate) dirty: bool,
16    pub bounds: Bounds,
17    pub dirty_rect: Bounds,
18    pub root_id: ViewId,
19    pub(crate) focused: Option<ViewId>,
20    pub layout_dirty: bool,
21}
22
23impl Scene {
24    pub fn dump(&self) {
25        info!("scene");
26        info!(
27            " dirty {} {}, focused {:?}",
28            self.dirty, self.dirty_rect, self.focused
29        );
30        self.dump_view(&self.root_id.clone(), "");
31    }
32    fn dump_view(&self, id: &ViewId, indent: &str) {
33        if let Some(view) = self.get_view(&id) {
34            info!("{indent}{id} ---");
35            // info!("{indent}  padding {}", view.padding);
36            info!("{indent}  bounds  {}", view.bounds);
37            info!("{indent}  h = {:?} {:?}", view.h_flex, view.h_align);
38            info!("{indent}  v = {:?} {:?}", view.v_flex, view.v_align);
39        }
40        let kids = self.get_children_ids(id);
41        for kid in kids {
42            self.dump_view(&kid, &format!("{indent}    "));
43        }
44    }
45}
46
47impl Scene {
48    pub fn root_id(&self) -> ViewId {
49        self.root_id
50    }
51    pub fn set_focused(&mut self, name: &ViewId) {
52        if self.focused.is_some() {
53            let fo = self.focused.as_ref().unwrap().clone();
54            self.mark_dirty_view(&fo);
55        }
56        self.focused = Some(name.clone());
57        self.mark_dirty_view(name);
58    }
59    pub fn get_focused(&self) -> Option<ViewId> {
60        self.focused.clone()
61    }
62    pub fn is_focused(&self, name: &ViewId) -> bool {
63        self.focused.as_ref().is_some_and(|focused| focused == name)
64    }
65    pub fn is_visible(&self, name: &ViewId) -> bool {
66        if let Some(view) = self.get_view(name) {
67            view.visible
68        } else {
69            false
70        }
71    }
72    pub fn show_view(&mut self, name: &ViewId) {
73        if let Some(view) = self.get_view_mut(name) {
74            view.visible = true;
75        }
76        self.mark_dirty_view(name);
77    }
78    pub fn hide_view(&mut self, name: &ViewId) {
79        if let Some(view) = self.get_view_mut(name) {
80            view.visible = false;
81        }
82        self.mark_dirty_view(name);
83    }
84    pub fn mark_dirty_all(&mut self) {
85        self.dirty_rect = self.bounds;
86        self.dirty = true;
87    }
88    pub fn mark_dirty_view(&mut self, name: &ViewId) {
89        if let Some(view) = self.get_view(name) {
90            let global_bounds = self.get_view_global_bounds(view);
91            self.dirty_rect = self.dirty_rect.union(global_bounds);
92            self.dirty = true;
93        }
94    }
95    pub fn mark_layout_dirty(&mut self) {
96        self.layout_dirty = true;
97        self.mark_dirty_all();
98    }
99
100    pub fn get_children_ids(&self, name: &ViewId) -> Vec<ViewId> {
101        if let Some(children) = self.children.get(name) {
102            children.clone()
103        } else {
104            Vec::new()
105        }
106    }
107    pub fn get_children_ids_filtered(&self, id: &ViewId, cb: fn(&View) -> bool) -> Vec<ViewId> {
108        self.get_children_ids(id)
109            .iter()
110            .map(|kid| self.get_view(kid))
111            .flatten()
112            .filter(|v| cb(v)) // WORKS
113            // .filter(cb) // DOESN'T WORK
114            .map(|v| v.name.clone())
115            .collect()
116    }
117
118    pub(crate) fn has_view(&self, name: &ViewId) -> bool {
119        self.keys.contains_key(name)
120    }
121    pub fn get_view(&self, name: &ViewId) -> Option<&View> {
122        self.keys.get(name)
123    }
124    pub fn get_view_mut(&mut self, name: &ViewId) -> Option<&mut View> {
125        self.keys.get_mut(name)
126    }
127    pub fn get_view_state<T: 'static>(&mut self, name: &ViewId) -> Option<&mut T> {
128        if let Some(view) = self.get_view_mut(name) {
129            if let Some(view) = &mut view.state {
130                return view.downcast_mut::<T>();
131            }
132        }
133        None
134    }
135    pub fn get_view_layout(&mut self, name: &ViewId) -> Option<LayoutFn> {
136        if let Some(view) = self.get_view_mut(name) {
137            return view.layout;
138        }
139        None
140    }
141    pub(crate) fn get_view_bounds(&self, name: &ViewId) -> Option<Bounds> {
142        if let Some(view) = self.get_view(name) {
143            return Some(view.bounds.clone());
144        }
145        None
146    }
147    pub(crate) fn viewcount(&self) -> usize {
148        self.keys.len()
149    }
150    pub fn remove_view(&mut self, name: &ViewId) -> Option<View> {
151        self.mark_dirty_view(name);
152        self.keys.remove(name)
153    }
154    pub fn get_parent_for_view(&self, name: &ViewId) -> Option<&ViewId> {
155        self.parents.get(name)
156    }
157    pub fn remove_view_from_parent(&mut self, parent: &ViewId, child: &ViewId) {
158        if let Some(children) = self.children.get_mut(parent) {
159            if let Some(n) = children.iter().position(|name| name == child) {
160                children.remove(n);
161            }
162        }
163        if self.parents.contains_key(child) {
164            self.parents.remove(child);
165        } else {
166            warn!("parent {parent} does not contain child {child}");
167        }
168    }
169    pub fn new_with_bounds(bounds: Bounds) -> Scene {
170        let root_id = ViewId::new("root");
171        let root = View {
172            name: root_id.clone(),
173            title: root_id.as_str().into(),
174            bounds,
175            visible: true,
176            input: None,
177            state: None,
178            layout: Some(layout_root_panel),
179            draw: Some(|e| e.ctx.fill_rect(&e.view.bounds, &e.theme.panel_bg)),
180            ..Default::default()
181        };
182        let mut keys: HashMap<ViewId, View> = HashMap::new();
183        keys.insert(root_id.clone(), root);
184        Scene {
185            bounds,
186            keys,
187            dirty: true,
188            layout_dirty: true,
189            root_id,
190            focused: None,
191            dirty_rect: bounds,
192            children: HashMap::new(),
193            parents: HashMap::new(),
194        }
195    }
196    pub fn new() -> Scene {
197        let bounds = Bounds::new(0, 0, 200, 200);
198        Self::new_with_bounds(bounds)
199    }
200    pub fn add_view(&mut self, view: View) {
201        let name = view.name.clone();
202        if self.keys.contains_key(&name) {
203            warn!("might be adding duplicate view key {name}");
204        }
205        self.keys.insert(name.clone(), view);
206        self.mark_dirty_view(&name);
207    }
208    pub fn add_view_to_root(&mut self, view: View) {
209        self.add_view_to_parent(view, &self.root_id.clone());
210    }
211    pub fn add_view_to_parent(&mut self, view: View, parent: &ViewId) {
212        if !self.children.contains_key(parent) {
213            self.children.insert(parent.clone(), vec![]);
214        }
215        self.parents.insert(view.name.clone(), parent.clone());
216        if let Some(children) = self.children.get_mut(parent) {
217            children.push(view.name.clone());
218        }
219        self.add_view(view);
220    }
221    pub fn move_view_to_parent(&mut self, child: &ViewId, parent: &ViewId) {
222        if !self.children.contains_key(parent) {
223            self.children.insert(parent.clone(), vec![]);
224        }
225        if let Some(children) = self.children.get_mut(parent) {
226            children.push(child.clone());
227        }
228    }
229    pub fn remove_parent_and_children(&mut self, name: &ViewId) {
230        let kids = self.get_children_ids(name);
231        for kid in kids {
232            self.remove_view(&kid);
233            self.remove_view_from_parent(name, &kid);
234        }
235        self.remove_view(name);
236    }
237
238    fn get_view_global_bounds(&self, view: &View) -> Bounds {
239        let mut current = &view.name;
240        let mut offset = Point::zero();
241        while let Some(parent) = self.parents.get(current) {
242            if let Some(bounds) = self.get_view_bounds(parent) {
243                offset = offset + bounds.position;
244            }
245            current = parent;
246        }
247        view.bounds + offset
248    }
249}
250
251fn layout_root_panel(pass: &mut LayoutEvent) {
252    if let Some(view) = pass.scene.get_view_mut(&pass.target) {
253        view.bounds.size.w = pass.space.w;
254        view.bounds.size.h = pass.space.h;
255    }
256    for kid in &pass.scene.get_children_ids(&pass.target) {
257        pass.layout_child(kid, pass.space);
258    }
259}
260
261pub type EventResult = (ViewId, Action);
262
263pub fn click_at(scene: &mut Scene, handlers: &Vec<Callback>, pt: Point) -> Option<EventResult> {
264    let targets = pick_at(scene, &pt);
265    if let Some((target, pt)) = targets.last() {
266        let mut event: GuiEvent = GuiEvent {
267            scene,
268            target,
269            event_type: EventType::Tap(pt.clone()),
270            action: None,
271        };
272        if let Some(view) = event.scene.get_view(target) {
273            if let Some(input) = view.input {
274                event.action = input(&mut event);
275            }
276        }
277        for cb in handlers {
278            cb(&mut event);
279        }
280        if let Some(action) = event.action {
281            return Some((target.clone(), action));
282        }
283    }
284    None
285}
286
287pub fn event_at_focused(scene: &mut Scene, event_type: &EventType) -> Option<EventResult> {
288    if scene.focused.is_some() {
289        let focused = scene.focused.as_ref().unwrap().clone();
290        let mut event: GuiEvent = GuiEvent {
291            scene,
292            target: &focused,
293            event_type: event_type.clone(),
294            action: None,
295        };
296        if let Some(view) = event.scene.get_view(&focused) {
297            if let Some(input) = view.input {
298                event.action = input(&mut event);
299            }
300            if let Some(action) = event.action {
301                return Some((focused, action));
302            }
303        }
304    }
305    None
306}
307
308type Pick = (ViewId, Point);
309
310pub fn pick_at(scene: &mut Scene, pt: &Point) -> Vec<Pick> {
311    pick_at_view(scene, pt, &scene.root_id)
312}
313
314fn pick_at_view(scene: &Scene, pt: &Point, name: &ViewId) -> Vec<Pick> {
315    let mut coll: Vec<Pick> = vec![];
316    if let Some(view) = scene.keys.get(name) {
317        if view.bounds.contains(pt) && view.visible {
318            coll.push((view.name.clone(), pt.clone()));
319            let pt2 = pt.subtract(&view.bounds.position);
320            for kid in scene.get_children_ids(&view.name) {
321                let mut coll2 = pick_at_view(scene, &pt2, &kid);
322                coll.append(&mut coll2);
323            }
324        }
325    }
326    coll
327}
328
329pub fn draw_scene(scene: &mut Scene, ctx: &mut dyn DrawingContext, theme: &Theme) {
330    if scene.dirty {
331        ctx.fill_rect(&scene.bounds, &theme.panel_bg);
332        let name = scene.root_id.clone();
333        draw_view(scene, ctx, theme, &name);
334        scene.dirty = false;
335        scene.dirty_rect = Bounds::new_empty();
336    }
337}
338
339fn draw_view(scene: &mut Scene, ctx: &mut dyn DrawingContext, theme: &Theme, name: &ViewId) {
340    let focused = &scene.focused.clone();
341    let bounds = &scene.bounds.clone();
342    if let Some(view) = scene.get_view_mut(name)
343        && view.visible
344    {
345        if let Some(draw) = view.draw {
346            let mut de: DrawEvent = DrawEvent {
347                theme,
348                view,
349                ctx,
350                focused,
351                bounds,
352            };
353            draw(&mut de);
354        }
355    }
356    if let Some(view) = scene.get_view(name) {
357        // only draw children if visible
358        if view.visible {
359            let bounds = view.bounds;
360            ctx.translate(&bounds.position);
361            for kid in scene.get_children_ids(&view.name) {
362                draw_view(scene, ctx, theme, &kid);
363            }
364            ctx.translate(&bounds.position.negate());
365        }
366    }
367}
368
369pub fn layout_scene(scene: &mut Scene, theme: &Theme) {
370    if scene.layout_dirty {
371        let mut pass = LayoutEvent {
372            target: &scene.root_id(),
373            space: scene.bounds.size,
374            scene,
375            theme,
376        };
377        if let Some(layout) = pass.scene.get_view_layout(&pass.scene.root_id()) {
378            layout(&mut pass);
379        }
380        scene.layout_dirty = false;
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use crate::geom::Bounds;
387    use crate::scene::Scene;
388    use crate::view::ViewId;
389
390    #[test]
391    fn basic_add_remove() {
392        let mut scene: Scene = Scene::new_with_bounds(Bounds::new(0, 0, 100, 30));
393        assert_eq!(scene.viewcount(), 1);
394        let view = crate::tests::make_simple_view(&"foo".into());
395        assert_eq!(scene.viewcount(), 1);
396        scene.add_view(view);
397        assert_eq!(scene.viewcount(), 2);
398        assert!(scene.get_view(&"foo".into()).is_some());
399        let res = scene.remove_view(&"foo".into());
400        assert_eq!(res.is_some(), true);
401        assert_eq!(scene.viewcount(), 1);
402        let res2 = scene.remove_view(&"bar".into());
403        assert_eq!(res2.is_some(), false);
404    }
405    #[test]
406    fn parent_child() {
407        let mut scene: Scene = Scene::new();
408        let parent_id: ViewId = "parent".into();
409        let child_id: ViewId = "child".into();
410        let parent_view = crate::tests::make_simple_view(&parent_id);
411        scene.add_view(parent_view);
412
413        let child_view = crate::tests::make_simple_view(&child_id);
414        assert_eq!(scene.get_children_ids(&parent_id).len(), 0);
415        assert_eq!(scene.viewcount(), 2);
416        scene.add_view_to_parent(child_view, &parent_id);
417        assert_eq!(scene.get_children_ids(&parent_id).len(), 1);
418        assert_eq!(scene.get_parent_for_view(&child_id).unwrap(), &parent_id);
419        scene.remove_view_from_parent(&parent_id, &child_id);
420        assert_eq!(scene.get_children_ids(&parent_id).len(), 0);
421        assert!(scene.get_parent_for_view(&child_id).is_none());
422
423        scene.move_view_to_parent(&child_id, &parent_id);
424        assert_eq!(scene.get_children_ids(&parent_id).len(), 1);
425        let child2 = crate::tests::make_simple_view(&"child2".into());
426        scene.add_view_to_parent(child2, &parent_id);
427        assert_eq!(scene.get_children_ids(&parent_id).len(), 2);
428        assert_eq!(scene.viewcount(), 4);
429
430        scene.remove_parent_and_children(&parent_id);
431        assert_eq!(scene.get_children_ids(&parent_id).len(), 0);
432        assert_eq!(scene.viewcount(), 1);
433    }
434}