microui_redux/widget_tree/
cache.rs1use std::collections::HashMap;
34
35use rs_math3d::{Dimensioni, Recti};
36
37use crate::input::{ControlState, ResourceState};
38
39use super::NodeId;
40
41#[derive(Copy, Clone, Debug, Default)]
47pub struct NodeLayout {
48 pub rect: Recti,
50 pub body: Recti,
52 pub content_size: Dimensioni,
54}
55
56impl NodeLayout {
57 pub const fn new(rect: Recti, body: Recti, content_size: Dimensioni) -> Self {
59 Self { rect, body, content_size }
60 }
61}
62
63#[derive(Copy, Clone, Debug)]
65pub struct NodeInteraction {
66 pub control: ControlState,
68 pub result: ResourceState,
70}
71
72impl NodeInteraction {
73 pub const fn new(control: ControlState, result: ResourceState) -> Self {
75 Self { control, result }
76 }
77}
78
79impl Default for NodeInteraction {
80 fn default() -> Self {
81 Self::new(ControlState::default(), ResourceState::NONE)
82 }
83}
84
85#[derive(Copy, Clone, Debug, Default)]
88pub struct NodeFrameState {
89 pub layout: NodeLayout,
91 pub interaction: NodeInteraction,
93}
94
95impl NodeFrameState {
96 pub const fn new(layout: NodeLayout, interaction: NodeInteraction) -> Self {
98 Self { layout, interaction }
99 }
100}
101
102#[derive(Default)]
108pub struct WidgetTreeCache {
109 prev_layout: HashMap<NodeId, NodeLayout>,
110 curr_layout: HashMap<NodeId, NodeLayout>,
111 prev_interaction: HashMap<NodeId, NodeInteraction>,
112 curr_interaction: HashMap<NodeId, NodeInteraction>,
113}
114
115impl WidgetTreeCache {
116 pub fn begin_frame(&mut self) {
118 self.curr_layout.clear();
119 self.curr_interaction.clear();
120 }
121
122 pub fn finish_frame(&mut self) {
124 std::mem::swap(&mut self.prev_layout, &mut self.curr_layout);
125 std::mem::swap(&mut self.prev_interaction, &mut self.curr_interaction);
126 self.curr_layout.clear();
127 self.curr_interaction.clear();
128 }
129
130 pub fn clear(&mut self) {
132 self.prev_layout.clear();
133 self.curr_layout.clear();
134 self.prev_interaction.clear();
135 self.curr_interaction.clear();
136 }
137
138 pub fn prev_layout(&self, node_id: NodeId) -> Option<&NodeLayout> {
140 self.prev_layout.get(&node_id)
141 }
142
143 pub fn current_layout(&self, node_id: NodeId) -> Option<&NodeLayout> {
145 self.curr_layout.get(&node_id)
146 }
147
148 pub fn prev_interaction(&self, node_id: NodeId) -> Option<&NodeInteraction> {
150 self.prev_interaction.get(&node_id)
151 }
152
153 pub fn current_interaction(&self, node_id: NodeId) -> Option<&NodeInteraction> {
155 self.curr_interaction.get(&node_id)
156 }
157
158 pub fn prev_state(&self, node_id: NodeId) -> Option<NodeFrameState> {
160 let layout = self.prev_layout(node_id).copied()?;
161 let interaction = self.prev_interaction(node_id).copied().unwrap_or_default();
162 Some(NodeFrameState::new(layout, interaction))
163 }
164
165 pub fn current_state(&self, node_id: NodeId) -> Option<NodeFrameState> {
167 let layout = self.current_layout(node_id).copied()?;
168 let interaction = self.current_interaction(node_id).copied().unwrap_or_default();
169 Some(NodeFrameState::new(layout, interaction))
170 }
171
172 pub fn record_layout(&mut self, node_id: NodeId, layout: NodeLayout) {
174 let prev = self.curr_layout.insert(node_id, layout);
175 debug_assert!(prev.is_none(), "Node {:?} layout was recorded more than once in the same frame", node_id);
176 }
177
178 pub fn record_interaction(&mut self, node_id: NodeId, interaction: NodeInteraction) {
180 let prev = self.curr_interaction.insert(node_id, interaction);
181 debug_assert!(prev.is_none(), "Node {:?} interaction was recorded more than once in the same frame", node_id);
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn layout_and_interaction_generations_are_independent() {
191 let node_id = NodeId::new(7);
192 let layout = NodeLayout::new(Recti::new(1, 2, 3, 4), Recti::new(5, 6, 7, 8), Dimensioni::new(9, 10));
193 let interaction = NodeInteraction::new(ControlState::default(), ResourceState::SUBMIT);
194
195 let mut cache = WidgetTreeCache::default();
196 cache.record_layout(node_id, layout);
197
198 let current_layout = cache.current_layout(node_id).copied().unwrap();
199 assert_eq!(current_layout.rect.x, layout.rect.x);
200 assert_eq!(current_layout.rect.y, layout.rect.y);
201 assert_eq!(current_layout.rect.width, layout.rect.width);
202 assert_eq!(current_layout.rect.height, layout.rect.height);
203 assert!(cache.current_interaction(node_id).is_none());
204
205 cache.record_interaction(node_id, interaction);
206 cache.finish_frame();
207
208 let committed_layout = cache.prev_layout(node_id).copied().unwrap();
209 assert_eq!(committed_layout.content_size.width, layout.content_size.width);
210 assert_eq!(committed_layout.content_size.height, layout.content_size.height);
211 assert!(cache.prev_interaction(node_id).copied().unwrap().result.is_submitted());
212 }
213}