use std::cell::RefCell;
use std::rc::Rc;
use glam::DVec2;
use renamite_render_bridge::SceneRenderer;
use repose_canvas::{Canvas, DrawScope};
use repose_core::input::PointerEvent;
use repose_core::{Modifier, RenderContext, Vec2, View, request_frame, theme};
pub use renamite_player::{Player, PlayerError};
pub use renamite_player_ui::{PlayerHost, PlayerHostRef, ViewTransform};
pub const MINIMAL_REN: &str = "RenFile(format_version: 1, meta: Meta(name: \"\", author: \"\", generator: \"\"), document: Document(format_version: 1, compositions: [SerdeSlot(value: None, version: 0), SerdeSlot(value: Some(Composition(name: \"\", size: (64, 64), rate: FrameRate(num: 60, den: 1), range: (Frame(0), Frame(1)), children: [])), version: 1)], nodes: [SerdeSlot(value: None, version: 0)], assets: [SerdeSlot(value: None, version: 0)], main: SerKey(idx: 1, version: 1)))";
pub fn host_from_str(source: &str) -> Result<PlayerHostRef, PlayerError> {
Ok(Rc::new(RefCell::new(PlayerHost::from_ren_str(source)?)))
}
pub fn host_from_bytes(bytes: &[u8]) -> Result<PlayerHostRef, PlayerError> {
Ok(Rc::new(RefCell::new(PlayerHost::from_ren_bytes(bytes)?)))
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ActorFit {
#[default]
Exact,
Margin,
}
#[derive(Clone, Copy, Debug)]
pub struct ActorViewOpts {
pub fit: ActorFit,
pub chrome: bool,
pub interactive: bool,
pub auto_tick: bool,
}
impl Default for ActorViewOpts {
fn default() -> Self {
Self {
fit: ActorFit::Exact,
chrome: false,
interactive: false,
auto_tick: false,
}
}
}
impl ActorViewOpts {
pub fn editor() -> Self {
Self {
fit: ActorFit::Margin,
chrome: true,
interactive: true,
auto_tick: true,
}
}
}
pub fn actors_view(host: PlayerHostRef, ctx: RenderContext) -> View {
actors_view_with(host, ctx, ActorViewOpts::default())
}
pub fn actors_view_with(host: PlayerHostRef, ctx: RenderContext, opts: ActorViewOpts) -> View {
let draw = host.clone();
let mut modifier = Modifier::new().fill_max_size();
if opts.chrome {
modifier = modifier.background(theme().surface_container_lowest);
}
if opts.interactive {
modifier = modifier
.on_scroll({
let host = host.clone();
move |delta: Vec2| {
let mut h = host.borrow_mut();
let factor = (1.0 + (-delta.y as f64) * 0.002).clamp(0.5, 2.0);
let anchor = h.last_pointer;
h.zoom_at(anchor, factor);
request_frame();
Vec2::ZERO
}
})
.on_pointer_down({
let host = host.clone();
move |pe: PointerEvent| {
let mut h = host.borrow_mut();
let p = pe_position(&pe);
h.last_pointer = p;
let world = h.view.screen_to_world(p);
h.player.pointer_down(world);
request_frame();
}
})
.on_pointer_up({
let host = host.clone();
move |pe: PointerEvent| {
let mut h = host.borrow_mut();
let world = h.view.screen_to_world(pe_position(&pe));
h.player.pointer_up(world);
request_frame();
}
})
.on_pointer_move({
let host = host.clone();
move |pe: PointerEvent| {
let mut h = host.borrow_mut();
let p = pe_position(&pe);
h.last_pointer = p;
let world = h.view.screen_to_world(p);
h.player.pointer_move(world);
request_frame();
}
})
.on_pointer_leave({
let host = host.clone();
move |_pe: PointerEvent| {
host.borrow_mut().player.pointer_leave();
request_frame();
}
});
} else {
modifier = modifier.hit_passthrough();
}
Canvas(modifier, move |scope| {
let mut h = draw.borrow_mut();
if opts.auto_tick && h.tick_playback() {
request_frame();
}
let sw = scope.size.width as f64;
let sh = scope.size.height as f64;
if sw <= 1.0 || sh <= 1.0 {
return;
}
let art = h.artboard();
let art_valid = art.x > 0.0 && art.y > 0.0;
let surface = DVec2::new(sw, sh);
match opts.fit {
ActorFit::Exact => {
if !art_valid {
return;
}
h.fit_exact(surface);
}
ActorFit::Margin => {
h.fit(surface);
}
}
if h.dirty_images {
let host = &mut *h;
host.renderer
.sync_document_images(&host.player.project.document, &ctx);
host.dirty_images = false;
}
if opts.chrome && art_valid {
paint_chrome(scope, art, h.view);
}
let scene = h.player.scene().clone();
let view = h.view;
let prepared = h.renderer.prepare(&scene, &view);
h.renderer.paint_prepared(&prepared, scope);
})
}
fn pe_position(pe: &PointerEvent) -> DVec2 {
DVec2::new(pe.position.x as f64, pe.position.y as f64)
}
fn paint_chrome(scope: &mut DrawScope, artboard: DVec2, view: ViewTransform) {
SceneRenderer::paint_artboard_chrome(scope, artboard, &view);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn opts_default_is_presentational() {
let opts = ActorViewOpts::default();
assert_eq!(opts.fit, ActorFit::Exact);
assert!(!opts.chrome && !opts.interactive && !opts.auto_tick);
}
#[test]
fn opts_editor_is_full_control() {
let opts = ActorViewOpts::editor();
assert_eq!(opts.fit, ActorFit::Margin);
assert!(opts.chrome && opts.interactive && opts.auto_tick);
}
#[test]
fn host_parses_minimal_document() {
let host = host_from_str(MINIMAL_REN).expect("minimal doc must parse");
assert!(host.borrow().dirty_images);
}
#[test]
fn views_construct_without_a_backend() {
let ctx = RenderContext::new();
let _presentational = actors_view(host_from_str(MINIMAL_REN).unwrap(), ctx.clone());
let _full = actors_view_with(
host_from_str(MINIMAL_REN).unwrap(),
ctx.clone(),
ActorViewOpts::editor(),
);
let _mixed = actors_view_with(
host_from_str(MINIMAL_REN).unwrap(),
ctx,
ActorViewOpts {
interactive: true,
..ActorViewOpts::default()
},
);
}
}