1use std::cell::RefCell;
17use std::rc::Rc;
18
19use glam::DVec2;
20use renamite_render_bridge::SceneRenderer;
21use repose_canvas::{Canvas, DrawScope};
22use repose_core::input::PointerEvent;
23use repose_core::{Modifier, RenderContext, Vec2, View, request_frame, theme};
24
25pub use renamite_player::{Player, PlayerError};
26pub use renamite_player_ui::{PlayerHost, PlayerHostRef, ViewTransform};
27
28pub 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)))";
31
32pub fn host_from_str(source: &str) -> Result<PlayerHostRef, PlayerError> {
36 Ok(Rc::new(RefCell::new(PlayerHost::from_ren_str(source)?)))
37}
38
39pub fn host_from_bytes(bytes: &[u8]) -> Result<PlayerHostRef, PlayerError> {
42 Ok(Rc::new(RefCell::new(PlayerHost::from_ren_bytes(bytes)?)))
43}
44
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
47pub enum ActorFit {
48 #[default]
51 Exact,
52 Margin,
54}
55
56#[derive(Clone, Copy, Debug)]
58pub struct ActorViewOpts {
59 pub fit: ActorFit,
61 pub chrome: bool,
63 pub interactive: bool,
65 pub auto_tick: bool,
67}
68
69impl Default for ActorViewOpts {
70 fn default() -> Self {
72 Self {
73 fit: ActorFit::Exact,
74 chrome: false,
75 interactive: false,
76 auto_tick: false,
77 }
78 }
79}
80
81impl ActorViewOpts {
82 pub fn editor() -> Self {
84 Self {
85 fit: ActorFit::Margin,
86 chrome: true,
87 interactive: true,
88 auto_tick: true,
89 }
90 }
91}
92
93pub fn actors_view(host: PlayerHostRef, ctx: RenderContext) -> View {
96 actors_view_with(host, ctx, ActorViewOpts::default())
97}
98
99pub fn actors_view_with(host: PlayerHostRef, ctx: RenderContext, opts: ActorViewOpts) -> View {
102 let draw = host.clone();
103
104 let mut modifier = Modifier::new().fill_max_size();
105 if opts.chrome {
106 modifier = modifier.background(theme().surface_container_lowest);
107 }
108 if opts.interactive {
109 modifier = modifier
110 .on_scroll({
111 let host = host.clone();
112 move |delta: Vec2| {
113 let mut h = host.borrow_mut();
114 let factor = (1.0 + (-delta.y as f64) * 0.002).clamp(0.5, 2.0);
115 let anchor = h.last_pointer;
116 h.zoom_at(anchor, factor);
117 request_frame();
118 Vec2::ZERO
119 }
120 })
121 .on_pointer_down({
122 let host = host.clone();
123 move |pe: PointerEvent| {
124 let mut h = host.borrow_mut();
125 let p = pe_position(&pe);
126 h.last_pointer = p;
127 let world = h.view.screen_to_world(p);
128 h.player.pointer_down(world);
129 request_frame();
130 }
131 })
132 .on_pointer_up({
133 let host = host.clone();
134 move |pe: PointerEvent| {
135 let mut h = host.borrow_mut();
136 let world = h.view.screen_to_world(pe_position(&pe));
137 h.player.pointer_up(world);
138 request_frame();
139 }
140 })
141 .on_pointer_move({
142 let host = host.clone();
143 move |pe: PointerEvent| {
144 let mut h = host.borrow_mut();
145 let p = pe_position(&pe);
146 h.last_pointer = p;
147 let world = h.view.screen_to_world(p);
148 h.player.pointer_move(world);
149 request_frame();
150 }
151 })
152 .on_pointer_leave({
153 let host = host.clone();
154 move |_pe: PointerEvent| {
155 host.borrow_mut().player.pointer_leave();
156 request_frame();
157 }
158 });
159 } else {
160 modifier = modifier.hit_passthrough();
161 }
162
163 Canvas(modifier, move |scope| {
164 let mut h = draw.borrow_mut();
165
166 if opts.auto_tick && h.tick_playback() {
167 request_frame();
168 }
169
170 let sw = scope.size.width as f64;
171 let sh = scope.size.height as f64;
172 if sw <= 1.0 || sh <= 1.0 {
173 return;
174 }
175 let art = h.artboard();
177 let art_valid = art.x > 0.0 && art.y > 0.0;
178 let surface = DVec2::new(sw, sh);
179 match opts.fit {
180 ActorFit::Exact => {
181 if !art_valid {
182 return;
183 }
184 h.fit_exact(surface);
186 }
187 ActorFit::Margin => {
188 h.fit(surface);
190 }
191 }
192 if h.dirty_images {
193 let host = &mut *h;
194 host.renderer
195 .sync_document_images(&host.player.project.document, &ctx);
196 host.dirty_images = false;
197 }
198 if opts.chrome && art_valid {
199 paint_chrome(scope, art, h.view);
200 }
201 let scene = h.player.scene().clone();
202 let view = h.view;
203 let prepared = h.renderer.prepare(&scene, &view);
204 h.renderer.paint_prepared(&prepared, scope);
205 })
206}
207
208fn pe_position(pe: &PointerEvent) -> DVec2 {
209 DVec2::new(pe.position.x as f64, pe.position.y as f64)
210}
211
212fn paint_chrome(scope: &mut DrawScope, artboard: DVec2, view: ViewTransform) {
214 SceneRenderer::paint_artboard_chrome(scope, artboard, &view);
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn opts_default_is_presentational() {
223 let opts = ActorViewOpts::default();
224 assert_eq!(opts.fit, ActorFit::Exact);
225 assert!(!opts.chrome && !opts.interactive && !opts.auto_tick);
226 }
227
228 #[test]
229 fn opts_editor_is_full_control() {
230 let opts = ActorViewOpts::editor();
231 assert_eq!(opts.fit, ActorFit::Margin);
232 assert!(opts.chrome && opts.interactive && opts.auto_tick);
233 }
234
235 #[test]
236 fn host_parses_minimal_document() {
237 let host = host_from_str(MINIMAL_REN).expect("minimal doc must parse");
238 assert!(host.borrow().dirty_images);
240 }
241
242 #[test]
243 fn views_construct_without_a_backend() {
244 let ctx = RenderContext::new();
246 let _presentational = actors_view(host_from_str(MINIMAL_REN).unwrap(), ctx.clone());
247 let _full = actors_view_with(
248 host_from_str(MINIMAL_REN).unwrap(),
249 ctx.clone(),
250 ActorViewOpts::editor(),
251 );
252 let _mixed = actors_view_with(
253 host_from_str(MINIMAL_REN).unwrap(),
254 ctx,
255 ActorViewOpts {
256 interactive: true,
257 ..ActorViewOpts::default()
258 },
259 );
260 }
261}