Skip to main content

azul_layout/managers/
gpu_state.rs

1//! Centralized GPU state management.
2//!
3//! This module provides management of GPU property keys
4//! (opacity, transforms, etc.), fade-in/fade-out animations
5//! for scrollbar opacity - as a single source of truth for
6//! the GPU cache.
7
8use alloc::collections::BTreeMap;
9
10use azul_core::{
11    dom::{DomId, NodeId},
12    geom::LogicalSize,
13    gpu::{GpuEventChanges, GpuScrollbarOpacityEvent, GpuTransformKeyEvent, GpuValueCache},
14    resources::{OpacityKey, TransformKey},
15    task::{Duration, Instant, SystemTimeDiff},
16    transform::{ComputedTransform3D, RotationMode},
17};
18
19use crate::{
20    managers::scroll_state::{FrameScrollInfo, ScrollManager},
21    solver3::{layout_tree::LayoutTree, scrollbar::ScrollbarRequirements},
22    text3::cache::ParsedFontTrait,
23};
24
25/// Default delay before scrollbars start fading out (500ms)
26pub const DEFAULT_FADE_DELAY_MS: u64 = 500;
27/// Default duration of scrollbar fade-out animation (200ms)
28pub const DEFAULT_FADE_DURATION_MS: u64 = 200;
29
30/// Manages GPU-accelerated properties across all DOMs.
31///
32/// The `GpuStateManager` maintains caches for transform and opacity keys
33/// that are used by the GPU renderer. It handles:
34///
35/// - Scrollbar thumb position transforms (updated on scroll)
36/// - Opacity fading for scrollbars (fade in on activity, fade out after delay)
37/// - Per-DOM GPU value caches for efficient rendering
38#[derive(Debug, Clone)]
39pub struct GpuStateManager {
40    /// GPU value caches indexed by DOM ID
41    pub caches: BTreeMap<DomId, GpuValueCache>,
42    /// Delay before scrollbars start fading out after last activity
43    pub fade_delay: Duration,
44    /// Duration of the fade-out animation
45    pub fade_duration: Duration,
46}
47
48impl Default for GpuStateManager {
49    fn default() -> Self {
50        Self::new(
51            Duration::System(SystemTimeDiff::from_millis(DEFAULT_FADE_DELAY_MS)),
52            Duration::System(SystemTimeDiff::from_millis(DEFAULT_FADE_DURATION_MS)),
53        )
54    }
55}
56
57/// Internal state for tracking opacity transitions.
58///
59/// Used to implement smooth fade-in/fade-out animations for scrollbar opacity.
60/// The opacity interpolates from `current_value` to `target_value` over time.
61#[derive(Debug, Clone)]
62struct OpacityState {
63    /// Current opacity value (0.0 = transparent, 1.0 = opaque)
64    current_value: f32,
65    /// Target opacity value to transition towards
66    target_value: f32,
67    /// Timestamp of last scroll or hover activity
68    last_activity_time: Instant,
69    /// When the transition animation started (None if not transitioning)
70    transition_start_time: Option<Instant>,
71}
72
73/// Result of a GPU state tick operation.
74///
75/// Contains information about whether the GPU state changed and
76/// what specific changes occurred for the renderer to process.
77#[derive(Debug, Default)]
78pub struct GpuTickResult {
79    /// Whether any GPU state changed requiring a repaint
80    pub needs_repaint: bool,
81    /// Detailed changes to transform and opacity keys
82    pub changes: GpuEventChanges,
83}
84
85impl GpuStateManager {
86    /// Creates a new GPU state manager with specified fade timing.
87    pub fn new(fade_delay: Duration, fade_duration: Duration) -> Self {
88        Self {
89            caches: BTreeMap::new(),
90            fade_delay,
91            fade_duration,
92        }
93    }
94
95    /// Advances GPU state by one tick, interpolating animated values.
96    ///
97    /// This should be called each frame to update opacity transitions
98    /// for smooth scrollbar fading.
99    pub fn tick(&mut self, now: Instant) -> GpuTickResult {
100        // For now, this is a placeholder. A full implementation would
101        // interpolate opacity values for smooth fading.
102        GpuTickResult::default()
103    }
104
105    /// Gets or creates the GPU cache for a specific DOM.
106    pub fn get_or_create_cache(&mut self, dom_id: DomId) -> &mut GpuValueCache {
107        self.caches.entry(dom_id).or_default()
108    }
109
110    /// Updates scrollbar thumb transforms based on current scroll positions.
111    ///
112    /// Calculates the transform needed to position scrollbar thumbs correctly
113    /// based on the scroll offset and content/container sizes. Returns the
114    /// GPU event changes that need to be applied by the renderer.
115    pub fn update_scrollbar_transforms(
116        &mut self,
117        dom_id: DomId,
118        scroll_manager: &ScrollManager,
119        layout_tree: &LayoutTree,
120    ) -> GpuEventChanges {
121        let mut changes = GpuEventChanges::empty();
122        let gpu_cache = self.get_or_create_cache(dom_id);
123
124        for (node_idx, node) in layout_tree.nodes.iter().enumerate() {
125            let Some(scrollbar_info) = &node.scrollbar_info else {
126                continue;
127            };
128            let Some(node_id) = node.dom_node_id else {
129                continue;
130            };
131
132            let scroll_offset = scroll_manager
133                .get_current_offset(dom_id, node_id)
134                .unwrap_or_default();
135            let container_size = node.used_size.unwrap_or_default();
136            let content_size = node
137                .inline_layout_result
138                .as_ref()
139                .map(|l| {
140                    let bounds = l.layout.bounds();
141                    LogicalSize {
142                        width: bounds.width,
143                        height: bounds.height,
144                    }
145                })
146                .unwrap_or(container_size);
147
148            if scrollbar_info.needs_vertical {
149                let transform = compute_vertical_thumb_transform(
150                    scrollbar_info,
151                    &container_size,
152                    &content_size,
153                    scroll_offset.y,
154                );
155
156                update_transform_key(gpu_cache, &mut changes, dom_id, node_id, transform);
157            }
158        }
159
160        changes
161    }
162
163    /// Returns a clone of all GPU value caches.
164    pub fn get_gpu_value_cache(&self) -> BTreeMap<DomId, GpuValueCache> {
165        self.caches.clone()
166    }
167}
168
169/// Computes the transform for a vertical scrollbar thumb.
170fn compute_vertical_thumb_transform(
171    scrollbar_info: &ScrollbarRequirements,
172    container_size: &LogicalSize,
173    content_size: &LogicalSize,
174    scroll_y: f32,
175) -> ComputedTransform3D {
176    let track_height = container_size.height - scrollbar_info.scrollbar_height;
177    let thumb_height = (container_size.height / content_size.height) * track_height;
178    let scrollable_dist = content_size.height - container_size.height;
179    let thumb_dist = track_height - thumb_height;
180
181    let scroll_ratio = if scrollable_dist > 0.0 {
182        scroll_y / scrollable_dist
183    } else {
184        0.0
185    };
186    let thumb_offset_y = scroll_ratio * thumb_dist;
187
188    ComputedTransform3D::new_translation(0.0, thumb_offset_y, 0.0)
189}
190
191/// Updates or creates a transform key in the GPU cache.
192fn update_transform_key(
193    gpu_cache: &mut GpuValueCache,
194    changes: &mut GpuEventChanges,
195    dom_id: DomId,
196    node_id: NodeId,
197    transform: ComputedTransform3D,
198) {
199    if let Some(existing_transform) = gpu_cache.current_transform_values.get(&node_id) {
200        if *existing_transform != transform {
201            let transform_key = gpu_cache.transform_keys[&node_id];
202            changes
203                .transform_key_changes
204                .push(GpuTransformKeyEvent::Changed(
205                    node_id,
206                    transform_key,
207                    *existing_transform,
208                    transform,
209                ));
210            gpu_cache
211                .current_transform_values
212                .insert(node_id, transform);
213        }
214    } else {
215        let transform_key = TransformKey::unique();
216        gpu_cache.transform_keys.insert(node_id, transform_key);
217        gpu_cache
218            .current_transform_values
219            .insert(node_id, transform);
220        changes
221            .transform_key_changes
222            .push(GpuTransformKeyEvent::Added(
223                node_id,
224                transform_key,
225                transform,
226            ));
227    }
228}