1use alloc::collections::BTreeMap;
9
10use azul_core::{
11 dom::{DomId, NodeId},
12 dom::ScrollbarOrientation,
13 geom::{LogicalPosition, LogicalRect, LogicalSize},
14 gpu::{GpuEventChanges, GpuTransformKeyEvent, GpuValueCache},
15 resources::TransformKey,
16 task::{Duration, Instant, SystemTimeDiff},
17 transform::ComputedTransform3D,
18};
19
20use crate::{
21 managers::scroll_state::ScrollManager,
22 solver3::{
23 fc::DEFAULT_SCROLLBAR_WIDTH_PX,
24 layout_tree::LayoutTree,
25 scrollbar::compute_scrollbar_geometry_with_button_size,
26 },
27};
28
29pub const DEFAULT_FADE_DELAY_MS: u64 = 500;
31pub const DEFAULT_FADE_DURATION_MS: u64 = 200;
33
34#[derive(Debug, Clone)]
43pub struct GpuStateManager {
44 pub caches: BTreeMap<DomId, GpuValueCache>,
46 pub fade_delay: Duration,
48 pub fade_duration: Duration,
50 pub fade_states: BTreeMap<(DomId, NodeId), ScrollbarFadeState>,
52 pub scrollbar_fade_active: bool,
57 pub pending_changes: GpuEventChanges,
62}
63
64impl Default for GpuStateManager {
65 fn default() -> Self {
66 Self::new(
67 Duration::System(SystemTimeDiff::from_millis(DEFAULT_FADE_DELAY_MS)),
68 Duration::System(SystemTimeDiff::from_millis(DEFAULT_FADE_DURATION_MS)),
69 )
70 }
71}
72
73#[derive(Debug, Clone)]
79pub struct ScrollbarFadeState {
80 pub last_activity_time: Option<Instant>,
82 pub needs_vertical: bool,
84 pub needs_horizontal: bool,
86}
87
88#[derive(Debug, Default)]
93#[must_use]
94pub struct GpuTickResult {
95 pub needs_repaint: bool,
97 pub changes: GpuEventChanges,
99}
100
101impl GpuStateManager {
102 pub fn new(fade_delay: Duration, fade_duration: Duration) -> Self {
104 Self {
105 caches: BTreeMap::new(),
106 fade_delay,
107 fade_duration,
108 fade_states: BTreeMap::new(),
109 scrollbar_fade_active: false,
110 pending_changes: GpuEventChanges::empty(),
111 }
112 }
113
114 pub fn take_pending_changes(&mut self) -> GpuEventChanges {
117 core::mem::take(&mut self.pending_changes)
118 }
119
120 pub fn tick(&mut self, now: Instant) -> GpuTickResult {
126 let mut needs_repaint = false;
127 let fade_delay = self.fade_delay;
128 let fade_duration = self.fade_duration;
129
130 for (&(dom_id, node_id), fade_state) in &self.fade_states {
132 let cache = match self.caches.get_mut(&dom_id) {
133 Some(c) => c,
134 None => continue,
135 };
136
137 let opacity = Self::calculate_fade_opacity(
138 fade_state.last_activity_time.as_ref(),
139 &now,
140 fade_delay,
141 fade_duration,
142 );
143
144 if fade_state.needs_vertical {
146 let key = (dom_id, node_id);
147 if let Some(old_val) = cache.scrollbar_v_opacity_values.get(&key) {
148 if (old_val - opacity).abs() > 0.001 {
149 cache.scrollbar_v_opacity_values.insert(key, opacity);
150 needs_repaint = true;
151 }
152 }
153 }
154
155 if fade_state.needs_horizontal {
157 let key = (dom_id, node_id);
158 if let Some(old_val) = cache.scrollbar_h_opacity_values.get(&key) {
159 if (old_val - opacity).abs() > 0.001 {
160 cache.scrollbar_h_opacity_values.insert(key, opacity);
161 needs_repaint = true;
162 }
163 }
164 }
165 }
166
167 GpuTickResult {
168 needs_repaint,
169 changes: GpuEventChanges::empty(),
170 }
171 }
172
173 fn calculate_fade_opacity(
180 last_activity: Option<&Instant>,
181 now: &Instant,
182 fade_delay: Duration,
183 fade_duration: Duration,
184 ) -> f32 {
185 let Some(last_activity) = last_activity else {
186 return 0.0;
187 };
188
189 let time_since_activity = now.duration_since(last_activity);
190
191 if time_since_activity.div(&fade_delay) < 1.0 {
193 return 1.0;
194 }
195
196 let fade_progress = (time_since_activity.div(&fade_duration) - fade_delay.div(&fade_duration)).min(1.0);
199
200 (1.0 - fade_progress).max(0.0)
202 }
203
204 pub fn record_scroll_activity(
209 &mut self,
210 dom_id: DomId,
211 node_id: NodeId,
212 now: Instant,
213 needs_vertical: bool,
214 needs_horizontal: bool,
215 ) {
216 let state = self.fade_states
217 .entry((dom_id, node_id))
218 .or_insert(ScrollbarFadeState {
219 last_activity_time: None,
220 needs_vertical: false,
221 needs_horizontal: false,
222 });
223 state.last_activity_time = Some(now);
224 state.needs_vertical = needs_vertical;
225 state.needs_horizontal = needs_horizontal;
226 }
227
228 pub fn get_cache(&self, dom_id: DomId) -> Option<&GpuValueCache> {
230 self.caches.get(&dom_id)
231 }
232
233 pub fn get_or_create_cache(&mut self, dom_id: DomId) -> &mut GpuValueCache {
234 self.caches.entry(dom_id).or_default()
235 }
236
237 pub fn update_scrollbar_transforms(
243 &mut self,
244 dom_id: DomId,
245 scroll_manager: &ScrollManager,
246 layout_tree: &LayoutTree,
247 ) -> GpuEventChanges {
248 let mut changes = GpuEventChanges::empty();
249 let gpu_cache = self.get_or_create_cache(dom_id);
250
251 for (node_idx, node) in layout_tree.nodes.iter().enumerate() {
252 let warm = layout_tree.warm(node_idx);
253 let Some(scrollbar_info) = warm.and_then(|w| w.scrollbar_info.as_ref()) else {
254 continue;
255 };
256 let Some(node_id) = node.dom_node_id else {
257 continue;
258 };
259
260 let scroll_offset = scroll_manager
261 .get_current_offset(dom_id, node_id)
262 .unwrap_or_default();
263
264 let border_box_size = node.used_size.unwrap_or_default();
266 let nbp = node.box_props.unpack();
267 let border = &nbp.border;
268 let inner_size = LogicalSize {
269 width: (border_box_size.width - border.left - border.right).max(0.0),
270 height: (border_box_size.height - border.top - border.bottom).max(0.0),
271 };
272 let inner_rect = LogicalRect {
274 origin: LogicalPosition::new(0.0, 0.0),
275 size: inner_size,
276 };
277
278 let content_size = layout_tree.get_content_size(node_idx);
280
281 if scrollbar_info.needs_vertical {
282 let is_overlay = scrollbar_info.scrollbar_height == 0.0;
287 let scrollbar_width_px = if scrollbar_info.visual_width_px > 0.0 {
288 scrollbar_info.visual_width_px
289 } else if !is_overlay {
290 scrollbar_info.scrollbar_height
291 } else {
292 DEFAULT_SCROLLBAR_WIDTH_PX
293 };
294 let button_size = if is_overlay { 0.0 } else { scrollbar_width_px };
296
297 let v_geom = compute_scrollbar_geometry_with_button_size(
298 ScrollbarOrientation::Vertical,
299 inner_rect,
300 content_size,
301 scroll_offset.y,
302 scrollbar_width_px,
303 scrollbar_info.needs_horizontal,
304 button_size,
305 );
306
307 let transform =
308 ComputedTransform3D::new_translation(0.0, v_geom.thumb_offset, 0.0);
309 update_transform_key(gpu_cache, &mut changes, dom_id, node_id, transform);
310 }
311
312 if scrollbar_info.needs_horizontal {
313 let is_overlay = scrollbar_info.scrollbar_width == 0.0;
314 let scrollbar_width_px = if scrollbar_info.visual_width_px > 0.0 {
315 scrollbar_info.visual_width_px
316 } else if !is_overlay {
317 scrollbar_info.scrollbar_width
318 } else {
319 DEFAULT_SCROLLBAR_WIDTH_PX
320 };
321 let button_size = if is_overlay { 0.0 } else { scrollbar_width_px };
322
323 let h_geom = compute_scrollbar_geometry_with_button_size(
324 ScrollbarOrientation::Horizontal,
325 inner_rect,
326 content_size,
327 scroll_offset.x,
328 scrollbar_width_px,
329 scrollbar_info.needs_vertical,
330 button_size,
331 );
332
333 let transform =
334 ComputedTransform3D::new_translation(h_geom.thumb_offset, 0.0, 0.0);
335 update_h_transform_key(gpu_cache, &mut changes, dom_id, node_id, transform);
336 }
337 }
338
339 changes
340 }
341
342 pub fn get_gpu_value_cache(&self) -> BTreeMap<DomId, GpuValueCache> {
344 self.caches.clone()
345 }
346}
347
348fn update_transform_key(
350 gpu_cache: &mut GpuValueCache,
351 changes: &mut GpuEventChanges,
352 dom_id: DomId,
353 node_id: NodeId,
354 transform: ComputedTransform3D,
355) {
356 if let Some(existing_transform) = gpu_cache.current_transform_values.get(&node_id) {
357 if *existing_transform != transform {
358 let transform_key = gpu_cache.transform_keys[&node_id];
359 changes
360 .transform_key_changes
361 .push(GpuTransformKeyEvent::Changed(
362 node_id,
363 transform_key,
364 *existing_transform,
365 transform,
366 ));
367 gpu_cache
368 .current_transform_values
369 .insert(node_id, transform);
370 }
371 } else {
372 let transform_key = TransformKey::unique();
373 gpu_cache.transform_keys.insert(node_id, transform_key);
374 gpu_cache
375 .current_transform_values
376 .insert(node_id, transform);
377 changes
378 .transform_key_changes
379 .push(GpuTransformKeyEvent::Added(
380 node_id,
381 transform_key,
382 transform,
383 ));
384 }
385}
386
387fn update_h_transform_key(
389 gpu_cache: &mut GpuValueCache,
390 changes: &mut GpuEventChanges,
391 dom_id: DomId,
392 node_id: NodeId,
393 transform: ComputedTransform3D,
394) {
395 if let Some(existing_transform) = gpu_cache.h_current_transform_values.get(&node_id) {
396 if *existing_transform != transform {
397 let transform_key = gpu_cache.h_transform_keys[&node_id];
398 changes
399 .transform_key_changes
400 .push(GpuTransformKeyEvent::Changed(
401 node_id,
402 transform_key,
403 *existing_transform,
404 transform,
405 ));
406 gpu_cache
407 .h_current_transform_values
408 .insert(node_id, transform);
409 }
410 } else {
411 let transform_key = TransformKey::unique();
412 gpu_cache.h_transform_keys.insert(node_id, transform_key);
413 gpu_cache
414 .h_current_transform_values
415 .insert(node_id, transform);
416 changes
417 .transform_key_changes
418 .push(GpuTransformKeyEvent::Added(
419 node_id,
420 transform_key,
421 transform,
422 ));
423 }
424}