renamite_player_ui/
lib.rs1use std::cell::RefCell;
28use std::rc::Rc;
29
30use glam::DVec2;
31use renamite_behavior_common::fit_exact_view;
32use renamite_player::{Player, PlayerError};
33use renamite_render_bridge::SceneRenderer;
34use repose_canvas::{Canvas, DrawScope};
35use repose_core::input::PointerEvent;
36use repose_core::{Modifier, RenderContext, Vec2, View, request_frame, theme};
37use web_time::Instant;
38
39pub type PlayerHostRef = Rc<RefCell<PlayerHost>>;
41
42pub use renamite_behavior_common::{FitState, ViewTransform};
45
46pub struct PlayerHost {
49 pub player: Player,
50 pub renderer: SceneRenderer,
51 pub view: ViewTransform,
53 pub playing: bool,
55 pub last_tick: Instant,
56 pub dirty_images: bool,
59 pub last_pointer: DVec2,
61 pub fit: FitState,
65}
66
67impl PlayerHost {
68 pub fn new(player: Player) -> Self {
69 Self {
70 player,
71 renderer: SceneRenderer::new(),
72 view: ViewTransform::identity(),
73 playing: true,
74 last_tick: Instant::now(),
75 dirty_images: true,
76 last_pointer: DVec2::ZERO,
77 fit: FitState::new(),
78 }
79 }
80
81 pub fn from_ren_str(text: &str) -> Result<Self, PlayerError> {
82 Ok(Self::new(Player::from_ren_str(text)?))
83 }
84
85 #[cfg(feature = "binary")]
86 pub fn from_ren_bytes(bytes: &[u8]) -> Result<Self, PlayerError> {
87 Ok(Self::new(Player::from_ren_bytes(bytes)?))
88 }
89
90 pub fn artboard(&self) -> DVec2 {
92 let comp = self.player.composition();
93 match self.player.project.document.compositions.get(comp) {
94 Some(c) => DVec2::new(c.size.0 as f64, c.size.1 as f64),
95 None => DVec2::ZERO,
96 }
97 }
98
99 pub fn mark_images_dirty(&mut self) {
102 self.dirty_images = true;
103 }
104
105 pub fn play(&mut self) {
106 self.playing = true;
107 self.last_tick = Instant::now();
108 request_frame();
109 }
110
111 pub fn pause(&mut self) {
112 self.playing = false;
113 }
114
115 pub fn toggle(&mut self) {
116 if self.playing {
117 self.pause();
118 } else {
119 self.play();
120 }
121 }
122
123 pub fn tick_playback(&mut self) -> bool {
127 if !self.playing {
128 return false;
129 }
130 let now = Instant::now();
131 let dt = now.duration_since(self.last_tick).as_secs_f64().min(0.05);
132 self.last_tick = now;
133 let _ = self.player.tick(dt);
134 true
135 }
136
137 pub fn tick_playback_fixed(&mut self, dt_secs: f64) -> bool {
142 if !self.playing {
143 return false;
144 }
145 let dt = dt_secs.max(0.0);
146 self.last_tick = Instant::now();
147 let _ = self.player.tick(dt);
148 true
149 }
150
151 fn ensure_fit(&mut self, surface: DVec2) {
154 let artboard = self.artboard();
155 self.fit.ensure(&mut self.view, surface, artboard, true);
156 }
157
158 pub fn fit(&mut self, surface: DVec2) {
162 let artboard = self.artboard();
163 self.fit.request();
164 if surface.x > 1.0 && surface.y > 1.0 {
165 self.fit.ensure(&mut self.view, surface, artboard, true);
166 }
167 }
168
169 pub fn fit_exact(&mut self, surface: DVec2) {
172 let artboard = self.artboard();
173 if surface.x > 1.0 && surface.y > 1.0 {
174 self.fit = FitState::with_surface(surface);
175 fit_exact_view(&mut self.view, surface, artboard);
176 }
177 }
178
179 pub fn zoom_centered(&mut self, factor: f64) {
180 self.fit.zoom_centered(&mut self.view, factor);
181 }
182
183 pub fn zoom_at(&mut self, screen_pos: DVec2, factor: f64) {
185 self.fit.zoom_at(&mut self.view, screen_pos, factor);
186 }
187
188 pub fn surface_size(&self) -> DVec2 {
190 self.fit.surface_size()
191 }
192}
193
194fn pe_position(pe: &PointerEvent) -> DVec2 {
195 DVec2::new(pe.position.x as f64, pe.position.y as f64)
196}
197
198#[allow(non_snake_case)]
199pub fn RenamitePlayer(host: PlayerHostRef, render_context: RenderContext) -> View {
203 let draw = host.clone();
204
205 Canvas(
206 Modifier::new()
207 .fill_max_size()
208 .background(theme().surface_container_lowest)
209 .on_scroll({
210 let host = host.clone();
211 move |delta: Vec2| {
212 let mut h = host.borrow_mut();
213 let factor = (1.0 + (-delta.y as f64) * 0.002).clamp(0.5, 2.0);
214 let anchor = h.last_pointer;
215 h.zoom_at(anchor, factor);
216 request_frame();
217 Vec2::ZERO
218 }
219 })
220 .on_pointer_down({
221 let host = host.clone();
222 move |pe: PointerEvent| {
223 let mut h = host.borrow_mut();
224 h.last_pointer = pe_position(&pe);
225 let world = h.view.screen_to_world(h.last_pointer);
226 h.player.pointer_down(world);
227 request_frame();
228 }
229 })
230 .on_pointer_up({
231 let host = host.clone();
232 move |pe: PointerEvent| {
233 let mut h = host.borrow_mut();
234 let world = h.view.screen_to_world(pe_position(&pe));
235 h.player.pointer_up(world);
236 request_frame();
237 }
238 })
239 .on_pointer_move({
240 let host = host.clone();
241 move |pe: PointerEvent| {
242 let mut h = host.borrow_mut();
243 h.last_pointer = pe_position(&pe);
244 let world = h.view.screen_to_world(h.last_pointer);
245 h.player.pointer_move(world);
246 request_frame();
247 }
248 })
249 .on_pointer_leave({
250 let host = host.clone();
251 move |_pe: PointerEvent| {
252 host.borrow_mut().player.pointer_leave();
253 request_frame();
254 }
255 }),
256 move |scope| {
257 let mut h = draw.borrow_mut();
258
259 if h.tick_playback() {
260 request_frame();
261 }
262
263 let surface = DVec2::new(scope.size.width as f64, scope.size.height as f64);
264 h.ensure_fit(surface);
265
266 if h.dirty_images {
267 let PlayerHost {
268 renderer, player, ..
269 } = &mut *h;
270 renderer.sync_document_images(&player.project.document, &render_context);
271 h.dirty_images = false;
272 }
273
274 paint_artboard(scope, h.artboard(), &h.view);
275
276 let scene = h.player.scene().clone();
277 let view = h.view;
278 let prepared = h.renderer.prepare(&scene, &view);
279 h.renderer.paint_prepared(&prepared, scope);
280 },
281 )
282}
283
284fn paint_artboard(scope: &mut DrawScope, artboard: DVec2, view: &ViewTransform) {
287 SceneRenderer::paint_artboard_chrome(scope, artboard, view);
288}