use std::any::Any;
use std::sync::Arc;
use skia_safe::{ClipOp, FilterMode, MipmapMode, Paint, SamplingOptions, Size};
use crate::animated_frames::{self, FramePlayer, FramesProps};
use crate::control::{Control, Has, LayoutCx, PaintCx};
use crate::controls::image::{DrawImageAlignment, Loaded, TransformAspect, place};
use crate::images::Frames;
use crate::props;
use crate::tree::{Build, ControlId, Cx, Handle, Tree, wrong_state};
use crate::types::{CacheType, Dirty, IntoProp};
props!(GifProps, GifBuild, GifSet {
source / set_source: String = String::new(), MEASURE;
aspect / set_aspect: TransformAspect = TransformAspect::AspectFitFill, MEASURE;
horizontal_alignment / set_horizontal_alignment: DrawImageAlignment = DrawImageAlignment::Center, DRAW;
vertical_alignment / set_vertical_alignment: DrawImageAlignment = DrawImageAlignment::Center, DRAW;
});
#[derive(Default)]
pub struct SkiaGif {
pub p: GifProps,
player: FramePlayer,
frames: Option<Arc<Frames>>,
pub(crate) resolved: String,
loading: bool,
error: bool,
paint: Paint,
pub(crate) on_success: Option<Loaded>,
pub(crate) on_error: Option<Loaded>,
}
impl SkiaGif {
#[allow(clippy::new_ret_no_self)]
pub fn new(source: impl IntoProp<String>) -> Build<SkiaGif> {
let mut paint = Paint::default();
paint.set_anti_alias(true);
let cache = if cfg!(target_os = "emscripten") { CacheType::None } else { CacheType::ImageDoubleBuffered };
Build::new(SkiaGif { paint, ..SkiaGif::default() }).source(source).use_cache(cache)
}
pub fn animation(&self) -> Option<&Frames> {
self.frames.as_deref().filter(|_| self.resolved == self.p.source)
}
pub fn player(&self) -> &FramePlayer {
&self.player
}
pub fn is_loading(&self) -> bool {
self.loading
}
pub fn has_error(&self) -> bool {
self.error
}
fn resolve(&mut self, cx: &mut LayoutCx) {
if self.resolved == self.p.source {
return;
}
let images = &mut cx.tree.images;
images.release(&self.resolved, cx.id);
self.resolved.clone_from(&self.p.source);
(self.frames, self.error) = (None, false);
if !self.resolved.is_empty()
&& let Some(frames) = images.request_frames(&self.resolved, cx.id)
{
if self.on_success.is_some() {
images.events.push((cx.id, self.resolved.clone(), true));
}
self.take(frames);
}
self.loading = self.frames.is_none() && !self.resolved.is_empty();
}
fn take(&mut self, frames: Arc<Frames>) {
self.player.set_frames(frames.durations.iter().map(|ms| *ms as f32));
self.frames = Some(frames);
}
pub(crate) fn arrived(
tree: &mut Tree,
id: ControlId,
source: &str,
frames: Option<Arc<Frames>>,
) -> Vec<(ControlId, String, bool)> {
let Some(mut me) = tree.find_mut::<SkiaGif>(id) else { return Vec::new() };
let gif = me.control_mut();
let mut events = Vec::new();
match frames {
Some(frames) => {
gif.take(frames);
(gif.loading, gif.error) = (false, false);
if gif.on_success.is_some() {
events.push((id, source.to_owned(), true));
}
me.mark(Dirty::MEASURE);
}
None => {
(gif.loading, gif.error) = (false, true);
if gif.on_error.is_some() {
events.push((id, source.to_owned(), false));
}
}
}
events
}
}
impl Has<GifProps> for SkiaGif {
fn part(&self) -> &GifProps {
&self.p
}
fn part_mut(&mut self) -> &mut GifProps {
&mut self.p
}
}
impl Has<FramesProps> for SkiaGif {
fn part(&self) -> &FramesProps {
&self.player.p
}
fn part_mut(&mut self) -> &mut FramesProps {
&mut self.player.p
}
}
impl Control for SkiaGif {
fn inner(&self) -> Option<&dyn Control> {
Some(&self.player)
}
fn inner_mut(&mut self) -> Option<&mut dyn Control> {
Some(&mut self.player)
}
fn measure(&mut self, cx: &mut LayoutCx, width: f32, height: f32) -> Size {
self.resolve(cx);
let (mut w, mut h) = (width, height);
if let Some(size) = self.animation().map(|frames| frames.size).filter(|size| !size.is_empty()) {
let aspect = size.width as f32 / size.height as f32;
if !w.is_finite() && h.is_finite() {
w = h * aspect;
} else if !h.is_finite() && w.is_finite() {
h = w / aspect;
}
}
if !w.is_finite() || !h.is_finite() {
return Size::default();
}
Size::new(w, h)
}
fn arrange(&mut self, cx: &mut LayoutCx) {
animated_frames::placed(&mut self.player, cx.tree, cx.id);
}
fn paint(&self, cx: &mut PaintCx) {
let Some(frames) = self.animation() else { return };
let Some(image) = frames.images.get(self.player.current()) else { return };
let (h, v) = (self.p.horizontal_alignment, self.p.vertical_alignment);
let display = place(frames.size, cx.rect, self.p.aspect, h, v, (1.0, 1.0), (0.0, 0.0));
cx.canvas.save();
cx.canvas.clip_rect(cx.rect, ClipOp::Intersect, true);
let sampling = SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear);
cx.canvas.draw_image_rect_with_sampling_options(image, None, display, sampling, &self.paint);
cx.canvas.restore();
}
}
fn handler<T: 'static, S: Any>(
me: Handle<T>,
mut f: impl FnMut(Handle<T>, &mut S, &mut Cx<'_>, &str) + 'static,
) -> Loaded {
Box::new(move |state, cx, source| {
let state = state.downcast_mut::<S>().unwrap_or_else(|| wrong_state::<S>());
f(me, state, cx, source)
})
}
impl Build<SkiaGif> {
pub fn on_success<S: Any>(
mut self,
f: impl FnMut(Handle<SkiaGif>, &mut S, &mut Cx<'_>, &str) + 'static,
) -> Self {
let me = self.handle();
self.control_mut().on_success = Some(handler(me, f));
self
}
pub fn on_error<S: Any>(mut self, f: impl FnMut(Handle<SkiaGif>, &mut S, &mut Cx<'_>, &str) + 'static) -> Self {
let me = self.handle();
self.control_mut().on_error = Some(handler(me, f));
self
}
}