geng_ui/
controller.rs

1use super::*;
2
3struct State {
4    size: vec2<f64>,
5    scale: f64,
6    constraints: HashMap<*const c_void, Constraints>,
7    positions: HashMap<*const c_void, Aabb2<f64>>,
8    states: Vec<std::cell::UnsafeCell<Box<dyn std::any::Any>>>,
9    next_state: usize,
10    cursor_pos: Option<vec2<f64>>,
11}
12
13impl State {
14    fn get_constraints(&self, widget: &dyn Widget) -> Constraints {
15        self.constraints[&widget_ptr(widget)]
16    }
17    fn set_constraints(&mut self, widget: &dyn Widget, constraints: Constraints) {
18        self.constraints.insert(widget_ptr(widget), constraints);
19    }
20    fn get_position(&self, widget: &dyn Widget) -> Aabb2<f64> {
21        self.positions[&widget_ptr(widget)]
22    }
23    fn set_position(&mut self, widget: &dyn Widget, position: Aabb2<f64>) {
24        self.positions.insert(widget_ptr(widget), position);
25    }
26}
27
28fn widget_ptr(widget: &dyn Widget) -> *const c_void {
29    widget as *const _ as _
30}
31
32pub struct ConstraintsContext<'a> {
33    pub theme: &'a Theme,
34    state: &'a State,
35}
36
37impl ConstraintsContext<'_> {
38    pub fn get_constraints(&self, widget: &dyn Widget) -> Constraints {
39        self.state.get_constraints(widget)
40    }
41}
42
43pub struct LayoutContext<'a> {
44    pub theme: &'a Theme,
45    pub position: Aabb2<f64>,
46    state: &'a mut State,
47}
48
49impl LayoutContext<'_> {
50    pub fn get_constraints(&self, widget: &dyn Widget) -> Constraints {
51        self.state.get_constraints(widget)
52    }
53    pub fn set_position(&mut self, widget: &dyn Widget, position: Aabb2<f64>) {
54        self.state.set_position(widget, position);
55    }
56}
57
58pub struct Controller {
59    target_ui_resolution: Option<vec2<f64>>,
60    draw2d: draw2d::Helper,
61    theme: Theme,
62    state: RefCell<State>,
63}
64
65impl Controller {
66    pub fn new(ugli: &Ugli, theme: Theme, target_ui_resolution: Option<vec2<f64>>) -> Self {
67        Self {
68            target_ui_resolution,
69            draw2d: draw2d::Helper::new(ugli, true),
70            theme,
71            state: RefCell::new(State {
72                size: vec2(1.0, 1.0),
73                scale: 1.0,
74                constraints: Default::default(),
75                positions: Default::default(),
76                states: Vec::new(),
77                next_state: 0,
78                cursor_pos: None,
79            }),
80        }
81    }
82
83    pub fn draw2d(&self) -> &draw2d::Helper {
84        &self.draw2d
85    }
86    pub fn theme(&self) -> &Theme {
87        &self.theme
88    }
89    pub fn get_state<T: Default + 'static>(&self) -> &mut T {
90        self.get_state_with(T::default)
91    }
92    #[allow(clippy::mut_from_ref)]
93    pub fn get_state_with<T: 'static>(&self, f: impl FnOnce() -> T) -> &mut T {
94        let mut f = Some(f);
95        let mut state = self.state.borrow_mut();
96        if state.next_state >= state.states.len() {
97            state
98                .states
99                .push(std::cell::UnsafeCell::new(Box::new(f.take().unwrap()())));
100        }
101        let current: &mut Box<dyn std::any::Any> =
102            unsafe { &mut *state.states[state.next_state].get() };
103        if !current.is::<T>() {
104            *current = Box::new(f.take().unwrap()());
105        }
106        state.next_state += 1;
107        current.downcast_mut().unwrap()
108    }
109}
110
111impl Controller {
112    pub fn update(&self, root: &mut dyn Widget, delta_time: f64) {
113        self.layout(root);
114        traverse_mut(root, &mut |widget| widget.update(delta_time), &mut |_| {});
115    }
116    fn layout(&self, root: &mut dyn Widget) {
117        let mut state = self.state.borrow_mut();
118        let state = state.deref_mut();
119        state.constraints.clear();
120        state.positions.clear();
121        traverse_mut(root, &mut |_| {}, &mut |widget| {
122            let constraints = widget.calc_constraints(&ConstraintsContext {
123                theme: &self.theme,
124                state,
125            });
126            state.set_constraints(widget, constraints);
127        });
128        let root_position = Aabb2::ZERO.extend_positive(state.size);
129        state.set_position(root, root_position);
130        traverse_mut(
131            root,
132            &mut |widget| {
133                widget.layout_children(&mut LayoutContext {
134                    theme: &self.theme,
135                    position: state.get_position(widget),
136                    state,
137                });
138            },
139            &mut |_| {},
140        );
141        for position in state.positions.values_mut() {
142            *position = position.map(|x| x * state.scale);
143        }
144
145        while state.states.len() > state.next_state {
146            state.states.pop();
147        }
148        state.next_state = 0;
149    }
150    pub fn draw(&self, root: &mut dyn Widget, framebuffer: &mut ugli::Framebuffer) {
151        {
152            let mut state = self.state.borrow_mut();
153            let framebuffer_size = framebuffer.size().map(|x| x as f64);
154            state.scale = match self.target_ui_resolution {
155                Some(target_size) => {
156                    (framebuffer_size.x / target_size.x).max(framebuffer_size.y / target_size.y)
157                }
158                None => 1.0,
159            };
160            state.size = framebuffer_size / state.scale;
161        }
162        self.layout(root);
163        let state = self.state.borrow();
164        traverse_mut(
165            root,
166            &mut |widget| {
167                widget.draw(&mut DrawContext {
168                    draw2d: &self.draw2d,
169                    theme: &self.theme,
170                    position: state.get_position(widget),
171                    framebuffer,
172                });
173            },
174            &mut |_| {},
175        );
176    }
177    pub fn handle_event(&self, root: &mut dyn Widget, event: Event) -> bool {
178        let event = &event;
179        self.layout(root);
180        let mut state = self.state.borrow_mut();
181        let mut captured = false;
182        traverse_mut(
183            root,
184            &mut |widget| {
185                let widget_position = state.get_position(widget);
186                enum CursorEvent {
187                    Move(vec2<f64>),
188                    Press(vec2<f64>),
189                    Release(vec2<f64>),
190                }
191                let cursor = match *event {
192                    Event::CursorMove { position } => {
193                        state.cursor_pos = Some(position);
194                        CursorEvent::Move(position)
195                    }
196                    Event::MousePress { .. } => {
197                        if let Some(position) = state.cursor_pos {
198                            CursorEvent::Press(position)
199                        } else {
200                            return;
201                        }
202                    }
203                    Event::MouseRelease { .. } => {
204                        if let Some(position) = state.cursor_pos {
205                            CursorEvent::Release(position)
206                        } else {
207                            return;
208                        }
209                    }
210                    Event::TouchMove(Touch { position, .. }) => CursorEvent::Move(position),
211                    Event::TouchStart(Touch { position, .. }) => CursorEvent::Press(position),
212                    Event::TouchEnd(Touch { position, .. }) => CursorEvent::Release(position),
213                    _ => return,
214                };
215                match cursor {
216                    CursorEvent::Move(position) => {
217                        if let Some(sense) = widget.sense() {
218                            sense.set_hovered(widget_position.contains(position));
219                        }
220                        widget.handle_event(event);
221                    }
222                    CursorEvent::Press(position) => {
223                        if widget_position.contains(position) {
224                            if let Some(sense) = widget.sense() {
225                                sense.set_captured(true);
226                                widget.handle_event(event);
227                            }
228                        } else if let Some(sense) = widget.sense() {
229                            if sense.is_captured() {
230                                widget.handle_event(event);
231                            }
232                        }
233                    }
234                    CursorEvent::Release(position) => {
235                        let mut default_sense = Sense::default();
236                        let sense = widget.sense().unwrap_or(&mut default_sense);
237                        let was_captured = sense.is_captured();
238                        sense.set_captured(false);
239                        if was_captured && widget_position.contains(position) {
240                            sense.click();
241                        }
242                        if was_captured || widget_position.contains(position) {
243                            widget.handle_event(event);
244                        }
245                    }
246                }
247            },
248            &mut |widget| {
249                if let Some(sense) = widget.sense() {
250                    if sense.is_captured() {
251                        captured = true;
252                    }
253                }
254            },
255        );
256        captured
257    }
258}