paint/
web_content_animation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, RefCell};
6use std::rc::Rc;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::time::Duration;
10
11use base::id::WebViewId;
12use embedder_traits::EventLoopWaker;
13use rustc_hash::FxHashMap;
14use servo_config::prefs;
15use webrender_api::{ColorF, PropertyBindingKey, PropertyValue};
16
17use crate::refresh_driver::TimerRefreshDriver;
18use crate::webview_renderer::WebViewRenderer;
19
20/// The amount of the time the caret blinks before ceasing, in order to preserve power. User
21/// activity (a new display list) will reset this.
22///
23/// TODO: This should be controlled by system settings.
24pub(crate) const CARET_BLINK_TIMEOUT: Duration = Duration::from_secs(30);
25
26/// A struct responsible for managing paint-side animations. Currently this only handles text caret
27/// blinking, but the idea is that in the future this would handle other types of paint-side
28/// animations as well.
29///
30/// Note: This does not control animations requiring layout (all CSS transitions and animations
31/// currently) nor animations due to touch events such as fling.
32pub(crate) struct WebContentAnimator {
33    event_loop_waker: Box<dyn EventLoopWaker>,
34    timer_refresh_driver: Rc<TimerRefreshDriver>,
35    caret_visible: Cell<bool>,
36    timer_scheduled: Cell<bool>,
37    need_update: Arc<AtomicBool>,
38}
39
40impl WebContentAnimator {
41    pub(crate) fn new(
42        event_loop_waker: Box<dyn EventLoopWaker>,
43        timer_refresh_driver: Rc<TimerRefreshDriver>,
44    ) -> Self {
45        Self {
46            event_loop_waker,
47            timer_refresh_driver,
48            caret_visible: Cell::new(true),
49            timer_scheduled: Default::default(),
50            need_update: Default::default(),
51        }
52    }
53
54    pub(crate) fn schedule_timer_if_necessary(&self) {
55        if self.timer_scheduled.get() {
56            return;
57        }
58
59        let Some(caret_blink_time) = prefs::get().editing_caret_blink_time() else {
60            return;
61        };
62
63        let event_loop_waker = self.event_loop_waker.clone();
64        let need_update = self.need_update.clone();
65        self.timer_refresh_driver.queue_timer(
66            caret_blink_time,
67            Box::new(move || {
68                need_update.store(true, Ordering::Relaxed);
69                event_loop_waker.wake();
70            }),
71        );
72        self.timer_scheduled.set(true);
73    }
74
75    pub(crate) fn update(
76        &self,
77        webview_renderers: &FxHashMap<WebViewId, WebViewRenderer>,
78    ) -> Option<Vec<PropertyValue<ColorF>>> {
79        if !self.need_update.load(Ordering::Relaxed) {
80            return None;
81        }
82
83        let mut colors = Vec::new();
84        for renderer in webview_renderers.values() {
85            renderer.for_each_connected_pipeline(&mut |pipeline_details| {
86                if let Some(property_value) =
87                    pipeline_details.animations.update(self.caret_visible.get())
88                {
89                    colors.push(property_value);
90                }
91            });
92        }
93
94        self.timer_scheduled.set(false);
95        self.need_update.store(false, Ordering::Relaxed);
96
97        if colors.is_empty() {
98            // All animations have stopped. When a new blinking caret is activated we want
99            // it to start in the visible state, so we set `caret_visible` to true here.
100            self.caret_visible.set(true);
101            return None;
102        }
103
104        self.caret_visible.set(!self.caret_visible.get());
105        self.schedule_timer_if_necessary();
106        Some(colors)
107    }
108}
109
110/// This structure tracks the animations active for a given pipeline. Currently only caret
111/// blinking is tracked, but in the future this could perhaps track paint-side animations.
112#[derive(Default)]
113pub(crate) struct PipelineAnimations {
114    caret: RefCell<Option<CaretAnimation>>,
115}
116
117impl PipelineAnimations {
118    pub(crate) fn update(&self, caret_visible: bool) -> Option<PropertyValue<ColorF>> {
119        let mut maybe_caret = self.caret.borrow_mut();
120        let caret = maybe_caret.as_mut()?;
121
122        if let Some(update) = caret.update(caret_visible) {
123            return Some(update);
124        }
125        *maybe_caret = None;
126        None
127    }
128
129    pub(crate) fn handle_new_display_list(
130        &self,
131        caret_property_binding: Option<(PropertyBindingKey<ColorF>, ColorF)>,
132        web_content_animator: &WebContentAnimator,
133    ) {
134        let Some(caret_blink_time) = prefs::get().editing_caret_blink_time() else {
135            return;
136        };
137
138        *self.caret.borrow_mut() = match caret_property_binding {
139            Some((caret_property_key, original_caret_color)) => {
140                web_content_animator.schedule_timer_if_necessary();
141                Some(CaretAnimation {
142                    caret_property_key,
143                    original_caret_color,
144                    remaining_blink_count: (CARET_BLINK_TIMEOUT.as_millis() /
145                        caret_blink_time.as_millis())
146                        as usize,
147                })
148            },
149            None => None,
150        }
151    }
152}
153
154/// Tracks the state of an ongoing caret blinking animation.
155struct CaretAnimation {
156    pub caret_property_key: PropertyBindingKey<ColorF>,
157    pub original_caret_color: ColorF,
158    pub remaining_blink_count: usize,
159}
160
161impl CaretAnimation {
162    pub(crate) fn update(&mut self, caret_visible: bool) -> Option<PropertyValue<ColorF>> {
163        if self.remaining_blink_count == 0 {
164            return None;
165        }
166
167        self.remaining_blink_count = self.remaining_blink_count.saturating_sub(1);
168        let value = if caret_visible || self.remaining_blink_count == 0 {
169            self.original_caret_color
170        } else {
171            ColorF::TRANSPARENT
172        };
173
174        Some(PropertyValue {
175            key: self.caret_property_key,
176            value,
177        })
178    }
179}