first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -1,18 +1,39 @@
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use instant::Instant;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F, Vector2I};
|
||||
|
||||
use super::{CornerRadius, Element, Point};
|
||||
use crate::assets::asset_cache::{AssetCache, AssetSource, AssetState};
|
||||
use crate::event::DispatchedEvent;
|
||||
pub use crate::image_cache::CacheOption;
|
||||
use crate::image_cache::{AnimatedImage, AnimatedImageBehavior, FitType, ImageCache, StaticImage};
|
||||
use crate::{
|
||||
assets::asset_cache::{AssetCache, AssetSource, AssetState},
|
||||
event::DispatchedEvent,
|
||||
image_cache::{AnimatedImage, AnimatedImageBehavior, FitType, ImageCache, StaticImage},
|
||||
AfterLayoutContext, AppContext, EventContext, LayoutContext, PaintContext, SingletonEntity,
|
||||
SizeConstraint,
|
||||
};
|
||||
|
||||
pub use crate::image_cache::CacheOption;
|
||||
use instant::Instant;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F, Vector2I};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
lazy_static! {
|
||||
static ref IMAGE_LOAD_TIMEOUT_STARTED_AT: Mutex<HashMap<u64, Instant>> =
|
||||
Mutex::new(HashMap::new());
|
||||
}
|
||||
struct LoadTimeout {
|
||||
timeout: Duration,
|
||||
element: Box<dyn Element>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum BackupElementKind {
|
||||
BeforeLoad,
|
||||
FailedToLoad,
|
||||
LoadTimeout,
|
||||
}
|
||||
|
||||
pub struct Image {
|
||||
source: AssetSource,
|
||||
@@ -20,6 +41,9 @@ pub struct Image {
|
||||
size: Option<Vector2F>,
|
||||
origin: Option<Point>,
|
||||
fit_type: FitType,
|
||||
/// If true, layout will produce a size based on the bounds at which the image will
|
||||
/// be painted instead of simply using the incoming max constraint.
|
||||
layout_using_paint_bounds: bool,
|
||||
animated_image_behavior: AnimatedImageBehavior,
|
||||
cache_option: CacheOption,
|
||||
started_at: Option<Instant>,
|
||||
@@ -31,6 +55,8 @@ pub struct Image {
|
||||
/// This could be None in two situations: (1) the caller does not provide a before_load_element
|
||||
/// or (2) the caller provided one but it's no longer needed due to the image having loaded.
|
||||
before_load_element: Option<Box<dyn Element>>,
|
||||
failed_to_load_element: Option<Box<dyn Element>>,
|
||||
load_timeout: Option<LoadTimeout>,
|
||||
|
||||
/// To avoid duplicating delayed repaint, we store whether or not we've requested a
|
||||
/// repaint on behalf of this element.
|
||||
@@ -65,7 +91,10 @@ impl Image {
|
||||
top_aligned: false,
|
||||
right_aligned: false,
|
||||
before_load_element: None,
|
||||
failed_to_load_element: None,
|
||||
load_timeout: None,
|
||||
requested_repaint_after_load: false,
|
||||
layout_using_paint_bounds: false,
|
||||
#[cfg(debug_assertions)]
|
||||
constructor_location: Some(std::panic::Location::caller()),
|
||||
}
|
||||
@@ -119,6 +148,16 @@ impl Image {
|
||||
self
|
||||
}
|
||||
|
||||
/// Uses the paint boundary of the image as the laid-out size.
|
||||
///
|
||||
/// By default, the [`Image`] element bounds will be the incoming max constraint.
|
||||
/// For some cases, we'd prefer for the element's laid-out size to match the painted
|
||||
/// image size.
|
||||
pub fn layout_using_paint_bounds(mut self) -> Self {
|
||||
self.layout_using_paint_bounds = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables animated images for the current image element. The start time indicates
|
||||
/// the timestamp at which the animated image started rendering. The element uses
|
||||
/// this timestamp to calculate which frame of the animation to display at a given
|
||||
@@ -134,6 +173,96 @@ impl Image {
|
||||
self.before_load_element = Some(element);
|
||||
self
|
||||
}
|
||||
pub fn on_load_failure(mut self, element: Box<dyn Element>) -> Self {
|
||||
self.failed_to_load_element = Some(element);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_load_timeout(mut self, timeout: Duration, element: Box<dyn Element>) -> Self {
|
||||
self.load_timeout = Some(LoadTimeout { timeout, element });
|
||||
self
|
||||
}
|
||||
|
||||
fn load_timeout_key(&self) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
self.source.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
fn load_started_at(&self, now: Instant) -> Instant {
|
||||
*IMAGE_LOAD_TIMEOUT_STARTED_AT
|
||||
.lock()
|
||||
.entry(self.load_timeout_key())
|
||||
.or_insert(now)
|
||||
}
|
||||
|
||||
fn clear_load_timeout_started_at(&self) {
|
||||
IMAGE_LOAD_TIMEOUT_STARTED_AT
|
||||
.lock()
|
||||
.remove(&self.load_timeout_key());
|
||||
}
|
||||
|
||||
fn loading_backup_element_kind(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
) -> (Option<BackupElementKind>, Option<Duration>) {
|
||||
let Some(load_timeout) = self.load_timeout.as_ref() else {
|
||||
return (
|
||||
self.before_load_element
|
||||
.as_ref()
|
||||
.map(|_| BackupElementKind::BeforeLoad),
|
||||
None,
|
||||
);
|
||||
};
|
||||
let started_at = self.load_started_at(now);
|
||||
let elapsed = now.duration_since(started_at);
|
||||
if elapsed >= load_timeout.timeout {
|
||||
return (Some(BackupElementKind::LoadTimeout), None);
|
||||
}
|
||||
|
||||
(
|
||||
self.before_load_element
|
||||
.as_ref()
|
||||
.map(|_| BackupElementKind::BeforeLoad),
|
||||
Some(load_timeout.timeout - elapsed),
|
||||
)
|
||||
}
|
||||
|
||||
fn failed_to_load_backup_element_kind(&self) -> Option<BackupElementKind> {
|
||||
if self.failed_to_load_element.is_some() {
|
||||
Some(BackupElementKind::FailedToLoad)
|
||||
} else if self.before_load_element.is_some() {
|
||||
Some(BackupElementKind::BeforeLoad)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_backup_element(
|
||||
&mut self,
|
||||
kind: BackupElementKind,
|
||||
origin: Vector2F,
|
||||
ctx: &mut PaintContext,
|
||||
app: &AppContext,
|
||||
) {
|
||||
match kind {
|
||||
BackupElementKind::BeforeLoad => {
|
||||
if let Some(before_load_element) = self.before_load_element.as_mut() {
|
||||
before_load_element.paint(origin, ctx, app);
|
||||
}
|
||||
}
|
||||
BackupElementKind::FailedToLoad => {
|
||||
if let Some(failed_to_load_element) = self.failed_to_load_element.as_mut() {
|
||||
failed_to_load_element.paint(origin, ctx, app);
|
||||
}
|
||||
}
|
||||
BackupElementKind::LoadTimeout => {
|
||||
if let Some(load_timeout) = self.load_timeout.as_mut() {
|
||||
load_timeout.element.paint(origin, ctx, app);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_static_image(
|
||||
&mut self,
|
||||
@@ -278,12 +407,27 @@ impl Element for Image {
|
||||
ctx: &mut LayoutContext,
|
||||
app: &AppContext,
|
||||
) -> Vector2F {
|
||||
let size = constraint.max;
|
||||
let mut size = constraint.max;
|
||||
|
||||
if self.layout_using_paint_bounds {
|
||||
let asset_cache = AssetCache::as_ref(app);
|
||||
let image_size = ImageCache::as_ref(app).image_size(self.source.clone(), asset_cache);
|
||||
if let Some(image_size) = image_size {
|
||||
size = dimensions(image_size.to_f32(), size, self.fit_type);
|
||||
}
|
||||
}
|
||||
|
||||
self.size = Some(size);
|
||||
|
||||
if let Some(before_load_element) = self.before_load_element.as_mut() {
|
||||
before_load_element.layout(constraint, ctx, app);
|
||||
}
|
||||
if let Some(failed_to_load_element) = self.failed_to_load_element.as_mut() {
|
||||
failed_to_load_element.layout(constraint, ctx, app);
|
||||
}
|
||||
if let Some(load_timeout) = self.load_timeout.as_mut() {
|
||||
load_timeout.element.layout(constraint, ctx, app);
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
@@ -292,6 +436,12 @@ impl Element for Image {
|
||||
if let Some(before_load_element) = self.before_load_element.as_mut() {
|
||||
before_load_element.after_layout(ctx, app);
|
||||
}
|
||||
if let Some(failed_to_load_element) = self.failed_to_load_element.as_mut() {
|
||||
failed_to_load_element.after_layout(ctx, app);
|
||||
}
|
||||
if let Some(load_timeout) = self.load_timeout.as_mut() {
|
||||
load_timeout.element.after_layout(ctx, app);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
|
||||
@@ -335,25 +485,34 @@ impl Element for Image {
|
||||
ctx.repaint_after_load(handle);
|
||||
self.requested_repaint_after_load = true;
|
||||
}
|
||||
|
||||
if let Some(before_load_element) = self.before_load_element.as_mut() {
|
||||
before_load_element.paint(origin, ctx, app);
|
||||
let (backup_element_kind, repaint_after) =
|
||||
self.loading_backup_element_kind(Instant::now());
|
||||
if let Some(repaint_after) = repaint_after {
|
||||
ctx.repaint_after(repaint_after);
|
||||
}
|
||||
if let Some(kind) = backup_element_kind {
|
||||
self.paint_backup_element(kind, origin, ctx, app);
|
||||
}
|
||||
}
|
||||
AssetState::Evicted => {
|
||||
self.clear_load_timeout_started_at();
|
||||
if let Some(before_load_element) = self.before_load_element.as_mut() {
|
||||
before_load_element.paint(origin, ctx, app);
|
||||
}
|
||||
}
|
||||
AssetState::FailedToLoad(_) => {
|
||||
if let Some(before_load_element) = self.before_load_element.as_mut() {
|
||||
before_load_element.paint(origin, ctx, app);
|
||||
self.clear_load_timeout_started_at();
|
||||
if let Some(kind) = self.failed_to_load_backup_element_kind() {
|
||||
self.paint_backup_element(kind, origin, ctx, app);
|
||||
}
|
||||
}
|
||||
AssetState::Loaded { data } => {
|
||||
// Don't waste time calling layout() and after_layout() on the backup element once the main
|
||||
// one has loaded.
|
||||
self.clear_load_timeout_started_at();
|
||||
self.before_load_element = None;
|
||||
self.failed_to_load_element = None;
|
||||
self.load_timeout = None;
|
||||
|
||||
match data.as_ref() {
|
||||
crate::image_cache::Image::Static(static_image) => {
|
||||
|
||||
Reference in New Issue
Block a user