From df730047ac0fe5ab67ae59479a40f556bbe36da0 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Mon, 2 Feb 2026 00:02:10 -0500 Subject: [PATCH] Animator tool can now play, stop, loop, and toggle an animation. --- tests/animate2.cpp | 8 +- tools/animator.cpp | 284 +++++++++++++++++++++++---------------------- tools/animator.hpp | 50 ++++++++ 3 files changed, 200 insertions(+), 142 deletions(-) create mode 100644 tools/animator.hpp diff --git a/tests/animate2.cpp b/tests/animate2.cpp index 3314506..727fb5a 100644 --- a/tests/animate2.cpp +++ b/tests/animate2.cpp @@ -38,7 +38,7 @@ Animate2 crafter() { .max_y{0.8f}, .simple{false}, .flipped{false}, - .ease_rate{0.5f}, + .ease_rate{5.0f}, .scaled{true}, .toggled{false}, .looped{false}, @@ -159,7 +159,8 @@ TEST_CASE("confirm transition changes work", "[animation-new]") { animation::init(); auto sprite = *textures::get_sprite("rat_king_boss").sprite; - auto pos = sprite.getPosition(); + sf::Vector2f pos{100,100}; + sprite.setPosition(pos); auto scale = sprite.getScale(); auto anim = crafter(); @@ -167,6 +168,7 @@ TEST_CASE("confirm transition changes work", "[animation-new]") { REQUIRE(anim.onFrame == nullptr); anim.play(); + REQUIRE(anim.playing == true); sf::Clock clock; clock.start(); @@ -188,7 +190,7 @@ TEST_CASE("confirm transition changes work", "[animation-new]") { } REQUIRE(anim.playing == false); - REQUIRE(pos != sf::Vector2f{0,0}); + REQUIRE(pos == sf::Vector2f{100, 100}); REQUIRE(scale != sf::Vector2f{0,0}); } diff --git a/tools/animator.cpp b/tools/animator.cpp index 29a6d44..7df5f78 100644 --- a/tools/animator.cpp +++ b/tools/animator.cpp @@ -5,149 +5,14 @@ #include #include "shaders.hpp" #include "backend.hpp" -#include "events.hpp" #include "constants.hpp" -#include "gui/event_router.hpp" -#include "constants.hpp" -#include -#include "gui/event_router.hpp" -#include "gui/guecstra.hpp" #include "animate2.hpp" +#include "tools/animator.hpp" using namespace std::chrono_literals; bool YES_SYNC=true; -namespace animator { - struct UI { - guecs::UI $ui; - - void init(const std::string& sprite_name) { - $ui.position(0,0, SCREEN_WIDTH, SCREEN_HEIGHT); - $ui.layout( - "[play|*%=(300,400)viewer|_|_]" - "[stop|_|_|_]" - "[next|_|_|_]" - "[prev|_|_|_]"); - - for(auto& [name, cell] : $ui.cells()) { - auto comp = $ui.entity(name); - if(name == "viewer") { - $ui.set(comp, { - sprite_name, 0, false}); - } else { - $ui.set(comp, {}); - $ui.set(comp, {guecs::to_wstring(name)}); - $ui.set(comp, {}); - $ui.set(comp, {[](auto){ - fmt::println("I don't know what to do here."); - }}); - } - } - - $ui.init(); - } - - void render(sf::RenderWindow& window) { - $ui.render(window); - } - - bool mouse(float x, float y, guecs::Modifiers mods) { - return $ui.mouse(x, y, mods); - } - - std::shared_ptr get_sprite() { - auto viewer = $ui.entity("viewer"); - return $ui.get(viewer).sprite; - } - }; - - enum class State { - START=__LINE__, - ANIMATE=__LINE__, - END=__LINE__, - }; - - struct FSM : public DeadSimpleFSM { - UI $ui; - gui::routing::Router $router; - sf::RenderWindow $window{sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Animation Crafting Tool"}; - - void init(const std::string &sprite_name) { - $ui.init(sprite_name); - - if(YES_SYNC) { - $window.setVerticalSyncEnabled(VSYNC); - if(FRAME_LIMIT) $window.setFramerateLimit(FRAME_LIMIT); - } - - $window.setPosition({0,0}); - } - - void event(game::Event ev, std::any data={}) { - switch($state) { - FSM_STATE(State, START, ev); - FSM_STATE(State, ANIMATE, ev); - FSM_STATE(State, END, ev); - } - } - - void START(game::Event ev) { - switch(ev) { - case game::Event::TICK: - state(State::ANIMATE); - break; - default: - state(State::START); - } - } - - void ANIMATE(game::Event ev) { - switch(ev) { - case game::Event::TICK: - // stuff - break; - default: - state(State::START); - } - } - - void END(game::Event ev) { - } - - void handle_keyboard_mouse() { - while(const auto ev = $window.pollEvent()) { - using enum game::Event; - auto gui_ev = $router.process_event(ev); - auto mouse_pos = $window.mapPixelToCoords($router.position); - - switch(gui_ev) { - case MOUSE_CLICK: - $ui.mouse(mouse_pos.x, mouse_pos.y, guecs::NO_MODS); - break; - case MOUSE_MOVE: - $ui.mouse(mouse_pos.x, mouse_pos.y, {1 << guecs::ModBit::hover}); - break; - case QUIT: - state(State::END); - default: - break; // ignored - } - } - } - - void render() { - $window.clear(); - $ui.render($window); - $window.display(); - } - - bool active() { - return !in_state(State::END); - } - }; -} - animate2::Sheet sheet{ .width{720*2}, .height{720}, @@ -170,7 +35,7 @@ animate2::Transform scale_tr{ .ease_rate{4.0f}, .scaled{true}, .toggled{false}, - .looped{false}, + .looped{true}, .easing = ease2::in_out_back, .motion = ease2::move_rush, }; @@ -190,6 +55,148 @@ animate2::Transform move_tr{ .motion = ease2::move_shake, }; +animate2::Animate2 anim{sheet, sequence, scale_tr}; + +namespace animator { + + void FSM::init(const std::string &sprite_name) { + $ui.init(sprite_name, *this); + + if(YES_SYNC) { + $window.setVerticalSyncEnabled(VSYNC); + if(FRAME_LIMIT) $window.setFramerateLimit(FRAME_LIMIT); + } + + $window.setPosition({0,0}); + } + + void FSM::event(Event ev, std::any data) { + switch($state) { + FSM_STATE(State, START, ev); + FSM_STATE(State, ANIMATE, ev); + FSM_STATE(State, END, ev); + } + } + + void FSM::START(Event ev) { + switch(ev) { + case Event::TICK: + state(State::ANIMATE); + break; + default: + state(State::START); + } + } + + void FSM::ANIMATE(Event ev) { + switch(ev) { + case Event::TICK: + // stuff + break; + case Event::PLAY: + if(!anim.playing) anim.play(); + break; + case Event::STOP: + if(anim.playing) anim.stop(); + break; + case Event::LOOP: + anim.$transform.looped = !anim.$transform.looped; + break; + case Event::TOGGLE: + anim.$transform.toggled = !anim.$transform.toggled; + break; + default: + state(State::START); + } + } + + void FSM::END(Event ev) { + } + + void FSM::handle_keyboard_mouse() { + while(const auto ev = $window.pollEvent()) { + using enum game::Event; + auto gui_ev = $router.process_event(ev); + auto mouse_pos = $window.mapPixelToCoords($router.position); + + switch(gui_ev) { + case MOUSE_CLICK: + $ui.mouse(mouse_pos.x, mouse_pos.y, guecs::NO_MODS); + break; + case MOUSE_MOVE: + $ui.mouse(mouse_pos.x, mouse_pos.y, {1 << guecs::ModBit::hover}); + break; + case QUIT: + state(State::END); + default: + break; // ignored + } + } + } + + void FSM::render() { + $window.clear(); + $ui.render($window); + $window.display(); + } + + bool FSM::active() { + return !in_state(State::END); + } + + void UI::button(const std::string& name, std::function cb) { + auto comp = $ui.entity(name); + $ui.set(comp, {}); + $ui.set(comp, {guecs::to_wstring(name)}); + $ui.set(comp, {}); + $ui.set(comp, {cb}); + } + + void UI::init(const std::string& sprite_name, FSM& fsm) { + $ui.position(0,0, SCREEN_WIDTH, SCREEN_HEIGHT); + $ui.layout( + "[play|*%=(300,400)viewer|_|_]" + "[stop|_|_|_]" + "[loop|_|_|_]" + "[toggled|_|_|_]"); + + auto viewer = $ui.entity("viewer"); + $ui.set(viewer, { sprite_name, 0, false}); + + button("stop", [&](auto){ + fsm.event(Event::STOP, {}); + }); + + button("play", [&](auto){ + fsm.event(Event::PLAY, {}); + }); + + button("loop", [&](auto){ + fsm.event(Event::LOOP, {}); + }); + + button("toggled", [&](auto){ + fsm.event(Event::TOGGLE, {}); + }); + + $ui.init(); + } + + void UI::render(sf::RenderWindow& window) { + $ui.render(window); + } + + bool UI::mouse(float x, float y, guecs::Modifiers mods) { + return $ui.mouse(x, y, mods); + } + + std::shared_ptr UI::get_sprite() { + auto viewer = $ui.entity("viewer"); + return $ui.get(viewer).sprite; + } +} + + int main(int argc, char* argv[]) { shaders::init(); components::init(); @@ -207,7 +214,6 @@ int main(int argc, char* argv[]) { animator::FSM main; main.init(argv[1]); - animate2::Animate2 anim{sheet, sequence, scale_tr}; anim.play(); auto sprite = main.$ui.get_sprite(); @@ -220,7 +226,7 @@ int main(int argc, char* argv[]) { auto [ticks, alpha] = anim.commit(); for(int i = 0; i < ticks; i++) { - main.event(game::Event::TICK, {}); + main.event(animator::Event::TICK, {}); if(anim.playing) { anim.update(); diff --git a/tools/animator.hpp b/tools/animator.hpp new file mode 100644 index 0000000..c2470ec --- /dev/null +++ b/tools/animator.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include "gui/event_router.hpp" +#include "gui/guecstra.hpp" +#include "events.hpp" + +namespace animator { + + enum class State { + START=__LINE__, + ANIMATE=__LINE__, + END=__LINE__, + }; + + enum class Event { + TICK=__LINE__, + PLAY=__LINE__, + STOP=__LINE__, + LOOP=__LINE__, + TOGGLE=__LINE__, + }; + + struct FSM; + + struct UI { + guecs::UI $ui; + + void button(const std::string& name, std::function cb); + void init(const std::string& sprite_name, FSM& fsm); + void render(sf::RenderWindow& window); + bool mouse(float x, float y, guecs::Modifiers mods); + std::shared_ptr get_sprite(); + }; + + struct FSM : public DeadSimpleFSM { + UI $ui; + gui::routing::Router $router; + sf::RenderWindow $window{sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Animation Crafting Tool"}; + + void init(const std::string &sprite_name); + void event(Event ev, std::any data={}); + void START(Event ev); + void ANIMATE(Event ev); + void END(Event ev); + void handle_keyboard_mouse(); + void render(); + bool active(); + }; + +}