mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
paint: Add support for a blinking text caret (#43128)
This change adds support for a blinking text caret using WebRender `DynamicProperties` updates. This ensures that text caret updates are as lightweight as possible. As part of this change, the initial framework for paint-side animations is added. Though this is driven by a timer for now (in order to not have to run full speed animations for text carets), the idea is that more animations can be driven by `Paint` in the future (in order to avoid doing layouts during animations). In addition, a preference is added which controls the caret blink speed. This is used to disable caret blinking during WPT testing. Testing: There are no tests for this change. We do not currently have a good way to test caret blinking as the caret is render only and fully testing blinking would require well timed screenshot creation. Fixes: #33237. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
use std::env::consts::ARCH;
|
||||
use std::sync::{RwLock, RwLockReadGuard};
|
||||
use std::time::Duration;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servo_config_macro::ServoPreferences;
|
||||
@@ -79,6 +80,9 @@ pub struct Preferences {
|
||||
pub fonts_monospace: String,
|
||||
pub fonts_default_size: i64,
|
||||
pub fonts_default_monospace_size: i64,
|
||||
/// The amount of time that a half cycle of a text caret blink takes in milliseconds.
|
||||
/// If this value is less than or equal to zero, then caret blink is disabled.
|
||||
pub editing_caret_blink_time: i64,
|
||||
pub css_animations_testing_enabled: bool,
|
||||
/// Start the devtools server at startup
|
||||
pub devtools_server_enabled: bool,
|
||||
@@ -323,6 +327,7 @@ impl Preferences {
|
||||
const fn const_default() -> Self {
|
||||
Self {
|
||||
css_animations_testing_enabled: false,
|
||||
editing_caret_blink_time: 600,
|
||||
devtools_server_enabled: false,
|
||||
devtools_server_listen_address: String::new(),
|
||||
dom_abort_controller_enabled: true,
|
||||
@@ -481,6 +486,16 @@ impl Preferences {
|
||||
log_filter: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The amount of time that a half cycle of a text caret blink takes. If blinking is disabled
|
||||
/// this returns `None`.
|
||||
pub fn editing_caret_blink_time(&self) -> Option<Duration> {
|
||||
if self.editing_caret_blink_time > 0 {
|
||||
Some(Duration::from_millis(self.editing_caret_blink_time as u64))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Preferences {
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::cell::{OnceCell, RefCell};
|
||||
use std::sync::Arc;
|
||||
|
||||
use app_units::{AU_PER_PX, Au};
|
||||
use base::id::ScrollTreeNodeId;
|
||||
use base::id::{PipelineId, ScrollTreeNodeId};
|
||||
use clip::{Clip, ClipId};
|
||||
use euclid::{Box2D, Point2D, Rect, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
|
||||
use fonts::GlyphStore;
|
||||
@@ -16,7 +16,7 @@ use net_traits::image_cache::Image as CachedImage;
|
||||
use paint_api::display_list::{PaintDisplayListInfo, SpatialTreeNodeInfo};
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use servo_config::opts::DiagnosticsLogging;
|
||||
use servo_config::pref;
|
||||
use servo_config::{pref, prefs};
|
||||
use servo_geometry::MaxRect;
|
||||
use servo_url::ServoUrl;
|
||||
use style::Zero;
|
||||
@@ -45,7 +45,7 @@ use webrender_api::{
|
||||
self as wr, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, BuiltDisplayList,
|
||||
ClipChainId, ClipMode, ColorF, CommonItemProperties, ComplexClipRegion, GlyphInstance,
|
||||
NinePatchBorder, NinePatchBorderSource, NormalBorder, PrimitiveFlags, PropertyBinding,
|
||||
SpatialId, SpatialTreeItemKey, units,
|
||||
PropertyBindingKey, SpatialId, SpatialTreeItemKey, units,
|
||||
};
|
||||
use wr::units::LayoutVector2D;
|
||||
|
||||
@@ -211,6 +211,9 @@ impl DisplayListBuilder<'_> {
|
||||
reflow_statistics,
|
||||
};
|
||||
|
||||
// Clear any caret color from previous display list constructions.
|
||||
builder.paint_info.caret_property_binding = None;
|
||||
|
||||
builder.add_all_spatial_nodes();
|
||||
|
||||
for clip in stacking_context_tree.clip_store.0.iter() {
|
||||
@@ -1101,10 +1104,24 @@ impl Fragment {
|
||||
ColorOrAuto::Auto => color,
|
||||
};
|
||||
let insertion_point_common = builder.common_properties(insertion_point_rect, &parent_style);
|
||||
builder.wr().push_rect(
|
||||
|
||||
let caret_color = rgba(caret_color);
|
||||
let property_binding = if prefs::get().editing_caret_blink_time().is_some() {
|
||||
// It's okay to always use the same property binding key for this pipeline, as
|
||||
// there is currently only a single thing that animates in this way (the caret).
|
||||
// This code should be updated if we ever add more paint-side animations.
|
||||
let pipeline_id: PipelineId = builder.paint_info.pipeline_id.into();
|
||||
let property_binding_key = PropertyBindingKey::new(pipeline_id.into());
|
||||
builder.paint_info.caret_property_binding = Some((property_binding_key, caret_color));
|
||||
PropertyBinding::Binding(property_binding_key, caret_color)
|
||||
} else {
|
||||
PropertyBinding::Value(caret_color)
|
||||
};
|
||||
|
||||
builder.wr().push_rect_with_animation(
|
||||
&insertion_point_common,
|
||||
insertion_point_rect,
|
||||
rgba(caret_color),
|
||||
property_binding,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1182,6 +1182,7 @@ macro_rules! malloc_size_of_is_webrender_malloc_size_of(
|
||||
);
|
||||
);
|
||||
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender::FastTransform<webrender_api::units::LayoutPixel, webrender_api::units::LayoutPixel>);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BorderRadius);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BorderStyle);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BoxShadowClipMode);
|
||||
@@ -1189,9 +1190,10 @@ malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ColorF);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::Epoch);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ExtendMode);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ExternalScrollId);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontKey);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontInstanceFlags);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontInstanceKey);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontKey);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontVariation);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GlyphInstance);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GradientStop);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ImageKey);
|
||||
@@ -1200,13 +1202,14 @@ malloc_size_of_is_webrender_malloc_size_of!(webrender_api::LineStyle);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::MixBlendMode);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::NormalBorder);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::PipelineId);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(
|
||||
webrender_api::PropertyBindingKey<webrender_api::ColorF>
|
||||
);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ReferenceFrameKind);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::RepeatMode);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontVariation);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::SpatialId);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::StickyOffsetBounds);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::TransformStyle);
|
||||
malloc_size_of_is_webrender_malloc_size_of!(webrender::FastTransform<webrender_api::units::LayoutPixel,webrender_api::units::LayoutPixel>);
|
||||
|
||||
macro_rules! malloc_size_of_is_stylo_malloc_size_of(
|
||||
($($ty:ty),+) => (
|
||||
|
||||
@@ -30,6 +30,7 @@ mod refresh_driver;
|
||||
mod render_notifier;
|
||||
mod screenshot;
|
||||
mod touch;
|
||||
mod web_content_animation;
|
||||
mod webrender_external_images;
|
||||
mod webview_renderer;
|
||||
|
||||
|
||||
@@ -153,6 +153,8 @@ bitflags! {
|
||||
const Resize = 1 << 3;
|
||||
/// A fling has started and a repaint needs to happen to process the animation.
|
||||
const StartedFlinging = 1 << 4;
|
||||
/// A blinking text caret requires a redraw.
|
||||
const BlinkingCaret = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, LazyCell};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@@ -47,10 +47,10 @@ use webrender_api::units::{
|
||||
};
|
||||
use webrender_api::{
|
||||
self, BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DirtyRect, DisplayListPayload,
|
||||
DocumentId, Epoch as WebRenderEpoch, ExternalScrollId, FontInstanceFlags, FontInstanceKey,
|
||||
FontInstanceOptions, FontKey, FontVariation, ImageData, ImageKey, NativeFontHandle,
|
||||
PipelineId as WebRenderPipelineId, PropertyBinding, ReferenceFrameKind, RenderReasons,
|
||||
SampledScrollOffset, SpaceAndClipInfo, SpatialId, TransformStyle,
|
||||
DocumentId, DynamicProperties, Epoch as WebRenderEpoch, ExternalScrollId, FontInstanceFlags,
|
||||
FontInstanceKey, FontInstanceOptions, FontKey, FontVariation, ImageData, ImageKey,
|
||||
NativeFontHandle, PipelineId as WebRenderPipelineId, PropertyBinding, ReferenceFrameKind,
|
||||
RenderReasons, SampledScrollOffset, SpaceAndClipInfo, SpatialId, TransformStyle,
|
||||
};
|
||||
use wr_malloc_size_of::MallocSizeOfOps;
|
||||
|
||||
@@ -60,6 +60,7 @@ use crate::paint::{RepaintReason, WebRenderDebugOption};
|
||||
use crate::refresh_driver::{AnimationRefreshDriverObserver, BaseRefreshDriver};
|
||||
use crate::render_notifier::RenderNotifier;
|
||||
use crate::screenshot::ScreenshotTaker;
|
||||
use crate::web_content_animation::WebContentAnimator;
|
||||
use crate::webrender_external_images::WebGLExternalImages;
|
||||
use crate::webview_renderer::{PinchZoomResult, ScrollResult, UnknownWebView, WebViewRenderer};
|
||||
|
||||
@@ -127,6 +128,10 @@ pub(crate) struct Painter {
|
||||
/// A cache that stores data for all animating images uploaded to WebRender. This is used
|
||||
/// for animated images, which only need to update their offset in the data.
|
||||
animation_image_cache: FxHashMap<ImageKey, Arc<Vec<u8>>>,
|
||||
|
||||
/// A [`WebContentAnimator`] used to manage web content-derived animations. Currently this only
|
||||
/// manages blinking caret animations.
|
||||
web_content_animator: WebContentAnimator,
|
||||
}
|
||||
|
||||
impl Drop for Painter {
|
||||
@@ -175,9 +180,11 @@ impl Painter {
|
||||
WindowGLContext::initialize_image_handler(&mut external_image_handlers);
|
||||
|
||||
let embedder_to_constellation_sender = paint.embedder_to_constellation_sender.clone();
|
||||
let timer_refresh_driver = LazyCell::default();
|
||||
let refresh_driver = Rc::new(BaseRefreshDriver::new(
|
||||
paint.event_loop_waker.clone_box(),
|
||||
rendering_context.refresh_driver(),
|
||||
&timer_refresh_driver,
|
||||
));
|
||||
let animation_refresh_driver_observer = Rc::new(AnimationRefreshDriverObserver::new(
|
||||
embedder_to_constellation_sender.clone(),
|
||||
@@ -272,6 +279,10 @@ impl Painter {
|
||||
frame_delayer: Default::default(),
|
||||
lcp_calculator: LargestContentfulPaintCalculator::new(),
|
||||
animation_image_cache: FxHashMap::default(),
|
||||
web_content_animator: WebContentAnimator::new(
|
||||
paint.event_loop_waker.clone_box(),
|
||||
(*timer_refresh_driver).clone(),
|
||||
),
|
||||
};
|
||||
painter.assert_gl_framebuffer_complete();
|
||||
painter.clear_background();
|
||||
@@ -297,6 +308,18 @@ impl Painter {
|
||||
.collect();
|
||||
|
||||
self.send_zoom_and_scroll_offset_updates(need_zoom, scroll_offset_updates);
|
||||
|
||||
if let Some(colors) = self.web_content_animator.update(&self.webview_renderers) {
|
||||
let mut transaction = Transaction::new();
|
||||
transaction.reset_dynamic_properties();
|
||||
transaction.append_dynamic_properties(DynamicProperties {
|
||||
transforms: Vec::new(),
|
||||
floats: Vec::new(),
|
||||
colors,
|
||||
});
|
||||
self.generate_frame(&mut transaction, RenderReasons::ANIMATED_PROPERTY);
|
||||
self.send_transaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -970,6 +993,11 @@ impl Painter {
|
||||
.set(PaintMetricState::Seen(epoch, first_reflow));
|
||||
}
|
||||
|
||||
details.animations.handle_new_display_list(
|
||||
display_list_info.caret_property_binding,
|
||||
&self.web_content_animator,
|
||||
);
|
||||
|
||||
let mut transaction = Transaction::new();
|
||||
let is_root_pipeline = Some(pipeline_id.into()) == webview_renderer.root_pipeline_id;
|
||||
if is_root_pipeline && old_scale != webview_renderer.device_pixels_per_page_pixel() {
|
||||
|
||||
@@ -13,6 +13,7 @@ use style_traits::CSSPixel;
|
||||
use webrender_api::units::DevicePixel;
|
||||
|
||||
use crate::painter::PaintMetricState;
|
||||
use crate::web_content_animation::PipelineAnimations;
|
||||
|
||||
pub(crate) struct PipelineDetails {
|
||||
/// The pipeline associated with this PipelineDetails object.
|
||||
@@ -21,6 +22,9 @@ pub(crate) struct PipelineDetails {
|
||||
/// The id of the parent pipeline, if any.
|
||||
pub parent_pipeline_id: Option<PipelineId>,
|
||||
|
||||
/// The ids of the child pipelines for this pipeline.
|
||||
pub children: Vec<PipelineId>,
|
||||
|
||||
/// Whether animations are running
|
||||
pub animations_running: bool,
|
||||
|
||||
@@ -55,6 +59,13 @@ pub(crate) struct PipelineDetails {
|
||||
/// The [`Epoch`] of the latest display list received for this `Pipeline` or `None` if no
|
||||
/// display list has been received.
|
||||
pub display_list_epoch: Option<Epoch>,
|
||||
|
||||
/// Paint-driven animations associated with this [`PipelineDetails`]. Currently only text caret
|
||||
/// is handled this way.
|
||||
///
|
||||
/// Note: This does not manage animations and transitions from CSS or for user input
|
||||
/// interaction.
|
||||
pub animations: PipelineAnimations,
|
||||
}
|
||||
|
||||
impl PipelineDetails {
|
||||
@@ -72,6 +83,7 @@ impl PipelineDetails {
|
||||
PipelineDetails {
|
||||
pipeline: None,
|
||||
parent_pipeline_id: None,
|
||||
children: Default::default(),
|
||||
viewport_scale: None,
|
||||
animations_running: false,
|
||||
animation_callbacks_running: false,
|
||||
@@ -82,6 +94,7 @@ impl PipelineDetails {
|
||||
largest_contentful_paint_metric: Cell::new(PaintMetricState::Waiting),
|
||||
exited: PipelineExitSource::empty(),
|
||||
display_list_epoch: None,
|
||||
animations: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cell::{Cell, LazyCell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -40,9 +40,9 @@ impl BaseRefreshDriver {
|
||||
pub(crate) fn new(
|
||||
event_loop_waker: Box<dyn EventLoopWaker>,
|
||||
refresh_driver: Option<Rc<dyn RefreshDriver>>,
|
||||
timer_refresh_driver: &LazyCell<Rc<TimerRefreshDriver>>,
|
||||
) -> Self {
|
||||
let refresh_driver =
|
||||
refresh_driver.unwrap_or_else(|| Rc::new(TimerRefreshDriver::default()));
|
||||
let refresh_driver = refresh_driver.unwrap_or_else(|| (**timer_refresh_driver).clone());
|
||||
Self {
|
||||
waiting_for_frame: Arc::new(AtomicBool::new(false)),
|
||||
event_loop_waker,
|
||||
@@ -193,7 +193,7 @@ enum TimerThreadMessage {
|
||||
/// It would be nice to integrate this somehow into the embedder thread, but it would
|
||||
/// require both some communication with the embedder and for all embedders to be well
|
||||
/// behave respecting wakeup timeouts -- a bit too much to ask at the moment.
|
||||
struct TimerRefreshDriver {
|
||||
pub(crate) struct TimerRefreshDriver {
|
||||
sender: Sender<TimerThreadMessage>,
|
||||
join_handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
@@ -232,7 +232,7 @@ impl Default for TimerRefreshDriver {
|
||||
}
|
||||
|
||||
impl TimerRefreshDriver {
|
||||
fn queue_timer(&self, duration: Duration, callback: BoxedTimerCallback) {
|
||||
pub(crate) fn queue_timer(&self, duration: Duration, callback: BoxedTimerCallback) {
|
||||
let _ = self
|
||||
.sender
|
||||
.send(TimerThreadMessage::Request(TimerEventRequest {
|
||||
|
||||
179
components/paint/web_content_animation.rs
Normal file
179
components/paint/web_content_animation.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use base::id::WebViewId;
|
||||
use embedder_traits::EventLoopWaker;
|
||||
use rustc_hash::FxHashMap;
|
||||
use servo_config::prefs;
|
||||
use webrender_api::{ColorF, PropertyBindingKey, PropertyValue};
|
||||
|
||||
use crate::refresh_driver::TimerRefreshDriver;
|
||||
use crate::webview_renderer::WebViewRenderer;
|
||||
|
||||
/// The amount of the time the caret blinks before ceasing, in order to preserve power. User
|
||||
/// activity (a new display list) will reset this.
|
||||
///
|
||||
/// TODO: This should be controlled by system settings.
|
||||
pub(crate) const CARET_BLINK_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
/// A struct responsible for managing paint-side animations. Currently this only handles text caret
|
||||
/// blinking, but the idea is that in the future this would handle other types of paint-side
|
||||
/// animations as well.
|
||||
///
|
||||
/// Note: This does not control animations requiring layout (all CSS transitions and animations
|
||||
/// currently) nor animations due to touch events such as fling.
|
||||
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() {
|
||||
// All animations have stopped. When a new blinking caret is activated we want
|
||||
// it to start in the visible state, so we set `caret_visible` to true here.
|
||||
self.caret_visible.set(true);
|
||||
return None;
|
||||
}
|
||||
|
||||
self.caret_visible.set(!self.caret_visible.get());
|
||||
self.schedule_timer_if_necessary();
|
||||
Some(colors)
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure tracks the animations active for a given pipeline. Currently only caret
|
||||
/// blinking is tracked, but in the future this could perhaps track paint-side animations.
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks the state of an ongoing caret blinking animation.
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -270,6 +270,11 @@ impl WebViewRenderer {
|
||||
let pipeline_details = self.ensure_pipeline_details(pipeline_id);
|
||||
pipeline_details.pipeline = Some(frame_tree.pipeline.clone());
|
||||
pipeline_details.parent_pipeline_id = parent_pipeline_id;
|
||||
pipeline_details.children = frame_tree
|
||||
.children
|
||||
.iter()
|
||||
.map(|frame_tree| frame_tree.pipeline.id)
|
||||
.collect();
|
||||
|
||||
for kid in &frame_tree.children {
|
||||
self.set_frame_tree_on_pipeline_details(kid, Some(pipeline_id));
|
||||
@@ -333,6 +338,26 @@ impl WebViewRenderer {
|
||||
self.webview.set_animating(self.animating());
|
||||
}
|
||||
|
||||
pub(crate) fn for_each_connected_pipeline(&self, callback: &mut impl FnMut(&PipelineDetails)) {
|
||||
if let Some(root_pipeline_id) = self.root_pipeline_id {
|
||||
self.for_each_connected_pipeline_internal(root_pipeline_id, callback);
|
||||
}
|
||||
}
|
||||
|
||||
fn for_each_connected_pipeline_internal(
|
||||
&self,
|
||||
pipeline_id: PipelineId,
|
||||
callback: &mut impl FnMut(&PipelineDetails),
|
||||
) {
|
||||
let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
|
||||
return;
|
||||
};
|
||||
callback(pipeline);
|
||||
for child_pipeline_id in &pipeline.children {
|
||||
self.for_each_connected_pipeline_internal(*child_pipeline_id, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update touch-based animations (currently just fling) during a `RefreshDriver`-based
|
||||
/// frame tick. Returns `true` if we should continue observing frames (the fling is ongoing)
|
||||
/// or `false` if we should stop observing frames (the fling has finished).
|
||||
|
||||
@@ -279,6 +279,12 @@ impl From<PipelineId> for TreeId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PipelineId> for u64 {
|
||||
fn from(pipeline_id: PipelineId) -> Self {
|
||||
((pipeline_id.namespace_id.0 as u64) << 32) + pipeline_id.index.0.get() as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_pipeline_id_to_accesskit_tree_id() {
|
||||
|
||||
@@ -20,8 +20,8 @@ use servo_geometry::FastLayoutTransform;
|
||||
use style::values::specified::Overflow;
|
||||
use webrender_api::units::{LayoutPixel, LayoutPoint, LayoutRect, LayoutSize, LayoutVector2D};
|
||||
use webrender_api::{
|
||||
ExternalScrollId, PipelineId, ReferenceFrameKind, ScrollLocation, SpatialId,
|
||||
StickyOffsetBounds, TransformStyle,
|
||||
ColorF, ExternalScrollId, PipelineId, PropertyBindingKey, ReferenceFrameKind, ScrollLocation,
|
||||
SpatialId, StickyOffsetBounds, TransformStyle,
|
||||
};
|
||||
|
||||
/// A scroll type, describing whether what kind of action originated this scroll request.
|
||||
@@ -850,6 +850,10 @@ pub struct PaintDisplayListInfo {
|
||||
/// Whether the first layout or a subsequent (incremental) layout triggered this
|
||||
/// display list creation.
|
||||
pub first_reflow: bool,
|
||||
|
||||
/// If this display list contains a blinking caret, this value will be filled with its animation
|
||||
/// key and original color value so that the painter can animate the caret.
|
||||
pub caret_property_binding: Option<(PropertyBindingKey<ColorF>, ColorF)>,
|
||||
}
|
||||
|
||||
impl PaintDisplayListInfo {
|
||||
@@ -899,6 +903,7 @@ impl PaintDisplayListInfo {
|
||||
root_scroll_node_id,
|
||||
is_contentful: false,
|
||||
first_reflow,
|
||||
caret_property_binding: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"dom_webxr_test": true,
|
||||
"editing_caret_blink_time": 0,
|
||||
"gfx_text_antialiasing_enabled": false,
|
||||
"dom_testutils_enabled": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user