use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use embedder_traits::EventLoopWaker;
use rustc_hash::FxHashMap;
use servo_base::id::WebViewId;
use servo_config::prefs;
use webrender_api::{ColorF, PropertyBindingKey, PropertyValue};
use crate::refresh_driver::TimerRefreshDriver;
use crate::webview_renderer::WebViewRenderer;
pub(crate) const CARET_BLINK_TIMEOUT: Duration = Duration::from_secs(30);
pub(crate) struct WebContentAnimator {
event_loop_waker: Box<dyn EventLoopWaker>,
timer_refresh_driver: Rc<TimerRefreshDriver>,
caret_visible: Cell<bool>,
timer_scheduled: Cell<bool>,
need_update: Arc<AtomicBool>,
}
impl WebContentAnimator {
pub(crate) fn new(
event_loop_waker: Box<dyn EventLoopWaker>,
timer_refresh_driver: Rc<TimerRefreshDriver>,
) -> Self {
Self {
event_loop_waker,
timer_refresh_driver,
caret_visible: Cell::new(true),
timer_scheduled: Default::default(),
need_update: Default::default(),
}
}
pub(crate) fn schedule_timer_if_necessary(&self) {
if self.timer_scheduled.get() {
return;
}
let Some(caret_blink_time) = prefs::get().editing_caret_blink_time() else {
return;
};
let event_loop_waker = self.event_loop_waker.clone();
let need_update = self.need_update.clone();
self.timer_refresh_driver.queue_timer(
caret_blink_time,
Box::new(move || {
need_update.store(true, Ordering::Relaxed);
event_loop_waker.wake();
}),
);
self.timer_scheduled.set(true);
}
pub(crate) fn update(
&self,
webview_renderers: &FxHashMap<WebViewId, WebViewRenderer>,
) -> Option<Vec<PropertyValue<ColorF>>> {
if !self.need_update.load(Ordering::Relaxed) {
return None;
}
let mut colors = Vec::new();
for renderer in webview_renderers.values() {
renderer.for_each_connected_pipeline(&mut |pipeline_details| {
if let Some(property_value) =
pipeline_details.animations.update(self.caret_visible.get())
{
colors.push(property_value);
}
});
}
self.timer_scheduled.set(false);
self.need_update.store(false, Ordering::Relaxed);
if colors.is_empty() {
self.caret_visible.set(true);
return None;
}
self.caret_visible.set(!self.caret_visible.get());
self.schedule_timer_if_necessary();
Some(colors)
}
}
#[derive(Default)]
pub(crate) struct PipelineAnimations {
caret: RefCell<Option<CaretAnimation>>,
}
impl PipelineAnimations {
pub(crate) fn update(&self, caret_visible: bool) -> Option<PropertyValue<ColorF>> {
let mut maybe_caret = self.caret.borrow_mut();
let caret = maybe_caret.as_mut()?;
if let Some(update) = caret.update(caret_visible) {
return Some(update);
}
*maybe_caret = None;
None
}
pub(crate) fn handle_new_display_list(
&self,
caret_property_binding: Option<(PropertyBindingKey<ColorF>, ColorF)>,
web_content_animator: &WebContentAnimator,
) {
let Some(caret_blink_time) = prefs::get().editing_caret_blink_time() else {
return;
};
*self.caret.borrow_mut() = match caret_property_binding {
Some((caret_property_key, original_caret_color)) => {
web_content_animator.schedule_timer_if_necessary();
Some(CaretAnimation {
caret_property_key,
original_caret_color,
remaining_blink_count: (CARET_BLINK_TIMEOUT.as_millis() /
caret_blink_time.as_millis())
as usize,
})
},
None => None,
}
}
}
struct CaretAnimation {
pub caret_property_key: PropertyBindingKey<ColorF>,
pub original_caret_color: ColorF,
pub remaining_blink_count: usize,
}
impl CaretAnimation {
pub(crate) fn update(&mut self, caret_visible: bool) -> Option<PropertyValue<ColorF>> {
if self.remaining_blink_count == 0 {
return None;
}
self.remaining_blink_count = self.remaining_blink_count.saturating_sub(1);
let value = if caret_visible || self.remaining_blink_count == 0 {
self.original_caret_color
} else {
ColorF::TRANSPARENT
};
Some(PropertyValue {
key: self.caret_property_key,
value,
})
}
}