diff --git a/Cargo.lock b/Cargo.lock
index db4b8b7b..40293335 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2625,7 +2625,7 @@ dependencies = [
[[package]]
name = "playdate-sys"
-version = "0.2.6"
+version = "0.2.7"
dependencies = [
"arrayvec 0.7.4",
"playdate-bindgen",
@@ -2656,6 +2656,19 @@ dependencies = [
"usb-ids",
]
+[[package]]
+name = "playdate-ui-crank-indicator"
+version = "0.1.0"
+dependencies = [
+ "playdate-controls",
+ "playdate-display",
+ "playdate-graphics",
+ "playdate-menu",
+ "playdate-sprite",
+ "playdate-sys",
+ "playdate-system",
+]
+
[[package]]
name = "plist"
version = "1.5.0"
diff --git a/Cargo.toml b/Cargo.toml
index 9b33bdda..d2a46ff2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ members = [
"cargo",
"api/*",
"support/*",
- # "components/*"
+ "components/*"
]
default-members = ["cargo", "support/tool", "support/bindgen"]
exclude = ["cargo/tests/crates/**/*"]
diff --git a/README.md b/README.md
index 40a65b2f..0198e830 100644
--- a/README.md
+++ b/README.md
@@ -12,11 +12,17 @@ This project allows you to create games for the [Playdate handheld gaming system
* [Modular low- & high- level API][api-dir]
- with [examples][ctrl-examples-dir]
* __All the parts of API are accumulated in [One Crate][playdate-crate]__ ([git][playdate-crate-git])
+* UI components
+ - [crank-indicator][crank-indicator-gh] (port from [lua version][crank-indicator-lua]), requires SDK 2.1
Welcome to [discussions][] and [issues][] for any questions and suggestions.
Take a look at [videos](#demo) or [do something great](#usage).
+[crank-indicator-gh]: https://github.com/boozook/playdate/tree/main/components/crank-indicator
+[crank-indicator-lua]: https://sdk.play.date/Inside%20Playdate.html#C-ui.crankIndicator
+
+
## Prerequisites
Follow the instructions for:
diff --git a/api/sys/Cargo.toml b/api/sys/Cargo.toml
index fa7dc5e7..baf2d0a7 100644
--- a/api/sys/Cargo.toml
+++ b/api/sys/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "playdate-sys"
-version = "0.2.6"
+version = "0.2.7"
build = "src/build.rs"
readme = "README.md"
description = "Low-level Playdate API bindings"
diff --git a/components/crank-indicator/Cargo.toml b/components/crank-indicator/Cargo.toml
new file mode 100644
index 00000000..2d0560bd
--- /dev/null
+++ b/components/crank-indicator/Cargo.toml
@@ -0,0 +1,99 @@
+[package]
+name = "playdate-ui-crank-indicator"
+version = "0.1.0"
+readme = "README.md"
+description = "Crank Indicator UI component."
+edition.workspace = true
+license.workspace = true
+authors.workspace = true
+homepage.workspace = true
+repository.workspace = true
+
+
+[features]
+default = [
+ "sys/default",
+ "gfx/default",
+ "display/default",
+ "sprite/default",
+ "system/default",
+]
+
+# playdate-sys features, should be shared because it's build configuration:
+
+bindgen-runtime = [
+ "sys/bindgen-runtime",
+ "gfx/bindgen-runtime",
+ "display/bindgen-runtime",
+ "sprite/bindgen-runtime",
+ "system/bindgen-runtime",
+]
+bindgen-static = [
+ "sys/bindgen-static",
+ "gfx/bindgen-static",
+ "display/bindgen-static",
+ "sprite/bindgen-static",
+ "system/bindgen-static",
+]
+
+bindings-derive-debug = [
+ "sys/bindings-derive-debug",
+ "gfx/bindings-derive-debug",
+ "display/bindings-derive-debug",
+ "sprite/bindings-derive-debug",
+ "system/bindings-derive-debug",
+]
+
+
+[dependencies]
+sys = { workspace = true, default-features = false }
+system = { workspace = true, default-features = false }
+display = { workspace = true, default-features = false }
+# feature `sdk_2_1` not used really, it's just because linked assets exists only in SDK starting from 2.1.
+gfx = { workspace = true, default-features = false, features = ["sdk_2_1"] }
+sprite = { workspace = true, default-features = false, features = ["sdk_2_1"] }
+
+
+[dev-dependencies]
+ctrl = { workspace = true, default-features = false }
+menu = { workspace = true, default-features = false }
+
+
+[[example]]
+name = "example"
+crate-type = ["dylib", "staticlib"]
+path = "examples/example.rs"
+required-features = [
+ "sys/lang-items",
+ "sys/entry-point",
+ "sys/try-trait-v2",
+ "system/try-trait-v2",
+]
+
+
+[package.metadata.playdate]
+bundle-id = "rs.playdate.ui.crank.indicator"
+
+[package.metadata.playdate.assets]
+"ui/crank-ind/" = "${PLAYDATE_SDK_PATH}/CoreLibs/assets/crank/*.png"
+
+
+[package.metadata.docs.rs]
+all-features = false
+features = [
+ "sys/bindings-derive-default",
+ "sys/bindings-derive-eq",
+ "sys/bindings-derive-copy",
+ "bindings-derive-debug",
+ "sys/bindings-derive-hash",
+ "sys/bindings-derive-ord",
+ "sys/bindings-derive-partialeq",
+ "sys/bindings-derive-partialord",
+]
+rustdoc-args = ["--cfg", "docsrs", "--show-type-layout"]
+default-target = "thumbv7em-none-eabihf"
+cargo-args = [
+ "-Zunstable-options",
+ "-Zrustdoc-scrape-examples",
+ "-Zbuild-std=core,alloc",
+]
diff --git a/components/crank-indicator/README.md b/components/crank-indicator/README.md
new file mode 100644
index 00000000..273aa867
--- /dev/null
+++ b/components/crank-indicator/README.md
@@ -0,0 +1,30 @@
+# Playdate Crank-Indicator Alert
+
+Requires SDK 2.1.
+
+Optimized port of [official lua version][crank-indicator-lua], implemented as sprite.
+
+> Small system-styled indicator, alerting the player that this game will use the crank.
+
+
+
+See [examples][crank-indicator-examples] to learn how to use.
+```rust
+use playdate_ui_crank_indicator::CrankIndicator;
+use playdate_display::DisplayScale;
+use playdate_sprite::add_sprite;
+
+let crank = CrankIndicator::new(DisplayScale::Normal)?;
+add_sprite(&crank);
+```
+
+
+[crank-indicator-gh]: https://github.com/boozook/playdate/tree/main/components/crank-indicator
+[crank-indicator-examples]: https://github.com/boozook/playdate/tree/main/components/crank-indicator/examples
+[crank-indicator-lua]: https://sdk.play.date/Inside%20Playdate.html#C-ui.crankIndicator
+
+
+
+- - -
+
+This software is not sponsored or supported by Panic.
diff --git a/components/crank-indicator/examples/README.md b/components/crank-indicator/examples/README.md
new file mode 100644
index 00000000..62ce6ba0
--- /dev/null
+++ b/components/crank-indicator/examples/README.md
@@ -0,0 +1,25 @@
+# Examples
+
+
+There is one example that demonstrates `CrankIndicator` in various modes and environments.
+
+Use controls to change:
+- `<-` & `->` arrows to change global render offset
+- `^` & `⌄` arrows to change global render scale factor
+- use system menu to change framerate
+
+
+![example](https://github.com/boozook/playdate/assets/888526/70fe1d74-4cea-4fd4-ab2b-8e56d178c3b7)
+
+
+# How to run
+
+```bash
+cargo playdate run -p=playdate-ui-crank-indicator --example=example --features=sys/lang-items,sys/entry-point,sys/try-trait-v2,system/try-trait-v2
+```
+
+More information how to use [cargo-playdate][] in help: `cargo playdate --help`.
+
+
+
+[cargo-playdate]: https://crates.io/crates/cargo-playdate
diff --git a/components/crank-indicator/examples/example.rs b/components/crank-indicator/examples/example.rs
new file mode 100644
index 00000000..354aa59a
--- /dev/null
+++ b/components/crank-indicator/examples/example.rs
@@ -0,0 +1,149 @@
+#![no_std]
+#[macro_use]
+extern crate alloc;
+
+#[macro_use]
+extern crate sys;
+extern crate playdate_ui_crank_indicator as ui;
+
+use core::ffi::*;
+use core::ptr::NonNull;
+
+use sys::EventLoopCtrl;
+use sys::ffi::PlaydateAPI;
+use ctrl::buttons::PDButtonsExt;
+use display::DisplayScale;
+use menu::OptionsMenuItem;
+use ui::CrankIndicator;
+use system::prelude::*;
+
+
+/// App state
+struct State {
+ /// system draw offset
+ offset: (c_int, c_int),
+ /// system scale mode
+ scale: DisplayScale,
+ /// custom system menu
+ fps: OptionsMenuItem,
+
+ /// our neat indicator
+ crank: Option,
+
+ // cached endpoints
+ system: system::System,
+ display: display::Display,
+ gfx: gfx::Graphics,
+ btns: ctrl::peripherals::Buttons,
+}
+
+impl State {
+ fn new() -> Result {
+ fn fps_changed(value: &mut bool) { *value = true }
+
+ let fps = OptionsMenuItem::new("FPS", ["20", "50", "Inf"], Some(fps_changed), false).unwrap();
+
+ let crank = CrankIndicator::new(DisplayScale::Normal)?;
+ sprite::add_sprite(&crank);
+
+ Ok(Self { offset: (0, 0),
+ scale: DisplayScale::Normal,
+ crank: crank.into(),
+ fps,
+ gfx: gfx::Graphics::new(),
+ system: system::System::new(),
+ display: display::Display::new(),
+ btns: ctrl::peripherals::Buttons::new() })
+ }
+
+
+ /// Updates the state
+ fn update(&mut self) -> UpdateCtrl {
+ sprite::update_and_draw_sprites();
+
+ // save current scale to compare
+ let old_scale = self.scale;
+
+ // react to input
+ let buttons = self.btns.get();
+ if buttons.released.right() {
+ self.offset.0 += 6;
+ self.offset.1 += 2;
+ self.gfx.set_draw_offset(self.offset.0, self.offset.1);
+ } else if buttons.released.left() {
+ self.offset.0 -= 6;
+ self.offset.1 -= 2;
+ self.gfx.set_draw_offset(self.offset.0, self.offset.1);
+ } else if buttons.released.up() {
+ self.scale = match self.scale {
+ DisplayScale::Normal => DisplayScale::Double,
+ DisplayScale::Double => DisplayScale::Quad,
+ DisplayScale::Quad => DisplayScale::Eight,
+ DisplayScale::Eight => DisplayScale::Eight,
+ };
+ self.display.set_scale(self.scale);
+ } else if buttons.released.down() {
+ self.scale = match self.scale {
+ DisplayScale::Normal => DisplayScale::Normal,
+ DisplayScale::Double => DisplayScale::Normal,
+ DisplayScale::Quad => DisplayScale::Double,
+ DisplayScale::Eight => DisplayScale::Quad,
+ };
+ self.display.set_scale(self.scale);
+ }
+
+ // create or remove indicator
+ if buttons.released.a() {
+ self.crank.take();
+ let crank = CrankIndicator::new(self.scale)?;
+ sprite::add_sprite(&crank);
+ self.crank = crank.into();
+ } else if buttons.released.b() {
+ self.crank.take();
+ }
+
+ // update changed scale for indicator
+ if old_scale != self.scale {
+ self.crank.as_mut().map(|crank| crank.set_scale(self.scale));
+ }
+
+ // update frame rate if changed
+ self.fps.get_userdata().filter(|v| **v).map(|value| {
+ let fps = match self.fps.selected_option() {
+ 0 => 20.,
+ 1 => 50.,
+ _ => 0.,
+ };
+ self.display.set_refresh_rate(fps);
+ *value = false;
+ });
+
+ // draw state (offset, scale, fps)
+ self.gfx
+ .draw_text(format!("{}x, {:?}", self.scale, self.offset), 18, 0)?;
+ self.system.draw_fps(0, 0);
+
+ UpdateCtrl::Continue
+ }
+}
+
+
+#[no_mangle]
+fn event_handler(_: NonNull, event: SystemEvent, _: u32) -> EventLoopCtrl {
+ // Ignore any other events, just for this minimalistic example
+ if !matches!(event, SystemEvent::Init) {
+ return EventLoopCtrl::Continue;
+ }
+
+ let state = State::new()?;
+ state.display.set_refresh_rate(20.);
+
+ let system = state.system;
+ system.set_update_callback_boxed(State::update, state);
+
+ EventLoopCtrl::Continue
+}
+
+
+// Needed for debug build
+ll_symbols!();
diff --git a/components/crank-indicator/src/lib.rs b/components/crank-indicator/src/lib.rs
new file mode 100644
index 00000000..b66fe64b
--- /dev/null
+++ b/components/crank-indicator/src/lib.rs
@@ -0,0 +1,435 @@
+#![cfg_attr(not(test), no_std)]
+
+#[macro_use]
+extern crate alloc;
+extern crate sys;
+
+use core::ffi::c_int;
+use core::ffi::c_uint;
+
+use display::DisplayScale;
+use gfx::BitmapFlip;
+use gfx::BitmapFlipExt;
+use gfx::bitmap;
+use gfx::bitmap::Bitmap;
+use gfx::bitmap::table::BitmapTable;
+use sprite::Sprite;
+use sys::ffi::PDRect;
+use sys::traits::AsRaw;
+
+use sprite::AnySprite;
+use sprite::prelude::*;
+use sprite::callback::update::SpriteUpdate;
+use sprite::callback::update;
+use sprite::callback::draw::SpriteDraw;
+use sprite::callback::draw;
+
+
+const CRANK_FRAME_COUNT: c_int = 12;
+const TEXT_FRAME_COUNT: c_int = 14;
+
+type MySprite = Sprite;
+type UpdHandle = update::Handle;
+type DrwHandle = draw::l2::Handle;
+
+
+pub struct CrankIndicator {
+ sprite: DrwHandle,
+}
+
+impl CrankIndicator {
+ pub fn new(scale: DisplayScale) -> Result {
+ let state = State::new(scale)?;
+
+ let sprite = Sprite::<_, sprite::api::Default>::new().into_update_handler::();
+ sprite.set_ignores_draw_offset(true);
+ sprite.set_bounds(state.bounds());
+
+ sprite.set_userdata(state);
+ Ok(Self { sprite: sprite.into_draw_handler::() })
+ }
+
+
+ pub fn set_scale(&self, scale: DisplayScale) { self.sprite.userdata().map(|state| state.set_scale(scale)); }
+
+ pub fn set_offset(&self, x: i8, y: i8) {
+ self.sprite
+ .userdata()
+ .map(|state| state.set_offset(Point::new(x, y)));
+ }
+}
+
+
+impl AsRaw for CrankIndicator {
+ type Type = sys::ffi::LCDSprite;
+ unsafe fn as_raw(&self) -> *mut Self::Type { self.sprite.as_raw() }
+}
+impl SpriteApi for CrankIndicator {
+ type Api = sprite::api::Default;
+
+ fn api(&self) -> Self::Api
+ where Self::Api: Copy {
+ self.sprite.api()
+ }
+
+ fn api_ref(&self) -> &Self::Api { self.sprite.api_ref() }
+}
+impl AnySprite for CrankIndicator {}
+
+
+pub struct UpdateDraw(Sprite);
+
+impl SpriteUpdate for UpdateDraw where Self: From {
+ fn on_update(s: &update::Handle, Self>) {
+ if let Some(state) = s.userdata() {
+ if state.update() {
+ s.set_bounds(state.bounds());
+ s.mark_dirty();
+ } else {
+ // skip draw, not dirty
+ }
+ }
+ }
+}
+
+impl SpriteDraw for UpdateDraw where Self: From {
+ fn on_draw(s: &draw::Handle, Self>, bounds: PDRect, _: PDRect) {
+ if let Some(state) = s.userdata() {
+ let gfx = state.gfx;
+ gfx.draw(&state.bubble, bounds.x as _, bounds.y as _, state.bubble_flip);
+
+ const NORM: BitmapFlip = BitmapFlip::Unflipped;
+
+ if let Some(crank) = state.crank_current.as_ref() {
+ gfx.draw(&crank, state.crank_pos.x as _, state.crank_pos.y as _, NORM);
+ } else if let Some(text) = state.text.as_ref() {
+ gfx.draw(
+ &text,
+ state.text_position.x as _,
+ state.text_position.y as _,
+ NORM,
+ );
+ }
+ }
+ }
+}
+
+
+pub struct State {
+ // background
+ bubble: Bitmap,
+ bubble_pos: Point,
+ bubble_size: Size,
+ bubble_flip: BitmapFlip,
+
+ /// Crank animation frames
+ crank: BitmapTable,
+ /// Position of current frame of the crank for render
+ crank_pos: Point,
+ /// Current frame of the crank animation
+ crank_current: Option,
+
+ // frames of sequence
+ frame: c_int, // TODO: u8
+ frame_count: c_int, // TODO: u8
+
+ // text
+ text: Option>,
+ text_frame_count: c_int, // TODO: u8
+ text_offset: i16,
+ text_position: Point,
+
+ /// User set option clockwise
+ clockwise: bool,
+ /// User set option offset
+ offset: Point,
+ /// User set option scale
+ scale: DisplayScale,
+
+ /// Last draw moment
+ last_time: c_uint,
+ /// Need to reload bitmaps
+ dirty: bool,
+
+ // cached endpoints
+ system: system::System,
+ display: display::Display,
+ gfx: gfx::Graphics,
+}
+
+impl State {
+ fn new(scale: DisplayScale) -> Result {
+ let bubble = load_bubble_for_scale(scale)?;
+ let crank = load_crank_for_scale(scale)?;
+
+ let bubble_size = bubble.size();
+ let bubble_size = Size::new(bubble_size.0 as _, bubble_size.1 as _);
+
+ let mut this = Self { bubble,
+ bubble_pos: Point::new(0, 0),
+ bubble_size,
+ bubble_flip: BitmapFlip::Unflipped,
+ crank,
+ crank_current: None,
+ crank_pos: Point::new(0, 0),
+ frame: 1,
+ frame_count: CRANK_FRAME_COUNT as c_int * 3,
+ text: None,
+ text_frame_count: 0,
+ text_position: Point::new(0, 0),
+ text_offset: 0,
+ offset: Point::new(0, 0),
+ clockwise: true,
+ scale,
+ last_time: 0,
+ dirty: false,
+ system: system::System::new(),
+ display: display::Display::new(),
+ gfx: gfx::Graphics::new() };
+
+ this.load_text_if_needed()?;
+ this.calc_positions();
+
+ Ok(this)
+ }
+
+
+ fn calc_positions(&mut self) {
+ let crank_indicator_y = 210 / self.scale.as_u8();
+
+ if self.system.flipped() {
+ let y = self.display.height() as i16 - (crank_indicator_y - self.bubble_size.h / 2) as i16;
+ self.bubble_pos = Point::new(0, y);
+ self.bubble_flip = BitmapFlip::FlippedXY;
+ self.text_offset = 100 / self.scale.as_u8() as i16;
+ } else {
+ self.bubble_pos.x = self.display.width() as i16 - self.bubble_size.w as i16;
+ self.bubble_pos.y = crank_indicator_y as i16 - self.bubble_size.h as i16 / 2;
+ self.bubble_flip = BitmapFlip::Unflipped;
+ self.text_offset = 76 / self.scale.as_u8() as i16;
+ }
+
+ self.frame = 1;
+ self.frame_count = CRANK_FRAME_COUNT;
+
+ if let Some(text_frame_image) = &self.text {
+ self.text_frame_count = TEXT_FRAME_COUNT;
+ self.frame_count = CRANK_FRAME_COUNT + TEXT_FRAME_COUNT;
+
+ let x_offset = self.offset_correction_x();
+
+ let (tw, th) = text_frame_image.size();
+ let x = self.bubble_pos.x + x_offset + (self.text_offset - tw as i16) / 2;
+ let y = self.bubble_pos.y + self.offset.y as i16 + (self.bubble_size.h as i16 - th as i16) / 2;
+ self.text_position.x = x;
+ self.text_position.y = y;
+ } else {
+ self.text_frame_count = 0;
+ self.frame_count = CRANK_FRAME_COUNT;
+ }
+ }
+
+
+ fn load_text_if_needed(&mut self) -> Result<(), gfx::error::ApiError> {
+ if matches!(self.scale, DisplayScale::Normal | DisplayScale::Double) {
+ self.text = load_text_for_scale(self.scale)?.into();
+ } else {
+ self.text.take();
+ }
+ Ok(())
+ }
+
+
+ fn reload_bitmaps(&mut self) -> Result<(), gfx::error::ApiError> {
+ let bubble = load_bubble_for_scale(self.scale)?;
+ self.crank = load_crank_for_scale(self.scale)?;
+
+ let bubble_size = bubble.size();
+ self.bubble_size = Size::new(bubble_size.0 as _, bubble_size.1 as _);
+
+ self.bubble = bubble;
+
+ self.load_text_if_needed()?;
+
+ self.calc_positions();
+ self.dirty = false;
+
+ Ok(())
+ }
+
+ fn offset_correction_x(&self) -> i16 {
+ // if matches!(self.scale, DisplayScale::Double) {
+ // self.offset.x - 1
+ // } else {
+ // self.offset.x
+ // }
+
+ // this is better:
+ self.offset.x as i16
+ }
+
+ fn offset_correction_y(&self) -> i16 {
+ if matches!(self.scale, DisplayScale::Double | DisplayScale::Quad) {
+ self.offset.y as i16 + 1
+ } else {
+ self.offset.y as i16
+ }
+ }
+
+
+ fn set_scale(&mut self, scale: DisplayScale) {
+ self.scale = scale;
+ self.dirty = true;
+ }
+
+ fn set_offset(&mut self, offset: Point) {
+ self.offset = offset;
+ self.calc_positions();
+ }
+
+ fn update(&mut self) -> bool {
+ let mut dirty = self.dirty;
+ let last_frame = self.frame;
+ let crank_drawn = self.crank_current.is_some();
+
+
+ if self.dirty {
+ self.reload_bitmaps().ok();
+ }
+
+
+ let current_time = self.system.current_time_milliseconds_raw();
+ let mut delta = current_time - self.last_time;
+
+
+ // reset to start frame if `draw` hasn't been called in more than a second
+ if delta > 1000 {
+ self.frame = 1;
+ }
+
+ // normalized steps by delta
+ while delta >= 50 {
+ self.last_time += 50;
+ delta -= 50;
+ self.frame += 1;
+ if self.frame > self.frame_count {
+ self.frame = 1;
+ }
+ }
+
+ // prepare next frame of the crank
+ if self.scale.as_u8() > 2 || self.frame > self.text_frame_count {
+ let index = if self.clockwise {
+ ((self.frame - self.text_frame_count - 1) % CRANK_FRAME_COUNT) + 1
+ } else {
+ ((CRANK_FRAME_COUNT - (self.frame - self.text_frame_count - 1)) % CRANK_FRAME_COUNT) + 1
+ } - 1;
+
+ if index < 0 {
+ self.crank_current = None;
+ return true;
+ }
+
+ if dirty || self.frame != last_frame {
+ dirty = true;
+
+ let frame = self.crank
+ .get::(index)
+ .expect("missed frame");
+ let (fw, fh) = frame.size();
+
+ let x = self.bubble_pos.x + self.offset.x as i16 + (self.text_offset - fw as i16) / 2;
+ let y = self.bubble_pos.y + self.offset_correction_y() + (self.bubble_size.h as i16 - fh as i16) / 2;
+ self.crank_pos = Point::new(x, y);
+ self.crank_current = frame.into();
+ }
+ } else {
+ self.crank_current = None;
+ }
+
+ // is dirty:
+ // 0. if bitmaps just reloaded,
+ // 1. if frame changed,
+ // 2. if self.crank_current was None, but now is Some, and otherwise.
+ dirty || (crank_drawn != self.crank_current.is_some())
+ }
+
+
+ fn bounds(&self) -> PDRect {
+ PDRect { x: (self.bubble_pos.x + self.offset.x as i16) as _,
+ y: (self.bubble_pos.y + self.offset.y as i16) as _,
+ width: self.bubble_size.w as _,
+ height: self.bubble_size.h as _ }
+ }
+}
+
+
+fn load_bubble_for_scale(scale: DisplayScale) -> Result, gfx::error::ApiError> {
+ let path = format!("ui/crank-ind/crank-notice-bubble-{}x", scale.as_u8());
+ Bitmap::load(path)
+}
+
+fn load_text_for_scale(scale: DisplayScale) -> Result, gfx::error::ApiError> {
+ let path = format!("ui/crank-ind/crank-notice-text-{}x", scale.as_u8());
+ Bitmap::load(path)
+}
+
+fn load_crank_for_scale(scale: DisplayScale)
+ -> Result, gfx::error::ApiError> {
+ let path = format!("ui/crank-ind/crank-frames-{}x", scale.as_u8());
+ BitmapTable::load(path)
+}
+
+
+/// 2D point
+struct Point {
+ x: T,
+ y: T,
+}
+
+impl Point {
+ const fn new(x: T, y: T) -> Point { Self { x, y } }
+}
+
+/// 2D size
+struct Size {
+ w: T,
+ h: T,
+}
+
+impl Size {
+ const fn new(w: T, h: T) -> Size { Self { w, h } }
+}
+
+
+mod impls_combine_handlers {
+ use sys::traits::AsRaw;
+ use super::*;
+
+ impl AsRef> for UpdateDraw {
+ fn as_ref(&self) -> &Sprite { &self.0 }
+ }
+
+ impl From for UpdateDraw where Sprite: From {
+ fn from(ptr: T) -> Self { Self(Sprite::from(ptr)) }
+ }
+
+ impl TypedSprite for UpdateDraw {
+ type Userdata = State;
+ const FREE_ON_DROP: bool = false;
+ }
+ impl AsRaw for UpdateDraw {
+ type Type = ::Type;
+ unsafe fn as_raw(&self) -> *mut Self::Type { self.0.as_raw() }
+ }
+ impl SpriteApi for UpdateDraw {
+ type Api = ::Api;
+
+ fn api(&self) -> Self::Api
+ where Self::Api: Copy {
+ self.0.api()
+ }
+
+ fn api_ref(&self) -> &Self::Api { self.0.api_ref() }
+ }
+}