Skip to main content

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 embedder_traits::EventLoopWaker;
12use rustc_hash::FxHashMap;
13use servo_base::id::WebViewId;
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    /// Trigger a potential update to this [`WebContentAnimator`], returning `true` if
76    /// anything was updated and `false` otherwise.
77    pub(crate) fn update(&self, webview_renderers: &FxHashMap<WebViewId, WebViewRenderer>) -> bool {
78        if !self.need_update.load(Ordering::Relaxed) {
79            return false;
80        }
81
82        let mut anything_updated = false;
83        for renderer in webview_renderers.values() {
84            renderer.for_each_connected_pipeline(&mut |pipeline_details| {
85                anything_updated = pipeline_details.animations.update() || anything_updated;
86            });
87        }
88
89        self.timer_scheduled.set(false);
90        self.need_update.store(false, Ordering::Relaxed);
91
92        if !anything_updated {
93            // All animations have stopped. When a new blinking caret is activated we want
94            // it to start in the visible state, so we set `caret_visible` to true here.
95            self.caret_visible.set(true);
96            return false;
97        }
98
99        self.caret_visible.set(!self.caret_visible.get());
100        self.schedule_timer_if_necessary();
101        true
102    }
103
104    pub(crate) fn values(
105        &self,
106        webview_renderers: &FxHashMap<WebViewId, WebViewRenderer>,
107    ) -> Vec<PropertyValue<ColorF>> {
108        let mut colors = Vec::new();
109        for renderer in webview_renderers.values() {
110            renderer.for_each_connected_pipeline(&mut |pipeline_details| {
111                if let Some(property_value) =
112                    pipeline_details.animations.values(self.caret_visible.get())
113                {
114                    colors.push(property_value);
115                }
116            });
117        }
118        colors
119    }
120}
121
122/// This structure tracks the animations active for a given pipeline. Currently only caret
123/// blinking is tracked, but in the future this could perhaps track paint-side animations.
124#[derive(Default)]
125pub(crate) struct PipelineAnimations {
126    caret: RefCell<Option<CaretAnimation>>,
127}
128
129impl PipelineAnimations {
130    /// Update this [`PipelineAnimations`], returning `true` if anything updated and
131    /// `false` otherwise.
132    pub(crate) fn update(&self) -> bool {
133        let mut maybe_caret = self.caret.borrow_mut();
134        let Some(caret) = maybe_caret.as_mut() else {
135            return false;
136        };
137        caret.update();
138        if caret.done() {
139            *maybe_caret = None;
140        }
141        true
142    }
143
144    pub(crate) fn values(&self, caret_visible: bool) -> Option<PropertyValue<ColorF>> {
145        let maybe_caret = self.caret.borrow();
146        Some(maybe_caret.as_ref()?.value(caret_visible))
147    }
148
149    pub(crate) fn handle_new_display_list(
150        &self,
151        caret_property_binding: Option<(PropertyBindingKey<ColorF>, ColorF)>,
152        web_content_animator: &WebContentAnimator,
153    ) {
154        let Some(caret_blink_time) = prefs::get().editing_caret_blink_time() else {
155            return;
156        };
157
158        *self.caret.borrow_mut() = match caret_property_binding {
159            Some((caret_property_key, original_caret_color)) => {
160                web_content_animator.schedule_timer_if_necessary();
161                Some(CaretAnimation {
162                    caret_property_key,
163                    original_caret_color,
164                    remaining_blink_count: (CARET_BLINK_TIMEOUT.as_millis() /
165                        caret_blink_time.as_millis())
166                        as usize,
167                })
168            },
169            None => None,
170        }
171    }
172}
173
174/// Tracks the state of an ongoing caret blinking animation.
175struct CaretAnimation {
176    pub caret_property_key: PropertyBindingKey<ColorF>,
177    pub original_caret_color: ColorF,
178    pub remaining_blink_count: usize,
179}
180
181impl CaretAnimation {
182    pub(crate) fn done(&self) -> bool {
183        self.remaining_blink_count == 0
184    }
185
186    pub(crate) fn update(&mut self) {
187        self.remaining_blink_count = self.remaining_blink_count.saturating_sub(1);
188    }
189
190    pub(crate) fn value(&self, caret_visible: bool) -> PropertyValue<ColorF> {
191        debug_assert!(self.remaining_blink_count != 0);
192        let value = if caret_visible {
193            self.original_caret_color
194        } else {
195            ColorF::TRANSPARENT
196        };
197        PropertyValue {
198            key: self.caret_property_key,
199            value,
200        }
201    }
202}