diff --git a/Cargo.lock b/Cargo.lock index 0d20f8d..86b737a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,7 +623,7 @@ dependencies = [ [[package]] name = "figures" version = "0.1.0" -source = "git+https://github.com/khonsulabs/figures#1809e94c19e56aaf9df36d7553ffa9e65523369d" +source = "git+https://github.com/khonsulabs/figures#312f390a33025c902e94124cfab10c3dafdfb5f1" dependencies = [ "bytemuck", "euclid", @@ -968,9 +968,9 @@ dependencies = [ [[package]] name = "intentional" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509e910bdc87b3c71b1085ae9659c11ea74f9667f1e15b54a6fe68a307600560" +checksum = "bbc48117ac1523428c576e39831e93043112e2d2be0223bb0c0593af944e4e38" [[package]] name = "interner" @@ -1030,6 +1030,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "justjson" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7e253b574775d0ebd7975c471fc18f72f0775a4d42b563b5fbc3c4068aa1075" + [[package]] name = "kempt" version = "0.2.2" @@ -1056,7 +1062,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kludgine" version = "0.1.0" -source = "git+https://github.com/khonsulabs/kludgine#abb5625c3557c3c3b22e088fe1523b1544e7ddb7" +source = "git+https://github.com/khonsulabs/kludgine#924198daa5fc9fe758efd7c56084f8a05ef783d1" dependencies = [ "ahash", "alot", @@ -1067,6 +1073,7 @@ dependencies = [ "figures", "image", "intentional", + "justjson", "lyon_tessellation", "pollster", "smallvec", @@ -1935,9 +1942,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.26" +version = "0.38.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" +checksum = "bfeae074e687625746172d639330f1de242a178bf3189b51e35a7a21573513ac" dependencies = [ "bitflags 2.4.1", "errno", @@ -3127,18 +3134,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" [[package]] name = "zerocopy" -version = "0.7.29" +version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d075cf85bbb114e933343e087b92f2146bac0d55b534cbb8188becf0039948e" +checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.29" +version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86cd5ca076997b97ef09d3ad65efe811fa68c9e874cb636ccb211223a813b0c2" +checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" dependencies = [ "proc-macro2", "quote", diff --git a/examples/assets/ferris-happy.png b/examples/assets/ferris-happy.png new file mode 100644 index 0000000..8741baa Binary files /dev/null and b/examples/assets/ferris-happy.png differ diff --git a/examples/assets/grass.aseprite b/examples/assets/grass.aseprite new file mode 100644 index 0000000..8ed73b7 Binary files /dev/null and b/examples/assets/grass.aseprite differ diff --git a/examples/assets/grass.json b/examples/assets/grass.json new file mode 100644 index 0000000..1c6b33b --- /dev/null +++ b/examples/assets/grass.json @@ -0,0 +1,45 @@ +{ "frames": { + "Swaying_0.aseprite": { + "frame": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 300 + }, + "Swaying_1.aseprite": { + "frame": { "x": 32, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 500 + }, + "Still_2.aseprite": { + "frame": { "x": 64, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 500 + } + }, + "meta": { + "app": "http://www.aseprite.org/", + "version": "1.2.17", + "image": "grass.png", + "format": "RGBA8888", + "size": { "w": 96, "h": 32 }, + "scale": "1", + "frameTags": [ + { "name": "Swaying", "from": 0, "to": 1, "direction": "forward" }, + { "name": "Still", "from": 2, "to": 2, "direction": "forward" } + ], + "layers": [ + { "name": "Layer 1", "opacity": 255, "blendMode": "normal" }, + { "name": "Layer 2", "opacity": 255, "blendMode": "normal" } + ], + "slices": [ + ] + } +} diff --git a/examples/assets/grass.png b/examples/assets/grass.png new file mode 100644 index 0000000..fab08f6 Binary files /dev/null and b/examples/assets/grass.png differ diff --git a/examples/assets/isometric_tile.png b/examples/assets/isometric_tile.png new file mode 100644 index 0000000..66311af Binary files /dev/null and b/examples/assets/isometric_tile.png differ diff --git a/examples/assets/k.png b/examples/assets/k.png new file mode 100644 index 0000000..c1eda6c Binary files /dev/null and b/examples/assets/k.png differ diff --git a/examples/assets/stickguy.aseprite b/examples/assets/stickguy.aseprite new file mode 100644 index 0000000..4919ff5 Binary files /dev/null and b/examples/assets/stickguy.aseprite differ diff --git a/examples/assets/stickguy.json b/examples/assets/stickguy.json new file mode 100644 index 0000000..ce24da2 --- /dev/null +++ b/examples/assets/stickguy.json @@ -0,0 +1,99 @@ +{ + "frames": { + "Idle_0.aseprite": { + "frame": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 500 + }, + "Idle_1.aseprite": { + "frame": { "x": 32, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 500 + }, + "Idle_2.aseprite": { + "frame": { "x": 64, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 500 + }, + "Idle_3.aseprite": { + "frame": { "x": 96, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 500 + }, + "WalkRight_4.aseprite": { + "frame": { "x": 128, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 200 + }, + "WalkRight_5.aseprite": { + "frame": { "x": 160, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 200 + }, + "WalkRight_6.aseprite": { + "frame": { "x": 192, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 200 + }, + "WalkLeft_7.aseprite": { + "frame": { "x": 224, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 200 + }, + "WalkLeft_8.aseprite": { + "frame": { "x": 256, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 200 + }, + "WalkLeft_9.aseprite": { + "frame": { "x": 288, "y": 0, "w": 32, "h": 32 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, + "sourceSize": { "w": 32, "h": 32 }, + "duration": 200 + } + }, + "meta": { + "app": "http://www.aseprite.org/", + "version": "1.2.17", + "image": "stickguy.png", + "format": "RGBA8888", + "size": { "w": 320, "h": 32 }, + "scale": "1", + "frameTags": [ + { "name": "Idle", "from": 0, "to": 3, "direction": "forward" }, + { "name": "WalkRight", "from": 4, "to": 6, "direction": "pingpong" }, + { "name": "WalkLeft", "from": 7, "to": 9, "direction": "pingpong" } + ], + "layers": [{ "name": "Flattened", "opacity": 255, "blendMode": "normal" }], + "slices": [] + } +} diff --git a/examples/assets/stickguy.png b/examples/assets/stickguy.png new file mode 100644 index 0000000..0f4dc81 Binary files /dev/null and b/examples/assets/stickguy.png differ diff --git a/examples/tilemap.rs b/examples/tilemap.rs index a344d89..4fcb31f 100644 --- a/examples/tilemap.rs +++ b/examples/tilemap.rs @@ -1,3 +1,7 @@ +use std::array; +use std::cmp::Ordering; +use std::time::Duration; + use gooey::kludgine::app::winit::keyboard::Key; use gooey::kludgine::figures::units::Px; use gooey::kludgine::figures::{Point, Rect, Size}; @@ -6,42 +10,38 @@ use gooey::kludgine::shapes::Shape; use gooey::kludgine::tilemap::{Object, ObjectLayer, TileKind, TileMapFocus, Tiles, TILE_SIZE}; use gooey::kludgine::Color; use gooey::value::Dynamic; -use gooey::widget::MakeWidget; -use gooey::widgets::{Label, Stack, TileMap}; +use gooey::widgets::TileMap; use gooey::{Run, Tick}; use kludgine::app::winit::keyboard::NamedKey; use kludgine::figures::FloatConversion; -use kludgine::DrawableExt; +use kludgine::sprite::{Sprite, SpriteSource}; +use kludgine::{include_aseprite_sprite, DrawableExt}; const PLAYER_SIZE: Px = Px::new(16); -#[rustfmt::skip] -const TILES: [TileKind; 64] = { - const O: TileKind = TileKind::Color(Color::PURPLE); - const X: TileKind = TileKind::Color(Color::WHITE); - [ - X, X, X, X, X, X, X, X, - X, O, O, O, O, O, O, X, - X, O, X, O, O, X, O, X, - X, O, O, O, O, O, O, X, - X, O, X, O, O, X, O, X, - X, O, O, X, X, O, O, X, - X, O, O, O, O, O, O, X, - X, X, X, X, X, X, X, X, - ] -}; - fn main() -> gooey::Result { let mut characters = ObjectLayer::new(); + let mut sprite = include_aseprite_sprite!("assets/stickguy").unwrap(); + sprite.set_current_tag(Some("Idle")).unwrap(); + let myself = characters.push(Player { - color: Color::RED, + sprite, + current_frame: None, + hovered: false, position: Point::new(TILE_SIZE.into_float(), TILE_SIZE.into_float()), }); - let layers = Dynamic::new((Tiles::new(8, 8, TILES), characters)); + let sprite = include_aseprite_sprite!("assets/grass").unwrap(); - let debug_message = Dynamic::new(String::new()); + let layers = Dynamic::new(( + Tiles::new( + 8, + 8, + array::from_fn::<_, 64, _>(|_| TileKind::Sprite(sprite.clone())), + ), + characters, + )); let tilemap = TileMap::dynamic(layers.clone()) .focus_on(TileMapFocus::Object { @@ -70,24 +70,37 @@ fn main() -> gooey::Result { let cursor_pos = input.mouse.as_ref().map(|mouse| mouse.position); layers.map_mut(|layers| { - let pos = &mut layers.1[myself].position; - *pos += one_second_movement.x * elapsed.as_secs_f32(); + let player = &mut layers.1[myself]; - let rect = Rect::new(*pos - Size::squared(8.), Size::squared(16.)); - layers.1[myself].color = - match cursor_pos.map_or(false, |cursor_pos| rect.cast().contains(cursor_pos)) { - true => Color::RED, - false => Color::BLUE, - }; + let animation_tag = match direction.x.total_cmp(&0.) { + Ordering::Less => "WalkLeft", + Ordering::Equal => "Idle", + Ordering::Greater => "WalkRight", + }; + player + .sprite + .set_current_tag(Some(animation_tag)) + .expect("valid tag"); + + player.current_frame = + Some(player.sprite.get_frame(Some(elapsed)).expect("valid tag")); + + player.position += one_second_movement * elapsed.as_secs_f32(); + + let rect = Rect::new(player.position - Size::squared(8.), Size::squared(16.)); + layers.1[myself].hovered = + cursor_pos.map_or(false, |cursor_pos| rect.cast().contains(cursor_pos)); }); })); - Stack::rows(tilemap.expand().and(Label::new(debug_message))).run() + tilemap.run() } #[derive(Debug)] struct Player { - color: Color, + sprite: Sprite, + current_frame: Option, + hovered: bool, position: Point, } @@ -96,14 +109,30 @@ impl Object for Player { self.position.cast() } - fn render(&self, center: Point, zoom: f32, context: &mut Renderer<'_, '_>) { + fn render( + &self, + center: Point, + zoom: f32, + context: &mut Renderer<'_, '_>, + ) -> Option { let zoomed_size = PLAYER_SIZE * zoom; - context.draw_shape( - Shape::filled_rect( - Rect::new(Point::squared(-zoomed_size / 2), Size::squared(zoomed_size)), - self.color, - ) - .translate_by(center), - ) + if self.hovered { + context.draw_shape( + Shape::filled_rect( + Rect::new(Point::squared(-zoomed_size / 2), Size::squared(zoomed_size)), + Color::new(255, 255, 255, 80), + ) + .translate_by(center), + ); + } + + if let Some(frame) = &self.current_frame { + context.draw_texture( + frame, + Rect::new(center - zoomed_size / 2, Size::squared(zoomed_size)), + ); + } + + self.sprite.remaining_frame_duration().ok().flatten() } } diff --git a/src/widgets/tilemap.rs b/src/widgets/tilemap.rs index 3a75e72..6e53c65 100644 --- a/src/widgets/tilemap.rs +++ b/src/widgets/tilemap.rs @@ -70,14 +70,37 @@ where fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { let focus = self.focus.get(); // TODO this needs to be updated to support being placed in side of a scroll view. - self.layers.map(|layers| { - tilemap::draw(layers, focus, self.zoom, context.gfx.inner_graphics()); - }); + let redraw_after = match &mut self.layers { + Value::Constant(layers) => tilemap::draw( + layers, + focus, + self.zoom, + context.elapsed(), + context.gfx.inner_graphics(), + ), + Value::Dynamic(layers) => { + let mut layers = layers.lock(); + layers.prevent_notifications(); + tilemap::draw( + &mut *layers, + focus, + self.zoom, + context.elapsed(), + context.gfx.inner_graphics(), + ) + } + }; + context.draw_focus_ring(); if let Some(tick) = &self.tick { + // When we are driven by a tick, we ignore all other sources of + // refreshes. tick.rendered(context); } else { + if let Some(redraw_after) = redraw_after { + context.redraw_in(redraw_after); + } self.focus.redraw_when_changed(context); self.layers.redraw_when_changed(context); } @@ -168,10 +191,11 @@ where _location: Point, _device_id: DeviceId, button: kludgine::app::winit::event::MouseButton, - _context: &mut EventContext<'_, '_>, + context: &mut EventContext<'_, '_>, ) -> EventHandling { if let Some(tick) = &self.tick { tick.mouse_button(button, ElementState::Pressed); + context.focus(); HANDLED } else { IGNORED