Skip to main content

microui_redux/widget_tree/
cache.rs

1//
2// Copyright 2022-Present (c) Raja Lehtihet & Wael El Oraiby
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9//
10// 2. Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13//
14// 3. Neither the name of the copyright holder nor the names of its contributors
15// may be used to endorse or promote products derived from this software without
16// specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28// POSSIBILITY OF SUCH DAMAGE.
29//
30// -----------------------------------------------------------------------------
31//! Previous/current frame caches for retained node layout and interaction data.
32
33use std::collections::HashMap;
34
35use rs_math3d::{Dimensioni, Recti};
36
37use crate::input::{ControlState, ResourceState};
38
39use super::NodeId;
40
41/// Geometry resolved for a retained node in one frame.
42///
43/// This cache is intentionally layout-only. Parent nodes such as headers,
44/// tree nodes, and embedded containers need the previous frame's rectangles to
45/// react to structural input before the current frame's layout runs.
46#[derive(Copy, Clone, Debug, Default)]
47pub struct NodeLayout {
48    /// Outer rectangle assigned to the node.
49    pub rect: Recti,
50    /// Inner body rectangle, when the node exposes one.
51    pub body: Recti,
52    /// Content size produced while traversing the node's children.
53    pub content_size: Dimensioni,
54}
55
56impl NodeLayout {
57    /// Creates a layout snapshot for one node.
58    pub const fn new(rect: Recti, body: Recti, content_size: Dimensioni) -> Self {
59        Self { rect, body, content_size }
60    }
61}
62
63/// Interaction data sampled for a retained node in one frame.
64#[derive(Copy, Clone, Debug)]
65pub struct NodeInteraction {
66    /// Control state observed while handling the node this frame.
67    pub control: ControlState,
68    /// Resource state returned by the node this frame.
69    pub result: ResourceState,
70}
71
72impl NodeInteraction {
73    /// Creates an interaction snapshot for one node.
74    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/// Combined previous/current frame view for callers that need both layout and
86/// interaction at the same time.
87#[derive(Copy, Clone, Debug, Default)]
88pub struct NodeFrameState {
89    /// Layout resolved for the node in one frame.
90    pub layout: NodeLayout,
91    /// Interaction sampled for the node in one frame.
92    pub interaction: NodeInteraction,
93}
94
95impl NodeFrameState {
96    /// Creates a combined frame-state snapshot for one node.
97    pub const fn new(layout: NodeLayout, interaction: NodeInteraction) -> Self {
98        Self { layout, interaction }
99    }
100}
101
102/// Previous/current frame cache for widget tree nodes.
103///
104/// Layout and interaction are stored in separate generations so pass 1 can read
105/// only previous-frame geometry while pass 3 writes the next frame's geometry
106/// and interaction outputs independently.
107#[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    /// Clears the in-progress frame cache while preserving the committed frame.
117    pub fn begin_frame(&mut self) {
118        self.curr_layout.clear();
119        self.curr_interaction.clear();
120    }
121
122    /// Publishes the current frame cache as the previous frame for the next run.
123    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    /// Drops both previous and current cached node data.
131    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    /// Returns the previous frame layout for `node_id`.
139    pub fn prev_layout(&self, node_id: NodeId) -> Option<&NodeLayout> {
140        self.prev_layout.get(&node_id)
141    }
142
143    /// Returns the current frame layout for `node_id`.
144    pub fn current_layout(&self, node_id: NodeId) -> Option<&NodeLayout> {
145        self.curr_layout.get(&node_id)
146    }
147
148    /// Returns the previous frame interaction for `node_id`.
149    pub fn prev_interaction(&self, node_id: NodeId) -> Option<&NodeInteraction> {
150        self.prev_interaction.get(&node_id)
151    }
152
153    /// Returns the current frame interaction for `node_id`.
154    pub fn current_interaction(&self, node_id: NodeId) -> Option<&NodeInteraction> {
155        self.curr_interaction.get(&node_id)
156    }
157
158    /// Returns the previous combined frame state for `node_id`.
159    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    /// Returns the current combined frame state for `node_id`.
166    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    /// Records the current frame layout for `node_id`.
173    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    /// Records the current frame interaction for `node_id`.
179    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}