playdate_ui_crank_indicator/
lib.rs1#![cfg_attr(not(test), no_std)]
2
3#[macro_use]
4extern crate alloc;
5extern crate sys;
6
7use core::ffi::c_uint;
8use core::marker::PhantomData;
9
10use display::DisplayScale;
11use gfx::BitmapFlip;
12use gfx::BitmapFlipExt;
13use gfx::bitmap;
14use gfx::bitmap::Bitmap;
15use gfx::bitmap::table::BitmapTable;
16use sprite::Sprite;
17use sprite::SpriteType;
18use sys::ffi::PDRect;
19use sys::traits::AsRaw;
20
21use sprite::AnySprite;
22use sprite::prelude::*;
23use sprite::callback::update::SpriteUpdate;
24use sprite::callback::update;
25use sprite::callback::draw::SpriteDraw;
26use sprite::callback::draw;
27
28
29const CRANK_FRAME_COUNT: u8 = 12;
30const TEXT_FRAME_COUNT: u8 = 14;
31
32type MySprite = Sprite<State, sprite::api::Default>;
33type UpdHandle = update::Handle<true, MySprite, UpdateDraw>;
34type DrwHandle = draw::l2::Handle<true, MySprite, UpdHandle, UpdateDraw>;
35
36
37pub struct CrankIndicator {
38 sprite: DrwHandle,
39}
40
41impl CrankIndicator {
42 pub fn new(scale: DisplayScale) -> Result<Self, gfx::error::ApiError> {
43 let state = State::new(scale)?;
44
45 let sprite = Sprite::<_, sprite::api::Default>::new().into_update_handler::<UpdateDraw>();
46 sprite.set_ignores_draw_offset(true);
47 sprite.set_bounds(state.bounds());
48
49 sprite.set_userdata(state);
50 Ok(Self { sprite: sprite.into_draw_handler::<UpdateDraw>() })
51 }
52
53
54 pub fn set_scale(&self, scale: DisplayScale) { self.sprite.userdata().map(|state| state.set_scale(scale)); }
55
56 pub fn set_offset(&self, x: i8, y: i8) {
57 self.sprite
58 .userdata()
59 .map(|state| state.set_offset(Point::new(x, y)));
60 }
61}
62
63
64impl AsRaw for CrankIndicator {
65 type Type = sys::ffi::LCDSprite;
66 unsafe fn as_raw(&self) -> *mut Self::Type { self.sprite.as_raw() }
67}
68impl SpriteApi for CrankIndicator {
69 type Api = sprite::api::Default;
70
71 fn api(&self) -> Self::Api
72 where Self::Api: Copy {
73 self.sprite.api()
74 }
75
76 fn api_ref(&self) -> &Self::Api { self.sprite.api_ref() }
77}
78impl AnySprite for CrankIndicator {}
79
80impl SpriteType for CrankIndicator {
81 type Api = <Self as SpriteApi>::Api;
82 type Userdata = <UpdateDraw as SpriteType>::Userdata;
83}
84
85
86pub struct UpdateDraw<T: AnySprite = SpriteRef>(PhantomData<T>);
87
88impl<T: AnySprite> SpriteType for UpdateDraw<T> {
89 type Api = <T as SpriteApi>::Api;
90 type Userdata = State;
91 const FREE_ON_DROP: bool = false;
92}
93
94impl<T: AnySprite> SpriteUpdate for UpdateDraw<T> {
95 #[inline(always)]
96 fn on_update(s: &update::Handle<false, SharedSprite<Self::Userdata, Self::Api>, Self>) {
97 if let Some(state) = s.userdata() {
98 if state.update() {
99 s.set_bounds(state.bounds());
100 s.mark_dirty();
101 } else {
102 }
104 }
105 }
106}
107
108impl<T: AnySprite> SpriteDraw for UpdateDraw<T> {
109 #[inline(always)]
110 fn on_draw(s: &draw::Handle<false, SharedSprite<Self::Userdata, Self::Api>, Self>, bounds: PDRect, _: PDRect) {
111 if let Some(state) = s.userdata() {
112 let gfx = state.gfx;
113 gfx.draw(&state.bubble, bounds.x as _, bounds.y as _, state.bubble_flip);
114
115 const NORM: BitmapFlip = BitmapFlip::Unflipped;
116
117 if let Some(crank) = state.crank_current.as_ref() {
118 gfx.draw(&crank, state.crank_pos.x as _, state.crank_pos.y as _, NORM);
119 } else if let Some(text) = state.text.as_ref() {
120 gfx.draw(
121 &text,
122 state.text_position.x as _,
123 state.text_position.y as _,
124 NORM,
125 );
126 }
127 }
128 }
129}
130
131
132pub struct State {
133 bubble: Bitmap<gfx::api::Default>,
135 bubble_pos: Point<i16>,
136 bubble_size: Size<u8>,
137 bubble_flip: BitmapFlip,
138
139 crank: BitmapTable<gfx::api::Default>,
141 crank_pos: Point<i16>,
143 crank_current: Option<Bitmap>,
145
146 frame: u8,
148 frame_count: u8,
149
150 text: Option<Bitmap<gfx::api::Default>>,
152 text_frame_count: u8,
153 text_offset: i16,
154 text_position: Point<i16>,
155
156 clockwise: bool,
158 offset: Point<i8>,
160 scale: DisplayScale,
162
163 last_time: c_uint,
165 dirty: bool,
167
168 system: system::System<system::api::Cache>,
170 display: display::Display<display::api::Cache>,
171 gfx: gfx::Graphics<gfx::api::Cache>,
172}
173
174impl State {
175 fn new(scale: DisplayScale) -> Result<Self, gfx::error::ApiError> {
176 let bubble = load_bubble_for_scale(scale)?;
177 let crank = load_crank_for_scale(scale)?;
178
179 let bubble_size = bubble.size();
180 let bubble_size = Size::new(bubble_size.0 as _, bubble_size.1 as _);
181
182 let mut this = Self { bubble,
183 bubble_pos: Point::new(0, 0),
184 bubble_size,
185 bubble_flip: BitmapFlip::Unflipped,
186 crank,
187 crank_current: None,
188 crank_pos: Point::new(0, 0),
189 frame: 1,
190 frame_count: CRANK_FRAME_COUNT * 3,
191 text: None,
192 text_frame_count: 0,
193 text_position: Point::new(0, 0),
194 text_offset: 0,
195 offset: Point::new(0, 0),
196 clockwise: true,
197 scale,
198 last_time: 0,
199 dirty: false,
200 system: system::System::new(),
201 display: display::Display::new(),
202 gfx: gfx::Graphics::new() };
203
204 this.load_text_if_needed()?;
205 this.calc_positions();
206
207 Ok(this)
208 }
209
210
211 fn calc_positions(&mut self) {
212 let crank_indicator_y = 210 / self.scale.as_u8();
213
214 if self.system.flipped() {
215 let y = self.display.height() as i16 - (crank_indicator_y - self.bubble_size.h / 2) as i16;
216 self.bubble_pos = Point::new(0, y);
217 self.bubble_flip = BitmapFlip::FlippedXY;
218 self.text_offset = 100 / self.scale.as_u8() as i16;
219 } else {
220 self.bubble_pos.x = self.display.width() as i16 - self.bubble_size.w as i16;
221 self.bubble_pos.y = crank_indicator_y as i16 - self.bubble_size.h as i16 / 2;
222 self.bubble_flip = BitmapFlip::Unflipped;
223 self.text_offset = 76 / self.scale.as_u8() as i16;
224 }
225
226 self.frame = 1;
227 self.frame_count = CRANK_FRAME_COUNT;
228
229 if let Some(text_frame_image) = &self.text {
230 self.text_frame_count = TEXT_FRAME_COUNT;
231 self.frame_count = CRANK_FRAME_COUNT + TEXT_FRAME_COUNT;
232
233 let x_offset = self.offset_correction_x();
234
235 let (tw, th) = text_frame_image.size();
236 let x = self.bubble_pos.x + x_offset + (self.text_offset - tw as i16) / 2;
237 let y = self.bubble_pos.y + self.offset.y as i16 + (self.bubble_size.h as i16 - th as i16) / 2;
238 self.text_position.x = x;
239 self.text_position.y = y;
240 } else {
241 self.text_frame_count = 0;
242 self.frame_count = CRANK_FRAME_COUNT;
243 }
244 }
245
246
247 fn load_text_if_needed(&mut self) -> Result<(), gfx::error::ApiError> {
248 if matches!(self.scale, DisplayScale::Normal | DisplayScale::Double) {
249 self.text = load_text_for_scale(self.scale)?.into();
250 } else {
251 self.text.take();
252 }
253 Ok(())
254 }
255
256
257 fn reload_bitmaps(&mut self) -> Result<(), gfx::error::ApiError> {
258 let bubble = load_bubble_for_scale(self.scale)?;
259 self.crank = load_crank_for_scale(self.scale)?;
260
261 let bubble_size = bubble.size();
262 self.bubble_size = Size::new(bubble_size.0 as _, bubble_size.1 as _);
263
264 self.bubble = bubble;
265
266 self.load_text_if_needed()?;
267
268 self.calc_positions();
269 self.dirty = false;
270
271 Ok(())
272 }
273
274 fn offset_correction_x(&self) -> i16 {
275 self.offset.x as i16
283 }
284
285 fn offset_correction_y(&self) -> i16 {
286 if matches!(self.scale, DisplayScale::Double | DisplayScale::Quad) {
287 self.offset.y as i16 + 1
288 } else {
289 self.offset.y as i16
290 }
291 }
292
293
294 fn set_scale(&mut self, scale: DisplayScale) {
295 self.scale = scale;
296 self.dirty = true;
297 }
298
299 fn set_offset(&mut self, offset: Point<i8>) {
300 self.offset = offset;
301 self.calc_positions();
302 }
303
304 fn update(&mut self) -> bool {
305 let mut dirty = self.dirty;
306 let last_frame = self.frame;
307 let crank_drawn = self.crank_current.is_some();
308
309
310 if self.dirty {
311 self.reload_bitmaps().ok();
312 }
313
314
315 let current_time = self.system.current_time_ms();
316 let mut delta = current_time - self.last_time;
317
318
319 if delta > 1000 {
321 self.frame = 1;
322 }
323
324 while delta >= 50 {
326 self.last_time += 50;
327 delta -= 50;
328 self.frame += 1;
329 if self.frame > self.frame_count {
330 self.frame = 1;
331 }
332 }
333
334 if self.scale.as_u8() > 2 || self.frame > self.text_frame_count {
336 let index = if self.clockwise {
337 ((self.frame - self.text_frame_count - 1) % CRANK_FRAME_COUNT) + 1
338 } else {
339 ((CRANK_FRAME_COUNT - (self.frame - self.text_frame_count - 1)) % CRANK_FRAME_COUNT) + 1
340 } - 1;
341
342 if dirty || self.frame != last_frame {
343 dirty = true;
344
345 let frame = self.crank
346 .get::<bitmap::api::Default>(index as _)
347 .expect("missed frame");
348 let (fw, fh) = frame.size();
349
350 let x = self.bubble_pos.x + self.offset.x as i16 + (self.text_offset - fw as i16) / 2;
351 let y = self.bubble_pos.y + self.offset_correction_y() + (self.bubble_size.h as i16 - fh as i16) / 2;
352 self.crank_pos = Point::new(x, y);
353 self.crank_current = frame.into();
354 }
355 } else {
356 self.crank_current = None;
357 }
358
359 dirty || (crank_drawn != self.crank_current.is_some())
364 }
365
366
367 fn bounds(&self) -> PDRect {
368 PDRect { x: (self.bubble_pos.x + self.offset.x as i16) as _,
369 y: (self.bubble_pos.y + self.offset.y as i16) as _,
370 width: self.bubble_size.w as _,
371 height: self.bubble_size.h as _ }
372 }
373}
374
375
376fn load_bubble_for_scale(scale: DisplayScale) -> Result<Bitmap<gfx::api::Default>, gfx::error::ApiError> {
377 let path = format!("ui/crank-ind/crank-notice-bubble-{}x", scale.as_u8());
378 Bitmap::load(path)
379}
380
381fn load_text_for_scale(scale: DisplayScale) -> Result<Bitmap<gfx::api::Default>, gfx::error::ApiError> {
382 let path = format!("ui/crank-ind/crank-notice-text-{}x", scale.as_u8());
383 Bitmap::load(path)
384}
385
386fn load_crank_for_scale(scale: DisplayScale)
387 -> Result<BitmapTable<gfx::bitmap::table::api::Default>, gfx::error::ApiError> {
388 let path = format!("ui/crank-ind/crank-frames-{}x", scale.as_u8());
389 BitmapTable::load(path)
390}
391
392
393struct Point<T> {
395 x: T,
396 y: T,
397}
398
399impl<T> Point<T> {
400 const fn new(x: T, y: T) -> Point<T> { Self { x, y } }
401}
402
403struct Size<T> {
405 w: T,
406 h: T,
407}
408
409impl<T> Size<T> {
410 const fn new(w: T, h: T) -> Size<T> { Self { w, h } }
411}