From 3ecd785ac26146639203ff1ec5e845a9653c2668 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 19 Aug 2026 21:43:35 -0600 Subject: [PATCH] Rework UI lifecycle, finish renaming & cleanup --- docs/modding.md | 54 +++---- files.cmake | 4 +- .../mods/svc/{gamemode.h => game_mode.h} | 12 +- src/d/actor/d_a_alink_demo.inc | 2 +- src/d/d_bright_check.cpp | 4 +- src/d/d_file_select.cpp | 30 ++-- src/d/d_s_logo.cpp | 9 +- src/d/d_s_name.cpp | 6 +- src/d/d_save.cpp | 4 +- src/dusk/achievements.cpp | 27 ++-- src/dusk/game_mode.cpp | 88 ++++++++++ src/dusk/{gamemode.hpp => game_mode.hpp} | 44 ++--- src/dusk/gamemode.cpp | 92 ----------- src/dusk/imgui/ImGuiConsole.cpp | 4 +- src/dusk/livesplit.h | 2 +- src/dusk/mods/svc/game_mode.cpp | 140 ++++++++++++++++ src/dusk/mods/svc/gamemode.cpp | 153 ------------------ src/dusk/mods/svc/ui.cpp | 7 +- src/dusk/settings.cpp | 4 +- src/dusk/speedrun.cpp | 8 +- src/dusk/speedrun.h | 3 +- src/dusk/ui/document.cpp | 9 ++ src/dusk/ui/document.hpp | 5 + src/dusk/ui/menu_bar.cpp | 67 ++++---- src/dusk/ui/menu_bar.hpp | 5 +- src/dusk/ui/mod_window.hpp | 1 - src/dusk/ui/prelaunch.cpp | 103 ++++++------ src/dusk/ui/prelaunch.hpp | 5 +- src/dusk/ui/settings.cpp | 28 ++-- src/dusk/ui/tab_bar.cpp | 48 ++++-- src/dusk/ui/tab_bar.hpp | 6 +- src/dusk/ui/ui.cpp | 31 ++-- src/dusk/ui/ui.hpp | 4 +- src/f_ap/f_ap_game.cpp | 13 +- src/f_op/f_op_overlap_req.cpp | 2 +- src/m_Do/m_Do_Reset.cpp | 38 +---- 36 files changed, 536 insertions(+), 526 deletions(-) rename sdk/include/mods/svc/{gamemode.h => game_mode.h} (76%) create mode 100644 src/dusk/game_mode.cpp rename src/dusk/{gamemode.hpp => game_mode.hpp} (70%) delete mode 100644 src/dusk/gamemode.cpp create mode 100644 src/dusk/mods/svc/game_mode.cpp delete mode 100644 src/dusk/mods/svc/gamemode.cpp diff --git a/docs/modding.md b/docs/modding.md index bc58c83ba6..6fadaf55f1 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -638,14 +638,9 @@ state for the current frame. Register and unregister using `register_camera_oper ### GameModeService (`mods/svc/gamemode.h`) -Allows a mod to register a gamemode that allows the game to designate one form of gameplay (named a gamemode). This -is intended to allow large mods that change large amounts of game logic (such as a randomizer) to have explicit control -over how the game will function at certain points. When a gamemode is registered via the service, it will add an entry -to the pre-launch menu. When selected, the game will use a unique set of savefiles (designated by the `saveName` field) -to store save data while the gamemode is active. Any function pointers registered with the gamemode will be called by -dusklight when their condition is met. - -Note: for any gamemode wishing to use the vanilla set of savefiles, use `gczelda2` as the save file name. +Allows a mod to register a game mode with callbacks for key gameplay and save lifecycle events. Registered game modes +appear in the prelaunch menu. Game modes may use a unique set of saves by configuring `saveName`; leave it empty to use +the vanilla `gczelda2` save. ```cpp // An example that shows registering a gamemode with function hooks that are scoped to the gamemode being active @@ -653,18 +648,18 @@ IMPORT_SERVICE(LogService, svc_log); IMPORT_SERVICE(HookService, svc_hook); IMPORT_SERVICE(GameModeService, svc_gamemode); -#define MY_GAMEMODE_ID "gamemodeid" +DEFINE_HOOK(fopAcM_createItem, CreateItem); +#define MY_GAME_MODE_ID "game-mode-id" static HookAction myFunctionHook(ModContext *ctx, void *args, void *, void *) { // If we wish to have this hook only run while the gamemode is registered, we need to hook the function from the - // gamemode's onActivatedFunction, and uninstall the hook during the onDeactivatedFunction. An example is given below + // gamemode's onActivatedFunction, and uninstall the hook during the onDeactivatedFunction. Example below. + // Alternatively, check with `svc_gamemode->is_active(mod_ctx, MY_GAME_MODE_ID, &active) == MOD_OK && active`. return HOOK_CONTINUE; } -DEFINE_HOOK(fopAcM_createItem, CreateItem); - void onGameModeActivated() { // Setup the gamemode, Add any hooks that are gamemode specific // Overlay any files that are gamemode specific @@ -676,7 +671,7 @@ void onGameModeActivated() { void onGameModeDeactivated() { // Uninstall any hooks that are gamemode specific - // Remove overlays to any files that are gamemode specific + // Remove any file overlays that are gamemode specific ModResult result = mods::hook_uninstall(); if (result != MOD_OK) { svc_log->error(mod_ctx, "failed to uninstall CreateItem hook"); @@ -687,27 +682,24 @@ void onSaveLoaded() { // This function will be invoked by the game as a save is loaded } -// Register the gamemode when the mod is initialized -const GameModeDesc gamemodeDesc = { - .gamemodeId = MY_GAMEMODE_ID, - .fullName = "GameMode Name", - // The save name should be something that other gamemodes will not try to use, so appending your name to it - // is reccomended. Note: it is limited to 31 characters long - .saveName = "my-unique-save_developer-name", - .onActivatedFunction = onGameModeActivated, // Called when the gamemode is selected on the prelaunch menu (or is launched) - .onDeactivatedFunction = onGameModeDeactivated, // Called when the gamemode is deselected on the prelaunch menu (or the mod is disabled) +// Register the game mode when the mod is initialized. +const GameModeDesc gameModeDesc = { + .gameModeId = MY_GAME_MODE_ID, + .fullName = "My Game Mode", + .saveName = "my-unique-save", // Custom save names should be unique, max 31 chars + .onActivatedFunction = onGameModeActivated, // Called when the game mode is selected or launched + .onDeactivatedFunction = onGameModeDeactivated, // Called when it is deselected or the mod is disabled .onPlayFunction = nullptr, // Called when "Play" is pressed on the prelaunch menu .onSaveLoadedFunction = onSaveLoaded, // Called when a save is loaded .onNewSaveFunction = nullptr, // Called after a new savefile is created .onNewSaveSelectFunction = nullptr, // Called during the flow before the file name select is ran (see below) .onGameResetFunction = nullptr, // Called when the game is reset - .onTickFunction = nullptr, // Called every game tick while the gamemode is active + .onTickFunction = nullptr, // Called every game tick while the game mode is active }; -svc_gamemode->register_gamemode(mod_ctx, &gamemodeDesc); +svc_gamemode->register_game_mode(mod_ctx, &gameModeDesc); ``` -Within the gamemode service, a gamemode can also request to load a UI for per-save file settings when the button to -create a new file is pressed. +A game mode can also open UI for per-save settings when creating a new file. ```cpp IMPORT_SERVICE(GameModeService, svc_gamemode); @@ -753,13 +745,13 @@ void onNewSaveSelect(bool *out_proceedToNameSelect, bool *out_returnToFileSelect svc_ui->window_push(mod_ctx, &desc, &windowHandle); } -const GameModeDesc gamemodeDesc = { - .gamemodeId = "my-gamemode-id", - .fullName = "My GameMode", - .saveName = "my-gamemode-save", +const GameModeDesc gameModeDesc = { + .gameModeId = "my-game-mode-id", + .fullName = "My Game Mode", + .saveName = "my-unique-save", .onNewSaveSelectFunction = onNewSaveSelect, }; -svc_gamemode->register_gamemode(mod_ctx, &gamemodeDesc); +svc_gamemode->register_game_mode(mod_ctx, &gameModeDesc); ``` diff --git a/files.cmake b/files.cmake index 3febff731d..da97c3779f 100644 --- a/files.cmake +++ b/files.cmake @@ -1434,7 +1434,7 @@ set(DUSK_FILES src/dusk/extras.c src/dusk/frame_interpolation.cpp src/dusk/game_clock.cpp - src/dusk/gamemode.cpp + src/dusk/game_mode.cpp src/dusk/gamepad_color.cpp src/dusk/globals.cpp src/dusk/gyro.cpp @@ -1502,7 +1502,7 @@ set(DUSK_FILES src/dusk/mods/svc/texture.cpp src/dusk/mods/svc/ui.cpp src/dusk/mods/svc/ui.hpp - src/dusk/mods/svc/gamemode.cpp + src/dusk/mods/svc/game_mode.cpp src/dusk/mods/svc/window.cpp src/dusk/mods/svc/window.hpp src/dusk/mods/svc/save.cpp diff --git a/sdk/include/mods/svc/gamemode.h b/sdk/include/mods/svc/game_mode.h similarity index 76% rename from sdk/include/mods/svc/gamemode.h rename to sdk/include/mods/svc/game_mode.h index 1073c5bdd8..c497836049 100644 --- a/sdk/include/mods/svc/gamemode.h +++ b/sdk/include/mods/svc/game_mode.h @@ -8,13 +8,13 @@ #define GAMEMODE_SERVICE_MINOR 0u typedef struct { - const char* gamemodeId; + const char* gameModeId; const char* fullName; - const char saveName[32]; // Should be unique. GCI Filenames are limited to 31 characters - void (*onActivatedFunction)(); // Called when the gamemode is selected - void (*onDeactivatedFunction)(); // Called when the gamemode is deselected + const char saveName[32]; // Empty uses default (gczelda2); max 31 chars + void (*onActivatedFunction)(); // Called when the game mode is selected + void (*onDeactivatedFunction)(); // Called when the game mode is deselected void (*onPlayFunction)(); // Called when play is pressed on the prelaunch menu - void (*onSaveLoadedFunction)(); // Called whenever a savefile is loaded + void (*onSaveLoadedFunction)(); // Called whenever a save file is loaded void (*onNewSaveFunction)(); // Called when a new save is created void (*onNewSaveSelectFunction)(bool* out_proceedToNameSelect, bool* out_returnToFileSelect); // Set out_proceedToNameSelect to true once any UI flows are @@ -27,7 +27,7 @@ typedef struct GameModeService { ServiceHeader header; ModResult (*register_game_mode)(ModContext* ctx, const GameModeDesc* desc); ModResult (*unregister_game_mode)(ModContext* ctx, const char* id); - ModResult (*is_active)(ModContext* ctx, const char* gamemodeId, bool* out_active); + ModResult (*is_active)(ModContext* ctx, const char* gameModeId, bool* out_active); } GameModeService; #ifdef __cplusplus diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index 415893e8bf..e8812c6e41 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -24,10 +24,10 @@ #include #ifdef TARGET_PC +#include "dusk/game_mode.hpp" #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/settings.h" #include "dusk/speedrun.h" -#include "dusk/gamemode.hpp" #include "dusk/version.hpp" #endif diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp index 725bbc019c..7ec4f09b10 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -13,10 +13,10 @@ #ifdef TARGET_PC #include -#include "dusk/livesplit.h" +#include "dusk/game_mode.hpp" #include "dusk/imgui/ImGuiConsole.hpp" +#include "dusk/livesplit.h" #include "dusk/speedrun.h" -#include "dusk/gamemode.hpp" #endif #include "dusk/version.hpp" diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index 09fc04c5dc..258aea3b90 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -26,10 +26,10 @@ #include "dusk/version.hpp" #if TARGET_PC +#include "dusk/game_mode.hpp" #include "dusk/menu_pointer.h" -#include "helpers/string.hpp" #include "dusk/mods/svc/save.hpp" -#include "dusk/gamemode.hpp" +#include "helpers/string.hpp" namespace { constexpr u8 pointer_target(u8 group, u8 index) noexcept { @@ -1315,11 +1315,13 @@ void dFile_select_c::selectDataNameMove() { bool isModoruTxtDisp = modoruTxtDispAnm(); #ifdef TARGET_PC - const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode(); - if (gamemode) { - if (isHeaderTxtChange == true && isFileRecScale == true && isModoruTxtDisp == true) { + const dusk::gamemode::GameMode* gameMode = + dusk::gamemode::getGameModeManager().getCurrentGameMode(); + if (gameMode) { + if (isHeaderTxtChange == true && isFileRecScale == true && isModoruTxtDisp == true) { if (mGameModeSaveStartBuildUi) { - gamemode->invokeOnNewSaveSelectFunction(&mGameModeProceedToNameSelect, &mGameModeReturnToFileSelect); + gameMode->invokeOnNewSaveSelectFunction( + &mGameModeProceedToNameSelect, &mGameModeReturnToFileSelect); mGameModeSaveStartBuildUi = false; } if (mGameModeReturnToFileSelect) { @@ -1430,9 +1432,10 @@ void dFile_select_c::menuSelectStart() { #if TARGET_PC dusk::mods::svc::save_slot_loaded(mSelectNum, &mSaveData[mSelectNum]); - const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode(); - if (gamemode) { - gamemode->invokeOnSaveLoadedFunction(); + const dusk::gamemode::GameMode* gameMode = + dusk::gamemode::getGameModeManager().getCurrentGameMode(); + if (gameMode) { + gameMode->invokeOnSaveLoadedFunction(); } #endif } else if (mSelectMenuNum == 0) { @@ -1787,10 +1790,11 @@ void dFile_select_c::nameInput2() { mIsSelectEnd = true; #if TARGET_PC dusk::mods::svc::save_slot_new(mSelectNum); - const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode(); - if (gamemode) { - gamemode->invokeOnNewSaveFunction(); - gamemode->invokeOnSaveLoadedFunction(); + const dusk::gamemode::GameMode* gameMode = + dusk::gamemode::getGameModeManager().getCurrentGameMode(); + if (gameMode) { + gameMode->invokeOnNewSaveFunction(); + gameMode->invokeOnSaveLoadedFunction(); } #endif mDataSelProc = DATASELPROC_NEXT_MODE_WAIT; diff --git a/src/d/d_s_logo.cpp b/src/d/d_s_logo.cpp index 874e9cf1d6..48c7519ded 100644 --- a/src/d/d_s_logo.cpp +++ b/src/d/d_s_logo.cpp @@ -24,11 +24,11 @@ #include "JSystem/JUtility/JUTConsole.h" #ifdef TARGET_PC +#include "dusk/game_mode.hpp" #include "dusk/language.hpp" #include "dusk/logging.h" #include "dusk/main.h" #include "dusk/mods/svc/save.hpp" -#include "dusk/gamemode.hpp" #include "dusk/version.hpp" #include "m_Do/m_Do_MemCard.h" #endif @@ -809,9 +809,10 @@ void dScnLogo_c::nextSceneChange() { if (status == 1) { dusk::mods::svc::save_slot_loaded( saveSlot, buf + saveSlot * SAVEDATA_SIZE); - const dusk::gamemode::GameMode* gamemode = dusk::gamemode::getGameModeManager().getCurrentGameMode(); - if (gamemode) { - gamemode->invokeOnSaveLoadedFunction(); + const dusk::gamemode::GameMode* gameMode = + dusk::gamemode::getGameModeManager().getCurrentGameMode(); + if (gameMode) { + gameMode->invokeOnSaveLoadedFunction(); } } diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index 24f92f3ea3..de66b0317d 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -18,13 +18,13 @@ #include "m_Do/m_Do_mtx.h" #ifdef TARGET_PC +#include "dusk/autosave.h" +#include "dusk/game_mode.hpp" #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/livesplit.h" #include "dusk/memory.h" -#include "dusk/speedrun.h" #include "dusk/settings.h" -#include "dusk/autosave.h" -#include "dusk/gamemode.hpp" +#include "dusk/speedrun.h" #endif #if TARGET_PC diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index b078b88dc2..7caba5c9c8 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -28,9 +28,9 @@ #endif #if TARGET_PC -#include "dusk/settings.h" -#include "dusk/gamemode.hpp" #include +#include "dusk/game_mode.hpp" +#include "dusk/settings.h" #include "helpers/string.hpp" #define strcpy SafeStringCopy diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp index c838970eb5..1c8f74f004 100644 --- a/src/dusk/achievements.cpp +++ b/src/dusk/achievements.cpp @@ -1,27 +1,26 @@ #include "dusk/achievements.h" -#include "dusk/io.hpp" -#include "dusk/main.h" -#include "d/d_com_inf_game.h" -#include "d/d_item_data.h" -#include "d/d_map_path_fmap.h" -#include "d/d_stage.h" -#include "d/d_menu_fmap.h" #include "JSystem/JKernel/JKRArchive.h" -#include "d/d_meter2_info.h" #include "d/actor/d_a_alink.h" -#include "d/actor/d_a_ni.h" -#include "d/actor/d_a_npc4.h" #include "d/actor/d_a_b_gnd.h" #include "d/actor/d_a_b_ob.h" +#include "d/actor/d_a_ni.h" +#include "d/actor/d_a_npc4.h" #include "d/actor/d_a_player.h" +#include "d/d_com_inf_game.h" #include "d/d_demo.h" +#include "d/d_item_data.h" +#include "d/d_map_path_fmap.h" +#include "d/d_menu_fmap.h" +#include "d/d_meter2_info.h" +#include "d/d_stage.h" +#include "dusk/game_mode.hpp" +#include "dusk/io.hpp" +#include "dusk/logging.h" +#include "dusk/main.h" +#include "dusk/speedrun.h" #include "dusk/ui/ui.hpp" -#include "f_pc/f_pc_name.h" #include "f_op/f_op_actor_mng.h" #include "f_pc/f_pc_name.h" -#include "dusk/logging.h" -#include "dusk/gamemode.hpp" -#include "dusk/speedrun.h" #include #include diff --git a/src/dusk/game_mode.cpp b/src/dusk/game_mode.cpp new file mode 100644 index 0000000000..2e97ea4c23 --- /dev/null +++ b/src/dusk/game_mode.cpp @@ -0,0 +1,88 @@ +#include "dusk/game_mode.hpp" +#include "JSystem/JUtility/JUTGamePad.h" +#include "aurora/lib/logging.hpp" +#include "dusk/config.hpp" +#include "dusk/ui/prelaunch.hpp" +#include "m_Do/m_Do_MemCard.h" + +namespace dusk::gamemode { +namespace { +aurora::Module Log("dusk::gamemode"); +} + +GameModeManager g_GameModeManager; + +GameModeManager::GameModeManager() { + GameMode vanilla{kVanillaGameModeId, "Vanilla"}; + mRegisteredGameModes.emplace(vanilla.getId(), std::move(vanilla)); + mCurrentGameModeId = kVanillaGameModeId; +} + +void GameModeManager::setGameModeToPrevious() { + // Restore the previously selected game mode if still registered. + GameModeId id = getSettings().game.lastSelectedGameModeId; + if (!mRegisteredGameModes.contains(id)) { + setCurrentGameMode(kVanillaGameModeId); + return; + } + setCurrentGameMode(id); +} + +void GameModeManager::registerGameMode(const GameMode& gameMode) { + if (gameMode.getId().empty()) { + Log.fatal("No game mode ID specified in GameModeManager::registerGameMode"); + } + if (gameMode.getFullName().empty()) { + Log.fatal("No display name specified for game mode {}", gameMode.getId()); + } + + if (mRegisteredGameModes.contains(gameMode.getId())) { + Log.warn("Attempting to re-register existing game mode {}", gameMode.getId()); + return; + } + + mRegisteredGameModes.emplace(gameMode.getId(), gameMode); + ui::Prelaunch::refresh_menu_buttons(); +} + +void GameModeManager::unregisterGameMode(const GameModeId& gameModeId) { + const auto& it = mRegisteredGameModes.find(gameModeId); + if (it == mRegisteredGameModes.end()) { + Log.warn("Attempting to unregister unknown game mode {}", gameModeId); + return; + } + + if (mCurrentGameModeId == gameModeId) { + // Reset to prelaunch before unloading callbacks belonging to the active mod. + ui::prelaunch_state().returnToPrelaunchOnReset = true; + JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; + setCurrentGameMode(kVanillaGameModeId); + } + mRegisteredGameModes.erase(it); + ui::Prelaunch::refresh_menu_buttons(); +} + +void GameModeManager::setCurrentGameMode(const GameModeId& id) { + if (mCurrentGameModeId == id) { + return; + } + if (!mRegisteredGameModes.contains(id)) { + Log.warn("Attempting to configure unknown game mode {}", id); + return; + } + const GameMode* currentGameMode = getCurrentGameMode(); + if (currentGameMode) { + currentGameMode->invokeOnDeactivatedFunction(); + } + mCurrentGameModeId = id; + getSettings().game.lastSelectedGameModeId.setValue(id); + config::save(); + + currentGameMode = getCurrentGameMode(); + if (currentGameMode) { + mDoMemCd_SetFileName(currentGameMode->getSaveName()); + currentGameMode->invokeOnActivatedFunction(); + } +} + +} // namespace dusk::gamemode diff --git a/src/dusk/gamemode.hpp b/src/dusk/game_mode.hpp similarity index 70% rename from src/dusk/gamemode.hpp rename to src/dusk/game_mode.hpp index f2cb0db8af..b60c6498e8 100644 --- a/src/dusk/gamemode.hpp +++ b/src/dusk/game_mode.hpp @@ -1,21 +1,24 @@ #pragma once + +#include "d/d_file_select.h" + #include #include -#include "d/d_file_select.h" +#include +#include namespace dusk::gamemode { using GameModeId = std::string; constexpr const char* kVanillaGameModeId = "vanilla"; +constexpr const char* kDefaultGameModeSaveName = "gczelda2"; -// This class holds the definition for the gamemode and various function pointers to call +// Holds a game mode definition and its lifecycle callbacks. class GameMode { public: - GameMode(const GameModeId& id, const std::string& fullName, const std::string& saveName) { - mId = id; - mFullName = fullName; - mSaveName = saveName; - } + GameMode(GameModeId id, std::string fullName, std::string saveName = {}) + : mId{std::move(id)}, mFullName{std::move(fullName)}, + mSaveName{saveName.empty() ? kDefaultGameModeSaveName : std::move(saveName)} {} const GameModeId& getId() const { return mId; } const std::string& getFullName() const { return mFullName; } const std::string& getSaveName() const { return mSaveName; } @@ -53,11 +56,12 @@ public: mOnNewSaveFunction(); } } - - void invokeOnNewSaveSelectFunction(bool* out_proceedToNameSelect, bool* out_returnToFileSelect) const { + + void invokeOnNewSaveSelectFunction( + bool* out_proceedToNameSelect, bool* out_returnToFileSelect) const { if (mOnNewSaveSelectFunction) { mOnNewSaveSelectFunction(out_proceedToNameSelect, out_returnToFileSelect); - }else { + } else { *out_proceedToNameSelect = true; } } @@ -79,7 +83,8 @@ public: std::function mOnPlayFunction; std::function mOnSaveLoadedFunction; std::function mOnNewSaveFunction; - std::function mOnNewSaveSelectFunction; + std::function + mOnNewSaveSelectFunction; std::function mOnGameResetFunction; std::function mOnTickFunction; }; @@ -87,16 +92,17 @@ public: class GameModeManager { public: GameModeManager(); - void registerGameMode(const GameMode& gamemode); - void unregisterGameMode(const GameModeId& gamemodeId); + void registerGameMode(const GameMode& gameMode); + void unregisterGameMode(const GameModeId& gameModeId); const GameMode* getCurrentGameMode() const { const auto& it = mRegisteredGameModes.find(mCurrentGameModeId); - return it != mRegisteredGameModes.end() ? &it->second : &mRegisteredGameModes.at(kVanillaGameModeId); + return it != mRegisteredGameModes.end() ? &it->second : + &mRegisteredGameModes.at(kVanillaGameModeId); } bool isCurrentGameMode(const GameModeId& id) const { - const GameMode* gamemode = getCurrentGameMode(); - if (gamemode && gamemode->getId() == id) { + const GameMode* gameMode = getCurrentGameMode(); + if (gameMode && gameMode->getId() == id) { return true; } return false; @@ -104,7 +110,9 @@ public: void setCurrentGameMode(const GameModeId& id); void setGameModeToPrevious(); - std::map& getRegisteredGameModes() { return mRegisteredGameModes; } + const std::map& getRegisteredGameModes() const { + return mRegisteredGameModes; + } private: GameModeId mCurrentGameModeId; @@ -117,4 +125,4 @@ inline GameModeManager& getGameModeManager() { return g_GameModeManager; } -}; // namespace dusk::gamemode +} // namespace dusk::gamemode diff --git a/src/dusk/gamemode.cpp b/src/dusk/gamemode.cpp deleted file mode 100644 index c928aa9f7a..0000000000 --- a/src/dusk/gamemode.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "dusk/gamemode.hpp" -#include "dusk/config.hpp" -#include "JSystem/JUtility/JUTGamePad.h" -#include "aurora/lib/logging.hpp" -#include "m_Do/m_Do_MemCard.h" -#include "dusk/ui/prelaunch.hpp" - -namespace dusk::gamemode { - -GameModeManager g_GameModeManager; - -aurora::Module DuskGameModeLog("dusk::gamemode"); - -GameModeManager::GameModeManager() { - registerGameMode(GameMode(kVanillaGameModeId,"Vanilla","gczelda2")); - mCurrentGameModeId = kVanillaGameModeId; -} - -void GameModeManager::setGameModeToPrevious() { - // Gets the value from the settings of the last played gamemode id and sets that to the current gamemode (if registered) - GameModeId id = dusk::getSettings().game.lastSelectedGameModeId; - if (mRegisteredGameModes.find(id) == mRegisteredGameModes.end()) { - setCurrentGameMode(kVanillaGameModeId); - return; - } - setCurrentGameMode(id); -} - -void GameModeManager::registerGameMode(const GameMode& gamemode) { - if (gamemode.getId().empty()) { - DuskGameModeLog.fatal("No gamemode id specified in GameModeManager::registerGameMode!"); - } - if (gamemode.getSaveName().empty()) { - DuskGameModeLog.fatal("No save name provided for gamemode {}", gamemode.getId()); - } - if (gamemode.getFullName().empty()) { - DuskGameModeLog.fatal("No Name Specified for gamemode {}", gamemode.getId()); - } - - if (mRegisteredGameModes.find(gamemode.getId()) != mRegisteredGameModes.end()) { - DuskGameModeLog.warn("Attempting to register gamemode {} when it is already registered!", gamemode.getId()); - return; - } - - mRegisteredGameModes.emplace(gamemode.getId(),gamemode); - dusk::ui::Prelaunch::rebuild_menu_buttons(); -} - -void GameModeManager::unregisterGameMode(const GameModeId& gamemodeId) { - const auto& it = mRegisteredGameModes.find(gamemodeId); - if (it == mRegisteredGameModes.end()) { - DuskGameModeLog.warn( - "Attempting to unregister gamemode of id {} that isn't registered!", gamemodeId); - return; - } - - if (mCurrentGameModeId == gamemodeId) { - // We need to be careful if we are unregistering a running gamemode, the easiest way is just - // to reset the game back to title as vanilla; - ui::prelaunch_state().showPrelaunchOnReset = true; - JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; - setCurrentGameMode(kVanillaGameModeId); - } - mRegisteredGameModes.erase(it); - dusk::ui::Prelaunch::rebuild_menu_buttons(); -} - -void GameModeManager::setCurrentGameMode(const GameModeId& id) { - if (mCurrentGameModeId == id) { - return; - } - const GameMode* currentGameMode = getCurrentGameMode(); - if (currentGameMode) { - currentGameMode->invokeOnDeactivatedFunction(); - } - if (mRegisteredGameModes.find(id) == mRegisteredGameModes.end()) { - DuskGameModeLog.warn("Attempting to set current game mode to {} when it hasn't been registered!", id); - } - - mCurrentGameModeId = id; - dusk::getSettings().game.lastSelectedGameModeId.setValue(id); - dusk::config::save(); - - currentGameMode = getCurrentGameMode(); - if (currentGameMode) { - // Set the loaded save file to our gamemode's save name - mDoMemCd_SetFileName(currentGameMode->mSaveName); - currentGameMode->invokeOnActivatedFunction(); - } -} - -}; // namespace dusk::gamemode diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 6f156ef975..30cfa78021 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -9,7 +9,6 @@ #include "imgui.h" #include -#include "fmt/format.h" #include "ImGuiConsole.hpp" #include "ImGuiEngine.hpp" #include "JSystem/JUtility/JUTGamePad.h" @@ -19,14 +18,15 @@ #include "dusk/data.hpp" #include "dusk/dusk.h" #include "dusk/frame_interpolation.h" +#include "dusk/game_mode.hpp" #include "dusk/livesplit.h" #include "dusk/main.h" #include "dusk/presentation.hpp" #include "dusk/settings.h" #include "dusk/ui/ui.hpp" -#include "dusk/gamemode.hpp" #include "f_pc/f_pc_manager.h" #include "f_pc/f_pc_name.h" +#include "fmt/format.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_main.h" #include "tracy/Tracy.hpp" diff --git a/src/dusk/livesplit.h b/src/dusk/livesplit.h index b803e9b50b..3196956be6 100644 --- a/src/dusk/livesplit.h +++ b/src/dusk/livesplit.h @@ -1,7 +1,7 @@ #pragma once #include -#include "dusk/gamemode.hpp" +#include "dusk/game_mode.hpp" #include "dusk/speedrun.h" namespace dusk::speedrun { diff --git a/src/dusk/mods/svc/game_mode.cpp b/src/dusk/mods/svc/game_mode.cpp new file mode 100644 index 0000000000..089d2799af --- /dev/null +++ b/src/dusk/mods/svc/game_mode.cpp @@ -0,0 +1,140 @@ +#include "mods/svc/game_mode.h" +#include "dusk/game_mode.hpp" + +#include "config.hpp" +#include "registry.hpp" +#include "slot_map.hpp" + +#include "aurora/lib/logging.hpp" +#include "dusk/mod_loader.hpp" + +#include +#include +#include +#include +#include + +namespace dusk::mods::svc::game_mode_impl { +namespace { + +aurora::Module Log("dusk::mods::game_mode"); + +// Track which gamemodes are registered by which mods, allowing us to automatically unregister them +std::unordered_map> s_gameModesByMod; + +std::string get_mod_game_mode_id(ModContext* ctx, const std::string& id) { + // Include the mod ID to prevent clashes and normalize to lowercase + std::string fullId = id + "_" + ctx->mod->metadata.id; + std::transform(fullId.begin(), fullId.end(), fullId.begin(), + [](unsigned char c) { return std::tolower(c); }); + return fullId; +} + +void game_mode_remove_mod(LoadedMod& mod) { + const auto it = s_gameModesByMod.find(mod.metadata.id); + if (it != s_gameModesByMod.end()) { + for (const auto& id : it->second) { + gamemode::getGameModeManager().unregisterGameMode(id); + } + s_gameModesByMod.erase(it); + } +} +} // namespace + +ModResult register_game_mode(ModContext* ctx, const GameModeDesc* desc) { + std::string id; + if (!desc->gameModeId) { + Log.error("Attempted to register a game mode with a null ID"); + return MOD_ERROR; + } + id = desc->gameModeId; + if (id.empty()) { + Log.error("Attempted to register a game mode with an empty ID"); + return MOD_ERROR; + } + id = get_mod_game_mode_id(ctx, id); + + std::string fullName; + if (!desc->fullName) { + Log.warn("Game mode {} has no display name; using its ID", id); + fullName = id; + } else { + fullName = desc->fullName; + if (fullName.empty()) { + Log.warn("Game mode {} has an empty display name; using its ID", id); + fullName = id; + } + } + + gamemode::GameMode mode{id, fullName, desc->saveName}; + if (desc->onActivatedFunction) { + mode.mOnActivatedFunction = desc->onActivatedFunction; + } + if (desc->onDeactivatedFunction) { + mode.mOnDeactivatedFunction = desc->onDeactivatedFunction; + } + if (desc->onPlayFunction) { + mode.mOnPlayFunction = desc->onPlayFunction; + } + if (desc->onSaveLoadedFunction) { + mode.mOnSaveLoadedFunction = desc->onSaveLoadedFunction; + } + if (desc->onNewSaveFunction) { + mode.mOnNewSaveFunction = desc->onNewSaveFunction; + } + if (desc->onNewSaveSelectFunction) { + mode.mOnNewSaveSelectFunction = desc->onNewSaveSelectFunction; + } + if (desc->onGameResetFunction) { + mode.mOnGameResetFunction = desc->onGameResetFunction; + } + if (desc->onTickFunction) { + mode.mOnTickFunction = desc->onTickFunction; + } + + gamemode::getGameModeManager().registerGameMode(mode); + s_gameModesByMod[ctx->mod->metadata.id].push_back(id); + return MOD_OK; +} + +ModResult unregister_game_mode(ModContext* ctx, const char* id) { + std::string fullId = get_mod_game_mode_id(ctx, id); + gamemode::getGameModeManager().unregisterGameMode(fullId); + + // Remove the game mode from the service registered game modes map + auto it = s_gameModesByMod.find(ctx->mod->metadata.id); + if (it != s_gameModesByMod.end()) { + std::erase(it->second, fullId); + } + return MOD_OK; +} + +ModResult is_active(ModContext* ctx, const char* gameModeId, bool* out_active) { + *out_active = + gamemode::getGameModeManager().isCurrentGameMode(get_mod_game_mode_id(ctx, gameModeId)); + return MOD_OK; +} + +} // namespace dusk::mods::svc::game_mode_impl + +namespace dusk::mods::svc { +namespace { + +constexpr GameModeService s_gamemodeService{ + .header = SERVICE_HEADER(GameModeService, GAMEMODE_SERVICE_MAJOR, GAMEMODE_SERVICE_MINOR), + .register_game_mode = game_mode_impl::register_game_mode, + .unregister_game_mode = game_mode_impl::unregister_game_mode, + .is_active = game_mode_impl::is_active, +}; + +} // namespace + +constinit const ServiceModule g_gamemodeModule{ + .id = GAMEMODE_SERVICE_ID, + .majorVersion = GAMEMODE_SERVICE_MAJOR, + .minorVersion = GAMEMODE_SERVICE_MINOR, + .service = &s_gamemodeService, + .modDeactivating = game_mode_impl::game_mode_remove_mod, +}; + +} // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/gamemode.cpp b/src/dusk/mods/svc/gamemode.cpp deleted file mode 100644 index 0d95812abd..0000000000 --- a/src/dusk/mods/svc/gamemode.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "mods/svc/gamemode.h" -#include "dusk/gamemode.hpp" - -#include "config.hpp" -#include "registry.hpp" -#include "slot_map.hpp" - -#include "aurora/lib/logging.hpp" -#include "dusk/mod_loader.hpp" - -#include - - -namespace dusk::mods::svc::gamemode_impl { -namespace { - -aurora::Module Log("dusk::mods::gamemode"); - -// These track which gamemodes are registered by which mods, allowing us to automatically unregister them -std::unordered_map> s_gamemodesRegisteredToMods; - -std::string get_mod_gamemode_id(ModContext* ctx, const std::string& id) { - // Standardize the IDs to include the mod id (to prevent clashes) and set them as lowercase - std::string fullId = id + "_" + ctx->mod->metadata.id; - std::transform(fullId.begin(),fullId.end(),fullId.begin(),[](unsigned char c) { - return std::tolower(c); - }); - return fullId; -} - -void gamemode_remove_mod(LoadedMod& mod) { - const auto it = s_gamemodesRegisteredToMods.find(mod.metadata.id); - if (it != s_gamemodesRegisteredToMods.end()) { - for (const auto& id : it->second) { - dusk::gamemode::getGameModeManager().unregisterGameMode(id); - } - s_gamemodesRegisteredToMods.erase(it); - } -} -} // namespace - - -ModResult register_gamemode(ModContext* ctx, const GameModeDesc* desc) { - std::string id; - if (!desc->gamemodeId) { - Log.error("Attempted to register a gamemode with a null id!"); - return MOD_ERROR; - } - id = desc->gamemodeId; - if (id.empty()) { - Log.error("Attempted to register a gamemode with an empty id!"); - return MOD_ERROR; - } - id = get_mod_gamemode_id(ctx, id); // Append the mod id to the end of the gamemode id to ensure they are unique - - std::string fullName; - if (!desc->fullName) { - Log.warn("Attempted to register gamemode {} with a null full name! Defaulting to: ({})",id,id); - fullName = id; - }else{ - fullName = desc->fullName; - if (fullName.empty()) { - Log.warn("Attempted to register gamemode {} with an empty full name! Defaulting to: ({})",id,id); - fullName = id; - } - } - - std::string saveName = desc->saveName; - if (saveName.empty()) { - Log.warn("Attempted to register gamemode {} with an empty save name! Defaulting to: (gczelda2)",id); - saveName = "gczelda2"; - } - - dusk::gamemode::GameMode gamemode(id, fullName, saveName); - - if (desc->onActivatedFunction) { - gamemode.mOnActivatedFunction = desc->onActivatedFunction; - } - if (desc->onDeactivatedFunction) { - gamemode.mOnDeactivatedFunction = desc->onDeactivatedFunction; - } - if (desc->onPlayFunction) { - gamemode.mOnPlayFunction = desc->onPlayFunction; - } - if (desc->onSaveLoadedFunction) { - gamemode.mOnSaveLoadedFunction = desc->onSaveLoadedFunction; - } - if (desc->onNewSaveFunction) { - gamemode.mOnNewSaveFunction = desc->onNewSaveFunction; - } - if (desc->onNewSaveSelectFunction) { - gamemode.mOnNewSaveSelectFunction = desc->onNewSaveSelectFunction; - } - if (desc->onGameResetFunction) { - gamemode.mOnGameResetFunction = desc->onGameResetFunction; - } - if (desc->onTickFunction) { - gamemode.mOnTickFunction = desc->onTickFunction; - } - - dusk::gamemode::getGameModeManager().registerGameMode(gamemode); - - const auto it = s_gamemodesRegisteredToMods.find(ctx->mod->metadata.id); - if (it == s_gamemodesRegisteredToMods.end()) { - std::vector registeredGameModes = {id}; - s_gamemodesRegisteredToMods.emplace(ctx->mod->metadata.id, registeredGameModes); - }else { - it->second.push_back(id); - } - - return MOD_OK; -} - -ModResult unregister_gamemode(ModContext* ctx, const char* id) { - std::string fullId = get_mod_gamemode_id(ctx,id); - dusk::gamemode::getGameModeManager().unregisterGameMode(fullId); - - // Remove the gamemode from the service registered gamemodes map - auto it = s_gamemodesRegisteredToMods.find(ctx->mod->metadata.id); - if (it != s_gamemodesRegisteredToMods.end()) { - std::erase(it->second, fullId); - } - return MOD_OK; -} - -ModResult is_active(ModContext* ctx, const char* gamemodeId, bool* out_active) { - *out_active = dusk::gamemode::getGameModeManager().isCurrentGameMode(get_mod_gamemode_id(ctx,gamemodeId)); - return MOD_OK; -} - -} - -namespace dusk::mods::svc { -namespace { - -constexpr GameModeService s_gamemodeService{ - .header = SERVICE_HEADER(GameModeService, GAMEMODE_SERVICE_MAJOR, GAMEMODE_SERVICE_MINOR), - .register_game_mode = gamemode_impl::register_gamemode, - .unregister_game_mode = gamemode_impl::unregister_gamemode, - .is_active = gamemode_impl::is_active -}; - -} - -constinit const ServiceModule g_gamemodeModule{ - .id = GAMEMODE_SERVICE_ID, - .majorVersion = GAMEMODE_SERVICE_MAJOR, - .minorVersion = GAMEMODE_SERVICE_MINOR, - .service = &s_gamemodeService, - .modDeactivating = gamemode_impl::gamemode_remove_mod, -}; - -} // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/ui.cpp b/src/dusk/mods/svc/ui.cpp index b47959a930..2e48d6b45f 100644 --- a/src/dusk/mods/svc/ui.cpp +++ b/src/dusk/mods/svc/ui.cpp @@ -424,7 +424,6 @@ public: } void close() { pop(); } - void force_close() { Document::hide(true); } private: std::function m_onDestroyed; @@ -929,7 +928,7 @@ void ui_sync_menu_tabs() { } s_menuTabsDirty = false; if (aurora::rmlui::is_initialized()) { - ui::MenuBar::rebuild(); + ui::MenuBar::refresh_tabs(); } } @@ -1017,14 +1016,14 @@ void ui_remove_mod(LoadedMod& mod) { case UiSlotKind::Window: { auto* window = static_cast(slot.document); if (window != nullptr) { - window->force_close(); + window->force_hide(true); } break; } case UiSlotKind::Dialog: { auto* dialog = static_cast(slot.document); if (dialog != nullptr) { - dialog->force_close(); + dialog->force_hide(true); } break; } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index e588f67d55..16bc26e532 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -1,7 +1,7 @@ #include "dusk/settings.h" -#include "dusk/config.hpp" -#include "dusk/gamemode.hpp" #include +#include "dusk/config.hpp" +#include "dusk/game_mode.hpp" namespace dusk { diff --git a/src/dusk/speedrun.cpp b/src/dusk/speedrun.cpp index 5a6067bfcb..cf6b4068e6 100644 --- a/src/dusk/speedrun.cpp +++ b/src/dusk/speedrun.cpp @@ -1,10 +1,10 @@ #include "dusk/speedrun.h" -#include "dusk/settings.h" -#include "dusk/config.hpp" -#include "m_Do/m_Do_main.h" #include -#include "dusk/gamemode.hpp" +#include "dusk/config.hpp" +#include "dusk/game_mode.hpp" #include "dusk/livesplit.h" +#include "dusk/settings.h" +#include "m_Do/m_Do_main.h" namespace dusk::speedrun { diff --git a/src/dusk/speedrun.h b/src/dusk/speedrun.h index c765f4d060..add396726f 100644 --- a/src/dusk/speedrun.h +++ b/src/dusk/speedrun.h @@ -1,7 +1,6 @@ #pragma once #include -#include "dusk/gamemode.hpp" - +#include "dusk/game_mode.hpp" namespace dusk::speedrun { diff --git a/src/dusk/ui/document.cpp b/src/dusk/ui/document.cpp index 2b387f3fe9..adb46e6334 100644 --- a/src/dusk/ui/document.cpp +++ b/src/dusk/ui/document.cpp @@ -101,6 +101,15 @@ bool Document::focus() { return false; } +bool Document::has_focus() const { + if (mDocument == nullptr) { + return false; + } + auto* context = mDocument->GetContext(); + const auto* focused = context != nullptr ? context->GetFocusElement() : nullptr; + return focused != nullptr && focused->GetOwnerDocument() == mDocument; +} + bool Document::set_document_styles(const Rml::String& rcss) { if (rcss.empty()) { mDocumentStyleSheets = nullptr; diff --git a/src/dusk/ui/document.hpp b/src/dusk/ui/document.hpp index e7bd35947d..0e2b3360d9 100644 --- a/src/dusk/ui/document.hpp +++ b/src/dusk/ui/document.hpp @@ -20,6 +20,7 @@ public: virtual void hide(bool close); virtual void update(); virtual bool focus(); + bool has_focus() const; virtual bool visible() const; virtual bool active() const; virtual bool obscures_game() const { return false; } @@ -64,6 +65,10 @@ public: hide(true); uncover_top_document(); } + void force_hide(bool close) { + hide(close); + Document::hide(close); + } bool closed() const { return mClosed; } diff --git a/src/dusk/ui/menu_bar.cpp b/src/dusk/ui/menu_bar.cpp index 44244ec39b..44f3afa533 100644 --- a/src/dusk/ui/menu_bar.cpp +++ b/src/dusk/ui/menu_bar.cpp @@ -7,12 +7,13 @@ #include "achievements.hpp" #include "aurora/rmlui.hpp" +#include "dusk/game_mode.hpp" #include "dusk/livesplit.h" -#include "dusk/gamemode.hpp" #include "dusk/main.h" #include "dusk/mods/svc/ui.hpp" #include "dusk/settings.h" #include "dusk/speedrun.h" +#include "dusk/ui/prelaunch.hpp" #include "editor.hpp" #include "f_pc/f_pc_manager.h" #include "f_pc/f_pc_name.h" @@ -21,7 +22,6 @@ #include "mods_window.hpp" #include "settings.hpp" #include "ui.hpp" -#include "dusk/ui/prelaunch.hpp" #include "warp.hpp" #include "window.hpp" @@ -56,6 +56,20 @@ MenuBar::MenuBar() }, .autoSelect = false, }); + + // Hide document after transition completion + listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) { + if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") && + Document::visible()) + { + Document::hide(mPendingClose); + } + }); + + build_tabs(); +} + +void MenuBar::build_tabs() { mTabBar->add_tab("Settings", [this] { push(std::make_unique()); }); if (getSettings().backend.enableAdvancedSettings) { @@ -63,7 +77,7 @@ MenuBar::MenuBar() mTabBar->add_tab("Editor", [this] { push(std::make_unique()); }); } - // Only allow us to access achievements if we are playing on a gamemode that uses them + // Only allow us to access achievements if we are playing on a game mode that uses them if (dusk::gamemode::getGameModeManager().isCurrentGameMode(dusk::gamemode::kVanillaGameModeId) || dusk::gamemode::getGameModeManager().isCurrentGameMode(dusk::speedrun::kSpeedrunGameModeId)) { mTabBar->add_tab("Achievements", [this] { push(std::make_unique()); }); @@ -101,12 +115,10 @@ MenuBar::MenuBar() } dismiss(modal); if (gamemode::getGameModeManager().getRegisteredGameModes().size() > 1) { - // If we have gamemodes registered, show pre-launch on a menubar reset - prelaunch_state().showPrelaunchOnReset = true; - Document::hide(true); - }else { - hide(false); + // If game modes are registered, return to prelaunch on reset. + prelaunch_state().returnToPrelaunchOnReset = true; } + hide(false); JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; }, }, @@ -157,28 +169,19 @@ MenuBar::MenuBar() hide(false); }); } - - // Hide document after transition completion - listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) { - if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") && - Document::visible()) - { - Document::hide(mPendingClose); - } - }); } void MenuBar::show() { Document::show(); mRoot->SetAttribute("open", ""); mTabBar->set_active_tab(-1); - if (!mTabBar->focus_tab(mFocusedTabIndex)) { + if (!mTabBar->focus_tab(mFocusedTabTitle)) { mTabBar->focus(); } } void MenuBar::hide(bool close) { - mFocusedTabIndex = mTabBar->focused_tab_index(); + mFocusedTabTitle = mTabBar->focused_tab_title(); mRoot->RemoveAttribute("open"); if (close) { mPendingClose = true; @@ -248,19 +251,19 @@ bool MenuBar::focus() { return mTabBar->focus(); } -void MenuBar::rebuild() { - for (auto& doc : get_document_stack()) { - if (auto* menuBar = dynamic_cast(doc.get())) { - const bool wasVisible = menuBar->visible(); - auto next = std::make_unique(); - next->mFocusedTabIndex = menuBar->mFocusedTabIndex; - next->mWasVisible = menuBar->mWasVisible; - doc = std::move(next); - if (wasVisible) { - doc->show(); - } - break; - } +void MenuBar::refresh_tabs() { + auto* menuBar = static_cast(find_document(DocumentScope::MenuBar)); + if (menuBar == nullptr) { + return; + } + const auto focusedTitle = menuBar->mTabBar->focused_tab_title(); + if (!focusedTitle.empty()) { + menuBar->mFocusedTabTitle = focusedTitle; + } + menuBar->mTabBar->clear_tabs(); + menuBar->build_tabs(); + if (menuBar->visible() && !menuBar->mTabBar->focus_tab(menuBar->mFocusedTabTitle)) { + menuBar->mTabBar->focus(); } } diff --git a/src/dusk/ui/menu_bar.hpp b/src/dusk/ui/menu_bar.hpp index 29ce2199c5..44b4ee77cd 100644 --- a/src/dusk/ui/menu_bar.hpp +++ b/src/dusk/ui/menu_bar.hpp @@ -21,12 +21,13 @@ public: bool focus() override; bool visible() const override; - static void rebuild(); + static void refresh_tabs(); protected: bool handle_nav_command(Rml::Event& event, NavCommand cmd) override; private: + void build_tabs(); void update_safe_area() noexcept; Rml::Element* mRoot; @@ -34,7 +35,7 @@ private: std::unique_ptr