1use alloc::collections::BTreeMap;
9
10#[cfg(feature = "std")]
11use std::collections::HashMap;
12#[cfg(not(feature = "std"))]
13use alloc::collections::BTreeMap as HashMap;
14
15use azul_core::{
16 dom::{DomId, NodeId},
17 dom::ScrollbarOrientation,
18 geom::{LogicalPosition, LogicalRect, LogicalSize},
19 gpu::{GpuEventChanges, GpuTransformKeyEvent, GpuValueCache},
20 resources::TransformKey,
21 task::{Duration, SystemTimeDiff},
22 transform::ComputedTransform3D,
23};
24
25use crate::{
26 managers::scroll_state::ScrollManager,
27 solver3::{
28 fc::DEFAULT_SCROLLBAR_WIDTH_PX,
29 layout_tree::LayoutTree,
30 scrollbar::compute_scrollbar_geometry_with_button_size,
31 },
32};
33
34pub const DEFAULT_FADE_DELAY_MS: u64 = 500;
36pub const DEFAULT_FADE_DURATION_MS: u64 = 200;
38
39#[derive(Debug, Clone)]
48pub struct GpuStateManager {
49 pub caches: BTreeMap<DomId, GpuValueCache>,
51 pub fade_delay: Duration,
53 pub fade_duration: Duration,
55 pub scrollbar_fade_active: bool,
60 pub pending_changes: GpuEventChanges,
65}
66
67impl Default for GpuStateManager {
68 fn default() -> Self {
69 Self::new(
70 Duration::System(SystemTimeDiff::from_millis(DEFAULT_FADE_DELAY_MS)),
71 Duration::System(SystemTimeDiff::from_millis(DEFAULT_FADE_DURATION_MS)),
72 )
73 }
74}
75
76impl GpuStateManager {
77 #[must_use] pub fn new(fade_delay: Duration, fade_duration: Duration) -> Self {
79 Self {
80 caches: BTreeMap::new(),
81 fade_delay,
82 fade_duration,
83 scrollbar_fade_active: false,
84 pending_changes: GpuEventChanges::empty(),
85 }
86 }
87
88 pub fn take_pending_changes(&mut self) -> GpuEventChanges {
91 core::mem::take(&mut self.pending_changes)
92 }
93
94 #[must_use] pub fn get_cache(&self, dom_id: DomId) -> Option<&GpuValueCache> {
104 self.caches.get(&dom_id)
105 }
106
107 pub fn get_or_create_cache(&mut self, dom_id: DomId) -> &mut GpuValueCache {
108 self.caches.entry(dom_id).or_default()
109 }
110
111 pub fn update_scrollbar_transforms(
117 &mut self,
118 dom_id: DomId,
119 scroll_manager: &ScrollManager,
120 layout_tree: &LayoutTree,
121 ) -> GpuEventChanges {
122 let mut changes = GpuEventChanges::empty();
123 let gpu_cache = self.get_or_create_cache(dom_id);
124
125 for (node_idx, node) in layout_tree.nodes.iter().enumerate() {
126 let warm = layout_tree.warm(node_idx);
127 let Some(scrollbar_info) = warm.and_then(|w| w.scrollbar_info.as_ref()) else {
128 continue;
129 };
130 let Some(node_id) = node.dom_node_id else {
131 continue;
132 };
133
134 let scroll_offset = scroll_manager
135 .get_current_offset(dom_id, node_id)
136 .unwrap_or_default();
137
138 let border_box_size = node.used_size.unwrap_or_default();
140 let nbp = node.box_props.unpack();
141 let border = &nbp.border;
142 let inner_size = LogicalSize {
143 width: (border_box_size.width - border.left - border.right).max(0.0),
144 height: (border_box_size.height - border.top - border.bottom).max(0.0),
145 };
146 let inner_rect = LogicalRect {
148 origin: LogicalPosition::new(0.0, 0.0),
149 size: inner_size,
150 };
151
152 let content_size = layout_tree.get_content_size(node_idx);
154
155 if scrollbar_info.needs_vertical {
156 let is_overlay = scrollbar_info.scrollbar_height == 0.0;
161 let scrollbar_width_px = if scrollbar_info.visual_width_px > 0.0 {
162 scrollbar_info.visual_width_px
163 } else if !is_overlay {
164 scrollbar_info.scrollbar_height
165 } else {
166 DEFAULT_SCROLLBAR_WIDTH_PX
167 };
168 let button_size = if is_overlay { 0.0 } else { scrollbar_width_px };
170
171 let v_geom = compute_scrollbar_geometry_with_button_size(
172 ScrollbarOrientation::Vertical,
173 inner_rect,
174 content_size,
175 scroll_offset.y,
176 scrollbar_width_px,
177 scrollbar_info.needs_horizontal,
178 button_size,
179 );
180
181 let transform =
182 ComputedTransform3D::new_translation(0.0, v_geom.thumb_offset, 0.0);
183 update_scrollbar_transform_key(gpu_cache, &mut changes, node_id, transform, ScrollbarOrientation::Vertical);
184 }
185
186 if scrollbar_info.needs_horizontal {
187 let is_overlay = scrollbar_info.scrollbar_width == 0.0;
188 let scrollbar_width_px = if scrollbar_info.visual_width_px > 0.0 {
189 scrollbar_info.visual_width_px
190 } else if !is_overlay {
191 scrollbar_info.scrollbar_width
192 } else {
193 DEFAULT_SCROLLBAR_WIDTH_PX
194 };
195 let button_size = if is_overlay { 0.0 } else { scrollbar_width_px };
196
197 let h_geom = compute_scrollbar_geometry_with_button_size(
198 ScrollbarOrientation::Horizontal,
199 inner_rect,
200 content_size,
201 scroll_offset.x,
202 scrollbar_width_px,
203 scrollbar_info.needs_vertical,
204 button_size,
205 );
206
207 let transform =
208 ComputedTransform3D::new_translation(h_geom.thumb_offset, 0.0, 0.0);
209 update_scrollbar_transform_key(gpu_cache, &mut changes, node_id, transform, ScrollbarOrientation::Horizontal);
210 }
211 }
212
213 changes
214 }
215}
216
217fn update_scrollbar_transform_key(
219 gpu_cache: &mut GpuValueCache,
220 changes: &mut GpuEventChanges,
221 node_id: NodeId,
222 transform: ComputedTransform3D,
223 orientation: ScrollbarOrientation,
224) {
225 let (keys, values) = match orientation {
226 ScrollbarOrientation::Vertical => (
227 &mut gpu_cache.transform_keys,
228 &mut gpu_cache.current_transform_values,
229 ),
230 ScrollbarOrientation::Horizontal => (
231 &mut gpu_cache.h_transform_keys,
232 &mut gpu_cache.h_current_transform_values,
233 ),
234 };
235
236 if let Some(existing_transform) = values.get(&node_id) {
237 if *existing_transform != transform {
238 let Some(&transform_key) = keys.get(&node_id) else {
239 return;
240 };
241 changes
242 .transform_key_changes
243 .push(GpuTransformKeyEvent::Changed(
244 node_id,
245 transform_key,
246 *existing_transform,
247 transform,
248 ));
249 values.insert(node_id, transform);
250 }
251 } else {
252 let transform_key = TransformKey::unique();
253 keys.insert(node_id, transform_key);
254 values.insert(node_id, transform);
255 changes
256 .transform_key_changes
257 .push(GpuTransformKeyEvent::Added(
258 node_id,
259 transform_key,
260 transform,
261 ));
262 }
263}
264
265impl crate::managers::NodeIdRemap for GpuStateManager {
266 fn remap_node_ids(&mut self, dom: DomId, map: &crate::managers::NodeIdMap) {
275 let Some(cache) = self.caches.get_mut(&dom) else {
276 return;
277 };
278
279 remap_hashmap(&mut cache.transform_keys, map);
280 remap_hashmap(&mut cache.current_transform_values, map);
281 remap_hashmap(&mut cache.h_transform_keys, map);
282 remap_hashmap(&mut cache.h_current_transform_values, map);
283 remap_hashmap(&mut cache.css_transform_keys, map);
284 remap_hashmap(&mut cache.css_current_transform_values, map);
285 remap_hashmap(&mut cache.opacity_keys, map);
286 remap_hashmap(&mut cache.current_opacity_values, map);
287 remap_dom_hashmap(&mut cache.scrollbar_v_opacity_keys, dom, map);
288 remap_dom_hashmap(&mut cache.scrollbar_h_opacity_keys, dom, map);
289 remap_dom_hashmap(&mut cache.scrollbar_v_opacity_values, dom, map);
290 remap_dom_hashmap(&mut cache.scrollbar_h_opacity_values, dom, map);
291 }
292}
293
294fn remap_hashmap<V>(map: &mut HashMap<NodeId, V>, node_map: &crate::managers::NodeIdMap) {
296 let old = core::mem::take(map);
297 for (old_id, v) in old {
298 if let Some(new_id) = node_map.resolve(old_id) {
299 map.insert(new_id, v);
300 }
301 }
302}
303
304fn remap_dom_hashmap<V>(
306 map: &mut HashMap<(DomId, NodeId), V>,
307 dom: DomId,
308 node_map: &crate::managers::NodeIdMap,
309) {
310 let old = core::mem::take(map);
311 for ((d, old_id), v) in old {
312 if d != dom {
313 map.insert((d, old_id), v);
314 } else if let Some(new_id) = node_map.resolve(old_id) {
315 map.insert((d, new_id), v);
316 }
317 }
318}