From 8a4e55a06d39b431e2b4262a5584f6f31254c14a Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 00:50:34 -0400 Subject: [PATCH 01/12] refactor: remove unused custom input box, asw::ui::InputBox replaces it --- src/Game.h | 1 - src/ui/InputBox.cpp | 121 -------------------------------------------- src/ui/InputBox.h | 35 ------------- 3 files changed, 157 deletions(-) delete mode 100644 src/ui/InputBox.cpp delete mode 100644 src/ui/InputBox.h diff --git a/src/Game.h b/src/Game.h index 7ad0bf1..cd5a86a 100644 --- a/src/Game.h +++ b/src/Game.h @@ -8,7 +8,6 @@ #include "Card.h" #include "GameDifficulty.h" #include "State.h" -#include "ui/InputBox.h" #include "util/ScoreManager.h" struct DifficultyConfig { diff --git a/src/ui/InputBox.cpp b/src/ui/InputBox.cpp deleted file mode 100644 index 07324ff..0000000 --- a/src/ui/InputBox.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "InputBox.h" - -InputBox::InputBox(int x, int y, int width, int height, asw::Font font, const std::string& value, - const std::string& type) - : x(x) - , y(y) - , width(width) - , height(height) - , font(font) - , text(value) - , type(type) -{ -} - -void InputBox::focus() -{ - focused = true; -} - -std::string InputBox::getValue() const -{ - return text; -} - -bool InputBox::hover() const -{ - return asw::input::mouse.position.x > x && asw::input::mouse.position.x < x + width - && asw::input::mouse.position.y > y && asw::input::mouse.position.y < y + height; -} - -void InputBox::update() -{ - // Focus - if (asw::input::mouse.pressed[1]) { - focused = hover(); - - if (focused) { - int closest = width; - - for (unsigned int i = 0; i <= text.length(); i++) { - const int textSize = asw::util::get_text_size(font, text.substr(0, i)).x; - const int distance = std::abs(textSize + x + 6 - asw::input::mouse.position.x); - - if (distance < closest) { - textIterator = i; - closest = distance; - } - } - } - } - - int lastKey = asw::input::keyboard.last_pressed; - - if (!focused || lastKey == -1) { - return; - } - - // a character key was pressed; add it to the string - if (type == "number" || type == "text") { - // Numeric only - if (lastKey >= 30 && lastKey <= 38) { - text.insert(text.begin() + textIterator, lastKey + 19); - textIterator++; - } - - if (lastKey == 39) { - text.insert(text.begin() + textIterator, lastKey + 9); - textIterator++; - } - } - - if (type == "text" && lastKey >= 4 && lastKey <= 29) { - if (asw::input::get_key_down(asw::input::Key::LShift) - || asw::input::get_key_down(asw::input::Key::RShift)) { - text.insert(text.begin() + textIterator, 'A' - 4 + lastKey); - } else { - text.insert(text.begin() + textIterator, 'a' - 4 + lastKey); - } - - textIterator++; - } - - // some other, "special" key was pressed; handle it here - if (asw::input::get_key_down(asw::input::Key::Backspace) && textIterator != 0) { - textIterator--; - text.erase(text.begin() + textIterator); - } - - if (asw::input::get_key_down(asw::input::Key::Right) && textIterator != text.size()) { - textIterator++; - } - - if (asw::input::get_key_down(asw::input::Key::Left) && textIterator != 0) { - textIterator--; - } -} - -// Draw box -void InputBox::draw() const -{ - asw::draw::rect_fill(asw::Quad(x, y, width, height), asw::Color(12, 12, 12)); - - asw::Color col = (hover() || focused) ? asw::Color(230, 230, 230) : asw::Color(245, 245, 245); - - if (focused) { - asw::draw::rect_fill(asw::Quad(x + 2, y + 2, width - 4, height - 4), col); - } else { - asw::draw::rect_fill(asw::Quad(x + 1, y + 1, width - 2, height - 2), col); - } - - // Output the string to the screen - asw::draw::text(font, text, asw::Vec2(x + 6, y), asw::Color(22, 22, 22)); - - // Draw the caret - if (focused) { - int textSize = asw::util::get_text_size(font, text.substr(0, textIterator)).x; - - asw::draw::rect_fill( - asw::Quad(textSize + x + 6, y + 8, 1, height - 16), asw::Color(0, 0, 0)); - } -} diff --git a/src/ui/InputBox.h b/src/ui/InputBox.h deleted file mode 100644 index 1ff2a60..0000000 --- a/src/ui/InputBox.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef INPUTBOX_H -#define INPUTBOX_H - -#include -#include - -class InputBox { -public: - explicit InputBox(int x = 0, int y = 0, int width = 100, int height = 20, - asw::Font font = nullptr, const std::string& value = "", const std::string& type = "text"); - - void focus(); - - std::string getValue() const; - void update(); - void draw() const; - bool hover() const; - -private: - int x; - int y; - - int width; - int height; - - asw::Font font; - - std::string text; - std::string type; - - unsigned int textIterator { 0 }; - bool focused { false }; -}; - -#endif // INPUTBOX_H From 572d2959f62b235e524ca47b1fdf9a92b2208358 Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 00:50:42 -0400 Subject: [PATCH 02/12] feat: bump to asw 0.11.1 --- CMakeLists.txt | 2 +- src/Card.cpp | 4 ++-- src/Intro.cpp | 2 +- src/Menu.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22399cd..a06d82e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/target) include(cmake/get_cpm.cmake) -CPMAddPackage("gh:adsgames/asw@0.8.0") +CPMAddPackage("gh:adsgames/asw@0.11.1") file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h) diff --git a/src/Card.cpp b/src/Card.cpp index 96716c8..8bb0b02 100644 --- a/src/Card.cpp +++ b/src/Card.cpp @@ -98,8 +98,8 @@ void Card::update(float dt) { auto screen_size = asw::display::get_logical_size(); - if (!selected_ && asw::input::mouse.pressed[1] && number_selected < 2 - && transform_.contains(asw::input::mouse.position)) { + if (!selected_ && asw::input::get_mouse_button_down(asw::input::MouseButton::Left) && number_selected < 2 + && transform_.contains(asw::input::get_mouse().position)) { animation_time_ = 0.0F; asw::sound::play(card_flip_); selected_ = true; diff --git a/src/Intro.cpp b/src/Intro.cpp index 528964a..14e1474 100644 --- a/src/Intro.cpp +++ b/src/Intro.cpp @@ -14,7 +14,7 @@ void Intro::update(float dt) timer_ += dt; - if (timer_ >= 3.0F || asw::input::keyboard.any_pressed) { + if (timer_ >= 3.0F || asw::input::get_keyboard().any_pressed) { manager.set_next_scene(States::Menu); } } diff --git a/src/Menu.cpp b/src/Menu.cpp index ccd900e..38526c0 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -30,7 +30,7 @@ void Menu::init() btn_quit.transform = { 320, 535, 640, 100 }; btn_quit.text = "Quit"; btn_quit.font = font; - btn_quit.on_click = []() { asw::core::exit = true; }; + btn_quit.on_click = []() { asw::core::exit(); }; } void Menu::update(float dt) From abf0779236b89db3f90d685567e17b096f6a5b3e Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 00:50:49 -0400 Subject: [PATCH 03/12] refactor: use asw easing for the card flip --- src/Card.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Card.cpp b/src/Card.cpp index 8bb0b02..1e3f028 100644 --- a/src/Card.cpp +++ b/src/Card.cpp @@ -1,6 +1,6 @@ #include "Card.h" -#include +#include #include asw::Texture Card::backImage { nullptr }; @@ -115,8 +115,9 @@ void Card::update(float dt) if (!animation_done_) { animation_time_ += dt; auto interpolation = animation_time_ / CARD_FLIP_TIME_S; - auto widthMult = (cos(2.0F * std::numbers::pi_v * interpolation) + 1.0F) / 2.0F; - animation_width_ = transform_.size.x * widthMult; + // Shrink to nothing at the halfway point, then grow back + animation_width_ = asw::easing::ease(0.0F, transform_.size.x, + std::abs((2.0F * interpolation) - 1.0F), asw::easing::ease_in_out_sine); if (selected_) { if (!flipped_ && interpolation > 0.5F) { From 9248db06960d94e93dfb88bc1a4611382b629fc4 Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 00:50:56 -0400 Subject: [PATCH 04/12] feat: controller support with asw action bindings --- src/Controls.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++ src/Controls.h | 32 ++++++++++++++++++++ src/Game.cpp | 5 ++-- src/HighScores.cpp | 5 +++- src/Intro.cpp | 6 ++-- src/LevelSelect.cpp | 4 ++- src/Menu.cpp | 4 +-- src/main.cpp | 2 ++ 8 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 src/Controls.cpp create mode 100644 src/Controls.h diff --git a/src/Controls.cpp b/src/Controls.cpp new file mode 100644 index 0000000..bd55fbf --- /dev/null +++ b/src/Controls.cpp @@ -0,0 +1,73 @@ +#include "Controls.h" + +#include + +namespace { +using asw::input::ControllerAxis; +using asw::input::ControllerAxisBinding; +using asw::input::ControllerButton; +using asw::input::ControllerButtonBinding; +using asw::input::Key; +using asw::input::KeyBinding; + +constexpr auto ANY = asw::input::ANY_CONTROLLER; + +// Stick must pass this before it counts as a direction +constexpr float STICK_THRESHOLD = 0.5F; + +void bind_direction(const std::string& name, ControllerButton dpad, ControllerAxis axis, bool positive) +{ + asw::input::bind_action(name, ControllerButtonBinding { dpad, ANY }); + asw::input::bind_action(name, ControllerAxisBinding { axis, ANY, STICK_THRESHOLD, positive }); +} +} // namespace + +void controls::bind() +{ + bind_direction(UI_UP, ControllerButton::DPadUp, ControllerAxis::LeftY, false); + bind_direction(UI_DOWN, ControllerButton::DPadDown, ControllerAxis::LeftY, true); + bind_direction(UI_LEFT, ControllerButton::DPadLeft, ControllerAxis::LeftX, false); + bind_direction(UI_RIGHT, ControllerButton::DPadRight, ControllerAxis::LeftX, true); + + asw::input::bind_action(UI_SELECT, ControllerButtonBinding { ControllerButton::A, ANY }); + + asw::input::bind_action(UI_BACK, KeyBinding { Key::Escape }); + asw::input::bind_action(UI_BACK, ControllerButtonBinding { ControllerButton::B, ANY }); + asw::input::bind_action(UI_BACK, ControllerButtonBinding { ControllerButton::Back, ANY }); + + asw::input::bind_action(CONFIRM, KeyBinding { Key::Return }); + asw::input::bind_action(CONFIRM, ControllerButtonBinding { ControllerButton::A, ANY }); + asw::input::bind_action(CONFIRM, ControllerButtonBinding { ControllerButton::Start, ANY }); +} + +void controls::update_ui(asw::ui::Root& ui) +{ + const auto move = [&ui](int dx, int dy) { + ui.ctx.focus.focus_dir(ui.ctx, dx, dy); + ui.ctx.theme.show_focus = true; + }; + + if (asw::input::get_action_down(UI_UP)) { + move(0, -1); + } + if (asw::input::get_action_down(UI_DOWN)) { + move(0, 1); + } + if (asw::input::get_action_down(UI_LEFT)) { + move(-1, 0); + } + if (asw::input::get_action_down(UI_RIGHT)) { + move(1, 0); + } + + if (asw::input::get_action_down(UI_SELECT)) { + ui.dispatch_to_focused(asw::ui::UIEvent { .type = asw::ui::UIEvent::Type::Activate }); + } +} + +bool controls::any_controller_skip() +{ + return asw::input::get_controller_button_down(ANY, ControllerButton::A) + || asw::input::get_controller_button_down(ANY, ControllerButton::B) + || asw::input::get_controller_button_down(ANY, ControllerButton::Start); +} diff --git a/src/Controls.h b/src/Controls.h new file mode 100644 index 0000000..7eaa905 --- /dev/null +++ b/src/Controls.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +// Input actions shared by keyboard and controllers +namespace controls { + +// Move UI focus, controller only (asw::ui::Root handles the arrow keys) +inline constexpr const char* UI_UP = "ui_up"; +inline constexpr const char* UI_DOWN = "ui_down"; +inline constexpr const char* UI_LEFT = "ui_left"; +inline constexpr const char* UI_RIGHT = "ui_right"; + +// Press the focused widget, controller only (asw::ui::Root handles Return) +inline constexpr const char* UI_SELECT = "ui_select"; + +// Leave the current screen +inline constexpr const char* UI_BACK = "ui_back"; + +// Confirm the name at the end of a game +inline constexpr const char* CONFIRM = "confirm"; + +// Bind all actions, call once after asw::core::init +void bind(); + +// Move focus and press widgets in a UI with a controller +void update_ui(asw::ui::Root& ui); + +// Check if a controller button that skips a screen was pressed +bool any_controller_skip(); + +} // namespace controls diff --git a/src/Game.cpp b/src/Game.cpp index 0fdae73..98c7c5b 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -7,6 +7,7 @@ #include #include +#include "Controls.h" #include "globals.h" GameDifficulty Game::difficulty = GameDifficulty::EASY; @@ -114,7 +115,7 @@ void Game::update(float dt) endgame_update(); } // Go to menu - else if (asw::input::get_key_down(asw::input::Key::Escape)) { + else if (asw::input::get_action_down(controls::UI_BACK)) { manager.set_next_scene(States::Menu); } @@ -135,7 +136,7 @@ void Game::endgame_update() { end_panel_->visible = true; - if (asw::input::get_key_down(asw::input::Key::Return)) { + if (asw::input::get_action_down(controls::CONFIRM)) { const auto& config = DIFFICULTY_CONFIG[difficulty]; score_manager_.add(in_name_->value, moves_); score_manager_.save(config.highscore_file); diff --git a/src/HighScores.cpp b/src/HighScores.cpp index 3c92236..6125d90 100644 --- a/src/HighScores.cpp +++ b/src/HighScores.cpp @@ -4,6 +4,8 @@ #include #include +#include "Controls.h" + std::map HighScores::SCORE_FILES { { GameDifficulty::EASY, "assets/data/highscores_easy.dat" }, { GameDifficulty::MEDIUM, "assets/data/highscores_medium.dat" }, @@ -70,6 +72,7 @@ void HighScores::update(float dt) { Scene::update(dt); ui_.update(); + controls::update_ui(ui_); if (difficulty_ == GameDifficulty::EASY) { lbl_mode_->text = "Easy"; @@ -82,7 +85,7 @@ void HighScores::update(float dt) } // Go to menu - if (asw::input::get_key_down(asw::input::Key::Escape)) { + if (asw::input::get_action_down(controls::UI_BACK)) { manager.set_next_scene(States::Menu); } } diff --git a/src/Intro.cpp b/src/Intro.cpp index 14e1474..54547bf 100644 --- a/src/Intro.cpp +++ b/src/Intro.cpp @@ -1,7 +1,6 @@ #include "Intro.h" -#include -#include +#include "Controls.h" void Intro::init() { @@ -14,7 +13,8 @@ void Intro::update(float dt) timer_ += dt; - if (timer_ >= 3.0F || asw::input::get_keyboard().any_pressed) { + if (timer_ >= 3.0F || asw::input::get_keyboard().any_pressed + || controls::any_controller_skip()) { manager.set_next_scene(States::Menu); } } diff --git a/src/LevelSelect.cpp b/src/LevelSelect.cpp index 1eff961..13ba1ac 100644 --- a/src/LevelSelect.cpp +++ b/src/LevelSelect.cpp @@ -2,6 +2,7 @@ #include +#include "Controls.h" #include "Game.h" void LevelSelect::init() @@ -61,9 +62,10 @@ void LevelSelect::update(float dt) { Scene::update(dt); ui_.update(); + controls::update_ui(ui_); // Go to menu - if (asw::input::get_key_down(asw::input::Key::Escape)) { + if (asw::input::get_action_down(controls::UI_BACK)) { manager.set_next_scene(States::Menu); } } diff --git a/src/Menu.cpp b/src/Menu.cpp index 38526c0..ffc14f1 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -1,7 +1,6 @@ #include "Menu.h" -#include -#include +#include "Controls.h" void Menu::init() { @@ -37,6 +36,7 @@ void Menu::update(float dt) { Scene::update(dt); ui_.update(); + controls::update_ui(ui_); } void Menu::draw() diff --git a/src/main.cpp b/src/main.cpp index e4073c2..b159e68 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include // For state engine +#include "Controls.h" #include "Game.h" #include "HighScores.h" #include "Init.h" @@ -19,6 +20,7 @@ int main() { asw::core::init(1280, 960); + controls::bind(); auto app = asw::scene::SceneManager(); app.register_scene(States::Init, app); From 6aefa0533571f86a1294ffd4ff46ee586e989f22 Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 00:51:16 -0400 Subject: [PATCH 05/12] style: wrap long lines --- src/Card.cpp | 4 ++-- src/Controls.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Card.cpp b/src/Card.cpp index 1e3f028..eec071d 100644 --- a/src/Card.cpp +++ b/src/Card.cpp @@ -98,8 +98,8 @@ void Card::update(float dt) { auto screen_size = asw::display::get_logical_size(); - if (!selected_ && asw::input::get_mouse_button_down(asw::input::MouseButton::Left) && number_selected < 2 - && transform_.contains(asw::input::get_mouse().position)) { + if (!selected_ && asw::input::get_mouse_button_down(asw::input::MouseButton::Left) + && number_selected < 2 && transform_.contains(asw::input::get_mouse().position)) { animation_time_ = 0.0F; asw::sound::play(card_flip_); selected_ = true; diff --git a/src/Controls.cpp b/src/Controls.cpp index bd55fbf..ab46ba4 100644 --- a/src/Controls.cpp +++ b/src/Controls.cpp @@ -15,7 +15,8 @@ constexpr auto ANY = asw::input::ANY_CONTROLLER; // Stick must pass this before it counts as a direction constexpr float STICK_THRESHOLD = 0.5F; -void bind_direction(const std::string& name, ControllerButton dpad, ControllerAxis axis, bool positive) +void bind_direction( + const std::string& name, ControllerButton dpad, ControllerAxis axis, bool positive) { asw::input::bind_action(name, ControllerButtonBinding { dpad, ANY }); asw::input::bind_action(name, ControllerAxisBinding { axis, ANY, STICK_THRESHOLD, positive }); From ff9a4b156fbe649b3334f275a0b2d3b8908b31a3 Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 00:51:34 -0400 Subject: [PATCH 06/12] ci: release every platform with the shared A.D.S. Games workflow --- .github/workflows/deploy.yml | 21 +++++---------------- .github/workflows/release.yml | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e0de6c2..96f541d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -28,15 +28,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup cache - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: "./build/release/_deps" key: deps-${{ runner.os }} - - uses: mymindstorm/setup-emsdk@v14 + - uses: mymindstorm/setup-emsdk@v16 with: version: 4.0.6 actions-cache-folder: "emsdk-cache" @@ -51,21 +51,10 @@ jobs: - name: Build run: cmake --build --preset release - - uses: actions/upload-pages-artifact@v3 + - uses: actions/upload-pages-artifact@v5 with: path: ./build/release/target/ - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 - - - name: Deploy to A.D.S. Games - if: startsWith(github.ref, 'refs/tags/v') - uses: adsgames/deploy-to-adsgames@v1 - with: - project-id: memory - build-dir: ./build/release/target/ - platform: WEB - bucket-access-key: ${{ secrets.LINODE_BUCKET_ACCESS_KEY }} - bucket-secret-key: ${{ secrets.LINODE_BUCKET_SECRET_KEY }} - api-key: ${{ secrets.ADSGAMES_API_KEY }} + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8309077 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Release + +on: + push: + tags: + - v* + pull_request: + branches: + - main + +jobs: + release: + uses: adsgames/deploy-to-adsgames/.github/workflows/release-game.yml@v1 + permissions: + id-token: write + contents: read + with: + project-id: memory + # Pull requests only check that every platform builds, and dry run adsgames.json + deploy: ${{ startsWith(github.ref, 'refs/tags/v') }} + secrets: + ADSGAMES_API_KEY: ${{ secrets.ADSGAMES_API_KEY }} From 78726bea05c94476792e749b413c9244a28ea3f0 Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 00:59:25 -0400 Subject: [PATCH 07/12] ci: build pages with emscripten 6.0.10 --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 96f541d..2369ae7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,11 +34,11 @@ jobs: uses: actions/cache@v6 with: path: "./build/release/_deps" - key: deps-${{ runner.os }} + key: deps-${{ runner.os }}-emsdk-6.0.10 - uses: mymindstorm/setup-emsdk@v16 with: - version: 4.0.6 + version: 6.0.10 actions-cache-folder: "emsdk-cache" - name: Download ports From 22b2197e13701a3f429c43c4d644668a16910148 Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 01:02:59 -0400 Subject: [PATCH 08/12] ci: deploy GitHub Pages with the shared A.D.S. Games workflow --- .github/workflows/deploy.yml | 60 ------------------------------ .github/workflows/github-pages.yml | 26 +++++++++++++ 2 files changed, 26 insertions(+), 60 deletions(-) delete mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/github-pages.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 2369ae7..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Deploy Game - -on: - push: - branches: - - main - tags: - - v* - pull_request: - branches: - - main - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "${{ github.workflow }}-${{ github.ref }}" - cancel-in-progress: true - -jobs: - deploy: - name: Deploy - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Setup cache - uses: actions/cache@v6 - with: - path: "./build/release/_deps" - key: deps-${{ runner.os }}-emsdk-6.0.10 - - - uses: mymindstorm/setup-emsdk@v16 - with: - version: 6.0.10 - actions-cache-folder: "emsdk-cache" - - - name: Download ports - run: | - embuilder build harfbuzz - - - name: Configure - run: emcmake cmake --preset release - - - name: Build - run: cmake --build --preset release - - - uses: actions/upload-pages-artifact@v5 - with: - path: ./build/release/target/ - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v5 diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml new file mode 100644 index 0000000..d9b633d --- /dev/null +++ b/.github/workflows/github-pages.yml @@ -0,0 +1,26 @@ +name: Deploy GitHub Pages + +on: + push: + branches: + - main + tags: + - v* + pull_request: + branches: + - main + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + pages: + uses: adsgames/deploy-to-adsgames/.github/workflows/deploy-github-pages.yml@v1 + permissions: + contents: read + pages: write + id-token: write + with: + # Pull requests only check that the web build works + deploy: ${{ github.event_name == 'push' }} From 7e82aa2eb75cee7752505b43dc8c4d39de946cae Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 09:40:48 -0400 Subject: [PATCH 09/12] ci: use the renamed deploy-asw-pages workflow --- .github/workflows/github-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index d9b633d..7c06243 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -16,7 +16,7 @@ concurrency: jobs: pages: - uses: adsgames/deploy-to-adsgames/.github/workflows/deploy-github-pages.yml@v1 + uses: adsgames/deploy-to-adsgames/.github/workflows/deploy-asw-pages.yml@v1 permissions: contents: read pages: write From 9dfab30da752df9e69f5f6c21078cd0ff3749d2e Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 10:00:21 -0400 Subject: [PATCH 10/12] feat: bump to asw 0.11.2 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a06d82e..4b91a25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/target) include(cmake/get_cpm.cmake) -CPMAddPackage("gh:adsgames/asw@0.11.1") +CPMAddPackage("gh:adsgames/asw@0.11.2") file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h) From 97518695fdb5bcfa7a856f134c078c2a083819be Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 12:40:33 -0400 Subject: [PATCH 11/12] feat: bump to asw 0.12.0 - Drive UI navigation with asw::ui::bind_default_navigation (keyboard, D-pad, left stick, A, B, plus the controller Back button) and replace the hand written controls::update_ui forwarding and UI_* actions. - Leave LevelSelect, HighScores and Game with Root::on_back. - Use theme.font instead of setting the font on every widget. - Remove root size workarounds, the root follows the screen size now. - Keep labels black through theme.text, labels now default to the theme. - Focus the name box when the end panel shows, focus is no longer given to the first widget automatically. --- CMakeLists.txt | 2 +- src/Controls.cpp | 52 ++++++--------------------------------------- src/Controls.h | 16 ++------------ src/Game.cpp | 24 +++++++++++++-------- src/HighScores.cpp | 15 ++++--------- src/LevelSelect.cpp | 12 +++-------- src/Menu.cpp | 7 ++---- 7 files changed, 34 insertions(+), 94 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b91a25..f6648b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/target) include(cmake/get_cpm.cmake) -CPMAddPackage("gh:adsgames/asw@0.11.2") +CPMAddPackage("gh:adsgames/asw@0.12.0") file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h) diff --git a/src/Controls.cpp b/src/Controls.cpp index ab46ba4..bc29a69 100644 --- a/src/Controls.cpp +++ b/src/Controls.cpp @@ -1,10 +1,6 @@ #include "Controls.h" -#include - namespace { -using asw::input::ControllerAxis; -using asw::input::ControllerAxisBinding; using asw::input::ControllerButton; using asw::input::ControllerButtonBinding; using asw::input::Key; @@ -12,58 +8,24 @@ using asw::input::KeyBinding; constexpr auto ANY = asw::input::ANY_CONTROLLER; -// Stick must pass this before it counts as a direction -constexpr float STICK_THRESHOLD = 0.5F; - -void bind_direction( - const std::string& name, ControllerButton dpad, ControllerAxis axis, bool positive) -{ - asw::input::bind_action(name, ControllerButtonBinding { dpad, ANY }); - asw::input::bind_action(name, ControllerAxisBinding { axis, ANY, STICK_THRESHOLD, positive }); -} +asw::ui::Navigation navigation_; } // namespace void controls::bind() { - bind_direction(UI_UP, ControllerButton::DPadUp, ControllerAxis::LeftY, false); - bind_direction(UI_DOWN, ControllerButton::DPadDown, ControllerAxis::LeftY, true); - bind_direction(UI_LEFT, ControllerButton::DPadLeft, ControllerAxis::LeftX, false); - bind_direction(UI_RIGHT, ControllerButton::DPadRight, ControllerAxis::LeftX, true); - - asw::input::bind_action(UI_SELECT, ControllerButtonBinding { ControllerButton::A, ANY }); - - asw::input::bind_action(UI_BACK, KeyBinding { Key::Escape }); - asw::input::bind_action(UI_BACK, ControllerButtonBinding { ControllerButton::B, ANY }); - asw::input::bind_action(UI_BACK, ControllerButtonBinding { ControllerButton::Back, ANY }); + // Arrows, Tab, Return, Space, Escape, D-pad, left stick, A and B + navigation_ = asw::ui::bind_default_navigation(); + asw::input::bind_action( + navigation_.back, ControllerButtonBinding { ControllerButton::Back, ANY }); asw::input::bind_action(CONFIRM, KeyBinding { Key::Return }); asw::input::bind_action(CONFIRM, ControllerButtonBinding { ControllerButton::A, ANY }); asw::input::bind_action(CONFIRM, ControllerButtonBinding { ControllerButton::Start, ANY }); } -void controls::update_ui(asw::ui::Root& ui) +const asw::ui::Navigation& controls::navigation() { - const auto move = [&ui](int dx, int dy) { - ui.ctx.focus.focus_dir(ui.ctx, dx, dy); - ui.ctx.theme.show_focus = true; - }; - - if (asw::input::get_action_down(UI_UP)) { - move(0, -1); - } - if (asw::input::get_action_down(UI_DOWN)) { - move(0, 1); - } - if (asw::input::get_action_down(UI_LEFT)) { - move(-1, 0); - } - if (asw::input::get_action_down(UI_RIGHT)) { - move(1, 0); - } - - if (asw::input::get_action_down(UI_SELECT)) { - ui.dispatch_to_focused(asw::ui::UIEvent { .type = asw::ui::UIEvent::Type::Activate }); - } + return navigation_; } bool controls::any_controller_skip() diff --git a/src/Controls.h b/src/Controls.h index 7eaa905..473ac53 100644 --- a/src/Controls.h +++ b/src/Controls.h @@ -5,26 +5,14 @@ // Input actions shared by keyboard and controllers namespace controls { -// Move UI focus, controller only (asw::ui::Root handles the arrow keys) -inline constexpr const char* UI_UP = "ui_up"; -inline constexpr const char* UI_DOWN = "ui_down"; -inline constexpr const char* UI_LEFT = "ui_left"; -inline constexpr const char* UI_RIGHT = "ui_right"; - -// Press the focused widget, controller only (asw::ui::Root handles Return) -inline constexpr const char* UI_SELECT = "ui_select"; - -// Leave the current screen -inline constexpr const char* UI_BACK = "ui_back"; - // Confirm the name at the end of a game inline constexpr const char* CONFIRM = "confirm"; // Bind all actions, call once after asw::core::init void bind(); -// Move focus and press widgets in a UI with a controller -void update_ui(asw::ui::Root& ui); +// UI navigation actions, set on asw::ui::Root::ctx.navigation +const asw::ui::Navigation& navigation(); // Check if a controller button that skips a screen was pressed bool any_controller_skip(); diff --git a/src/Game.cpp b/src/Game.cpp index 98c7c5b..5a452e9 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -32,9 +32,18 @@ void Game::init() const auto font = asw::assets::load_font("assets/fonts/jersey-10.ttf", 48); ui_ = asw::ui::Root(); - ui_.root.transform.set_size(screen_size.x, screen_size.y); + ui_.ctx.navigation = controls::navigation(); + ui_.ctx.theme.font = font; + ui_.ctx.theme.text = asw::Color(0, 0, 0); ui_.root.bg_image = asw::assets::load_texture("assets/img/backgrounds/background.png"); + // Go to menu, unless the game is over and the name is being entered + ui_.on_back = [this]() { + if (!cards_.empty()) { + manager.set_next_scene(States::Menu); + } + }; + // Moves panel auto& pnl_moves = ui_.root.add_child(); pnl_moves.transform.set_position(15, 15); @@ -43,7 +52,6 @@ void Game::init() auto& lbl_moves = pnl_moves.add_child(); lbl_moves.transform.set_position(20, 20); - lbl_moves.font = font; lbl_moves_ = &lbl_moves; // Level end panel @@ -57,13 +65,11 @@ void Game::init() auto& in_name = end_panel.add_child(); in_name.transform.set_position(screen_size.x / 2 - 200, screen_size.y / 2 - 50); in_name.transform.set_size(400, 80); - in_name.font = font; in_name.value = "Player"; in_name_ = &in_name; auto& lbl_complete = end_panel.add_child(); lbl_complete.transform.set_position(screen_size.x / 2, screen_size.y / 2 - 100); - lbl_complete.font = font; lbl_complete.text = "Congratulations! Enter Your Name"; lbl_complete.justify = asw::TextJustify::Center; @@ -114,10 +120,6 @@ void Game::update(float dt) if (cards_.empty()) { endgame_update(); } - // Go to menu - else if (asw::input::get_action_down(controls::UI_BACK)) { - manager.set_next_scene(States::Menu); - } erase_off_screen_cards(); calculate_selected_cards(); @@ -134,7 +136,11 @@ void Game::update(float dt) void Game::endgame_update() { - end_panel_->visible = true; + // Show the name entry and focus it so typing goes to it + if (!end_panel_->visible) { + end_panel_->visible = true; + ui_.focus(*in_name_); + } if (asw::input::get_action_down(controls::CONFIRM)) { const auto& config = DIFFICULTY_CONFIG[difficulty]; diff --git a/src/HighScores.cpp b/src/HighScores.cpp index 6125d90..08d3416 100644 --- a/src/HighScores.cpp +++ b/src/HighScores.cpp @@ -20,8 +20,11 @@ void HighScores::init() font_ = asw::assets::load_font("assets/fonts/jersey-10.ttf", 48); ui_ = asw::ui::Root(); - ui_.root.transform.size = asw::Vec2(1280, 960); + ui_.ctx.navigation = controls::navigation(); + ui_.ctx.theme.font = font_; + ui_.ctx.theme.text = asw::Color(0, 0, 0); ui_.root.bg_image = background; + ui_.on_back = [this]() { manager.set_next_scene(States::Menu); }; auto& pnl_scores = ui_.root.add_child(); pnl_scores.transform.set_position(320, 130); @@ -31,7 +34,6 @@ void HighScores::init() auto& btn_left = ui_.root.add_child(); btn_left.transform = { 0, 0, 64, 960 }; btn_left.text = "<"; - btn_left.font = font_; btn_left.on_click = [this, click]() { if (difficulty_ != GameDifficulty::EASY) { difficulty_ = static_cast(static_cast(difficulty_) - 1); @@ -43,7 +45,6 @@ void HighScores::init() auto& btn_right = ui_.root.add_child(); btn_right.transform = { 1216, 0, 64, 960 }; btn_right.text = ">"; - btn_right.font = font_; btn_right.on_click = [this, click]() { if (difficulty_ != GameDifficulty::EXTREME) { difficulty_ = static_cast(static_cast(difficulty_) + 1); @@ -54,13 +55,11 @@ void HighScores::init() auto& lbl_title = ui_.root.add_child(); lbl_title.transform = { 640, 50, 0, 0 }; - lbl_title.font = font_; lbl_title.text = "High Scores"; lbl_title.justify = asw::TextJustify::Center; auto& lbl_mode = ui_.root.add_child(); lbl_mode.transform = { 640, 150, 0, 0 }; - lbl_mode.font = font_; lbl_mode.text = ""; lbl_mode.justify = asw::TextJustify::Center; lbl_mode_ = &lbl_mode; @@ -72,7 +71,6 @@ void HighScores::update(float dt) { Scene::update(dt); ui_.update(); - controls::update_ui(ui_); if (difficulty_ == GameDifficulty::EASY) { lbl_mode_->text = "Easy"; @@ -83,11 +81,6 @@ void HighScores::update(float dt) } else if (difficulty_ == GameDifficulty::EXTREME) { lbl_mode_->text = "Extreme"; } - - // Go to menu - if (asw::input::get_action_down(controls::UI_BACK)) { - manager.set_next_scene(States::Menu); - } } void HighScores::draw() diff --git a/src/LevelSelect.cpp b/src/LevelSelect.cpp index 13ba1ac..dab27a4 100644 --- a/src/LevelSelect.cpp +++ b/src/LevelSelect.cpp @@ -23,14 +23,15 @@ void LevelSelect::init() const auto click = asw::assets::load_sample("assets/sfx/click.wav"); ui_ = asw::ui::Root(); - ui_.root.transform.size = asw::Vec2(1280, 960); + ui_.ctx.navigation = controls::navigation(); + ui_.ctx.theme.font = font; ui_.root.bg_image = background; + ui_.on_back = [this]() { manager.set_next_scene(States::Menu); }; // Add buttons auto& btn_left = ui_.root.add_child(); btn_left.transform = { 0, 0, 64, 960 }; btn_left.text = "<"; - btn_left.font = font; btn_left.on_click = [this, click]() { if (difficulty_ != GameDifficulty::EASY) { difficulty_ = static_cast(static_cast(difficulty_) - 1); @@ -49,7 +50,6 @@ void LevelSelect::init() auto& btn_right = ui_.root.add_child(); btn_right.transform = { 1216, 0, 64, 960 }; btn_right.text = ">"; - btn_right.font = font; btn_right.on_click = [this, click]() { if (difficulty_ != GameDifficulty::EXTREME) { difficulty_ = static_cast(static_cast(difficulty_) + 1); @@ -62,12 +62,6 @@ void LevelSelect::update(float dt) { Scene::update(dt); ui_.update(); - controls::update_ui(ui_); - - // Go to menu - if (asw::input::get_action_down(controls::UI_BACK)) { - manager.set_next_scene(States::Menu); - } } void LevelSelect::draw() diff --git a/src/Menu.cpp b/src/Menu.cpp index ffc14f1..e98686a 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -9,26 +9,24 @@ void Menu::init() const auto font = asw::assets::load_font("assets/fonts/jersey-10.ttf", 48); ui_ = asw::ui::Root(); - ui_.root.transform.size = asw::Vec2(1280, 960); + ui_.ctx.navigation = controls::navigation(); + ui_.ctx.theme.font = font; ui_.root.bg_image = background_; // Add buttons auto& btn_start = ui_.root.add_child(); btn_start.transform = { 320, 275, 640, 100 }; btn_start.text = "Start"; - btn_start.font = font; btn_start.on_click = [this]() { manager.set_next_scene(States::LevelSelect); }; auto& btn_highscores = ui_.root.add_child(); btn_highscores.transform = { 320, 405, 640, 100 }; btn_highscores.text = "High Scores"; - btn_highscores.font = font; btn_highscores.on_click = [this]() { manager.set_next_scene(States::HighScores); }; auto& btn_quit = ui_.root.add_child(); btn_quit.transform = { 320, 535, 640, 100 }; btn_quit.text = "Quit"; - btn_quit.font = font; btn_quit.on_click = []() { asw::core::exit(); }; } @@ -36,7 +34,6 @@ void Menu::update(float dt) { Scene::update(dt); ui_.update(); - controls::update_ui(ui_); } void Menu::draw() From 2a6a4f75eaf7a320b966968e4837326742a0346f Mon Sep 17 00:00:00 2001 From: Allan Legemaate Date: Mon, 28 Sep 2026 14:54:59 -0400 Subject: [PATCH 12/12] feat: bump to asw 0.12.1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f6648b9..2b0109d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/target) include(cmake/get_cpm.cmake) -CPMAddPackage("gh:adsgames/asw@0.12.0") +CPMAddPackage("gh:adsgames/asw@0.12.1") file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h)