From f7a687b1470c32867344d0a5f2cd1ac7c0c29926 Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Wed, 31 Jul 2024 22:29:21 +0200 Subject: [PATCH] Various changes - Add "Pikachu Bed" and "Tentacool Doll" decoration unlock options for gen 2 - bugfix for unsafe behaviour - attempt to make the RESET button work for cartridge switching. It failed though. (not reliable). Therefore I just added a warning message instead. - Work on new ScrollWidget for Credits/About screen (Work In Progress) --- include/core/Application.h | 2 + include/core/common.h | 19 ++ include/menu/MenuFunctions.h | 9 +- include/scenes/AbstractUIScene.h | 2 + include/scenes/MenuScene.h | 3 +- include/scenes/SceneManager.h | 2 +- include/scenes/SceneWithDialogWidget.h | 1 + include/transferpak/TransferPakManager.h | 3 +- include/widget/DialogWidget.h | 4 +- include/widget/ListItemFiller.h | 15 +- include/widget/MenuItemWidget.h | 2 +- include/widget/ScrollWidget.h | 115 +++++++++ src/core/Application.cpp | 23 ++ src/core/common.cpp | 20 ++ src/menu/MenuEntries.cpp | 20 ++ src/menu/MenuFunctions.cpp | 54 +++- src/scenes/AbstractUIScene.cpp | 4 +- src/scenes/InitTransferPakScene.cpp | 9 +- src/scenes/MenuScene.cpp | 24 +- src/scenes/SceneManager.cpp | 7 +- src/scenes/SceneWithDialogWidget.cpp | 6 + src/transferpak/TransferPakManager.cpp | 40 ++- src/widget/DialogWidget.cpp | 14 +- src/widget/MenuItemWidget.cpp | 14 +- src/widget/ScrollWidget.cpp | 310 +++++++++++++++++++++++ 25 files changed, 676 insertions(+), 46 deletions(-) create mode 100644 include/widget/ScrollWidget.h create mode 100644 src/widget/ScrollWidget.cpp diff --git a/include/core/Application.h b/include/core/Application.h index 8fa3202..5beab9d 100755 --- a/include/core/Application.h +++ b/include/core/Application.h @@ -16,6 +16,8 @@ public: void init(); void run(); + + void onResetInterrupt(); protected: private: RDPQGraphics graphics_; diff --git a/include/core/common.h b/include/core/common.h index 6e3be83..9608691 100755 --- a/include/core/common.h +++ b/include/core/common.h @@ -1,6 +1,21 @@ #ifndef _CORE_COMMON_H #define _CORE_COMMON_H +typedef struct Point +{ + int x; + int y; +} Point; + +typedef struct FloatPoint +{ + float x; + float y; +} FloatPoint; + +typedef Point Vector; +typedef FloatPoint FloatVector; + typedef struct Dimensions { int width; @@ -27,6 +42,10 @@ bool isZeroSizeRectangle(const Rectangle& rect); */ Rectangle addOffset(const Rectangle& a, const Rectangle& b); +bool isPointInsideRectangle(const Rectangle& a, const Point& p); + +bool doRectanglesOverlap(const Rectangle& a, const Rectangle& b); + /** * @brief Extract a Dimensions struct from a Rectangle that only contains the width and height */ diff --git a/include/menu/MenuFunctions.h b/include/menu/MenuFunctions.h index fc5a760..de53012 100755 --- a/include/menu/MenuFunctions.h +++ b/include/menu/MenuFunctions.h @@ -3,12 +3,17 @@ #include "Moves.h" +#include + // these are used to pass as a pointer to the gen1PrepareToTeachPikachu extern const Move MOVE_SURF; extern const Move MOVE_FLY; -void printMessage(void* context, const void* param); +extern const uint16_t GEN2_EVENTFLAG_DECORATION_PIKACHU_BED; +extern const uint16_t GEN2_EVENTFLAG_DECORATION_TENTACOOL_DOLL; + void activateFrameLog(void* context, const void* param); +void advanceDialog(void* context, const void* param); void goToTestScene(void* context, const void* param); @@ -19,5 +24,7 @@ void goToGen2PCNYDistributionPokemonMenu(void* context, const void* param); void gen1PrepareToTeachPikachu(void* context, const void* param); void gen1TeachPikachu(void* context, const void* param); void gen2ReceiveGSBall(void* context, const void* param); +void gen2SetEventFlag(void* context, const void* param); + #endif \ No newline at end of file diff --git a/include/scenes/AbstractUIScene.h b/include/scenes/AbstractUIScene.h index 658023d..734c718 100755 --- a/include/scenes/AbstractUIScene.h +++ b/include/scenes/AbstractUIScene.h @@ -49,6 +49,8 @@ typedef struct WidgetFocusChainSegment class AbstractUIScene : public IScene { public: + static const uint16_t MINIMUM_TIME_BETWEEN_INPUT_EVENTS; + AbstractUIScene(SceneDependencies& deps); virtual ~AbstractUIScene(); diff --git a/include/scenes/MenuScene.h b/include/scenes/MenuScene.h index 6ea598d..202c18b 100755 --- a/include/scenes/MenuScene.h +++ b/include/scenes/MenuScene.h @@ -13,6 +13,7 @@ typedef struct MenuSceneContext { MenuItemData* menuEntries; uint32_t numMenuEntries; + bool bButtonMeansUserWantsToSwitchCartridge; } MenuSceneContext; /** @@ -38,7 +39,7 @@ public: SceneDependencies& getDependencies(); - virtual void showDialog(DialogData* diagData); + void showDialog(DialogData* diagData) override; protected: virtual void setupMenu(); void setupDialog(DialogWidgetStyle& style) override; diff --git a/include/scenes/SceneManager.h b/include/scenes/SceneManager.h index 632a7b6..0cae261 100755 --- a/include/scenes/SceneManager.h +++ b/include/scenes/SceneManager.h @@ -41,7 +41,7 @@ public: * WARNING: If you specify a sceneContext, you MUST also specify a deleteContextFunc callback function pointer. * This is needed because you can't call delete() on a void*. And we need to keep the context around in the sceneHistory for as long as it needs to */ - void switchScene(SceneType sceneType, void (*deleteContextFunc)(void*) = nullptr, void* sceneContext = nullptr); + void switchScene(SceneType sceneType, void (*deleteContextFunc)(void*) = nullptr, void* sceneContext = nullptr, bool deleteHistory = false); /** * @brief Switch back to the previous scene in the history stack diff --git a/include/scenes/SceneWithDialogWidget.h b/include/scenes/SceneWithDialogWidget.h index 9260b6f..b7116ba 100755 --- a/include/scenes/SceneWithDialogWidget.h +++ b/include/scenes/SceneWithDialogWidget.h @@ -16,6 +16,7 @@ public: void render(RDPQGraphics& gfx, const Rectangle& sceneBounds) override; void advanceDialog(); + virtual void showDialog(DialogData* diagData); protected: virtual void setupFonts(); virtual void setupDialog(DialogWidgetStyle& style); diff --git a/include/transferpak/TransferPakManager.h b/include/transferpak/TransferPakManager.h index 686736c..9e442f9 100755 --- a/include/transferpak/TransferPakManager.h +++ b/include/transferpak/TransferPakManager.h @@ -42,6 +42,7 @@ public: void setPort(joypad_port_t port); bool hasTransferPak(); + bool isPoweredOn() const; bool setPower(bool on); uint8_t getStatus(); @@ -92,7 +93,7 @@ public: protected: private: joypad_port_t port_; - bool wasPoweredAtLeastOnce_; + bool isPoweredOn_; uint8_t currentSRAMBank_; uint16_t readBufferBankOffset_; uint16_t writeBufferSRAMBankOffset_; diff --git a/include/widget/DialogWidget.h b/include/widget/DialogWidget.h index a1dc6f4..8cfdf04 100755 --- a/include/widget/DialogWidget.h +++ b/include/widget/DialogWidget.h @@ -3,8 +3,7 @@ #include "widget/VerticalList.h" #include "widget/MenuItemWidget.h" -#include "core/Sprite.h" -#include "core/RDPQGraphics.h" +#include "widget/ListItemFiller.h" #define DIALOG_TEXT_SIZE 512 @@ -117,6 +116,7 @@ private: bool isAdvanceAllowed() const; VerticalList dialogOptionList_; + ListItemFiller dialogOptionListFiller_; AnimationManager& animationManager_; Rectangle bounds_; DialogWidgetStyle style_; diff --git a/include/widget/ListItemFiller.h b/include/widget/ListItemFiller.h index 609228d..951c17e 100755 --- a/include/widget/ListItemFiller.h +++ b/include/widget/ListItemFiller.h @@ -26,11 +26,7 @@ public: ~ListItemFiller() { - for(ListItemWidgetType* item : widgets_) - { - delete item; - } - widgets_.clear(); + deleteWidgets(); } void addItems(ListDataType* dataList, size_t dataListSize, const MenuItemStyle& itemStyle) @@ -44,6 +40,15 @@ public: list_.addWidget(itemWidget); } } + + void deleteWidgets() + { + for(ListItemWidgetType* item : widgets_) + { + delete item; + } + widgets_.clear(); + } protected: private: ListType& list_; diff --git a/include/widget/MenuItemWidget.h b/include/widget/MenuItemWidget.h index f3a570d..f152ac1 100755 --- a/include/widget/MenuItemWidget.h +++ b/include/widget/MenuItemWidget.h @@ -115,7 +115,7 @@ protected: /** * Executes the onConfirmAction callback (if any) */ - void execute(); + bool execute(); private: MenuItemData data_; MenuItemStyle style_; diff --git a/include/widget/ScrollWidget.h b/include/widget/ScrollWidget.h new file mode 100644 index 0000000..3d69ab9 --- /dev/null +++ b/include/widget/ScrollWidget.h @@ -0,0 +1,115 @@ +#ifndef _SCROLLWIDGET_H +#define _SCROLLWIDGET_H + +#include "widget/IWidget.h" +#include "animations/IAnimation.h" +#include + +typedef std::vector WidgetList; + +class ScrollWidget; +class AnimationManager; + +typedef struct ScrollWidgetStyle +{ + // it defines the amount of pixels we should scroll when the analog stick or dpad is used. + uint8_t scrollStep; +} ScrollWidgetStyle; + +/** + * @brief This Animation implementation is used internal in ScrollWidget + * to move the "view window"'s x and y-coordinates. + * + * It's specific to the ScrollWidget + */ +class MoveScrollWidgetWindowAnimation : public AbstractAnimation +{ +public: + MoveScrollWidgetWindowAnimation(ScrollWidget* list); + virtual ~MoveScrollWidgetWindowAnimation(); + + AnimationDistanceTimeFunctionType getDistanceTimeFunctionType() const override; + + uint32_t getDurationInMs() const override; + + void start(const Point& startPoint, const Point& endPoint); +protected: + void apply(float pos) override; +private: + ScrollWidget* list_; + Point windowStartPoint_; + Point windowEndPoint_; +}; + +/** + * @brief This is a widget that contains other widgets and can scroll through it. + * It differs from VerticalList in that ScrollWidget manages "unstructured" widgets instead of just a list in a certain direction. + * + * But this scroll widget does not allow you to select a widget and/or handle events on it. Every widget added will remain unfocused and won't handle keys + * display only. + * + */ +class ScrollWidget : public IWidget +{ +public: + ScrollWidget(AnimationManager& animManager); + virtual ~ScrollWidget(); + + bool isFocused() const override; + void setFocused(bool isFocused) override; + + bool isVisible() const override; + void setVisible(bool visible) override; + + Rectangle getBounds() const override; + void setBounds(const Rectangle& bounds) override; + Dimensions getSize() const override; + + void setStyle(const ScrollWidgetStyle& style); + + void addWidget(IWidget* widget); + void removeWidget(IWidget* widget); + + bool handleUserInput(const joypad_inputs_t& userInput) override; + void render(RDPQGraphics& gfx, const Rectangle& parentBounds) override; + + /** + * @brief Sets the view window start xy coords. + * This is used by MoveScrollWidgetWindowAnimation + */ + void setWindowStart(const Point& windowStart); + + /** + * @brief Indicates the percentage at which the current window position is at horizontally + * in relation to the entire window + */ + float getWindowProgressX() const; + /** + * @brief Indicates the percentage at which the current window position is at vertically + * in relation to the entire window + */ + float getWindowProgressY() const; +protected: +private: + /** + * @brief This function will grow the maximum dimensions of the window + * based on the specified IWidget + */ + void growWindow(IWidget* widget); + /** + * @brief Recalculates the entire width and height of the view window + * This is necessary when removing a widget + */ + void recalculateWindowSize(); + + MoveScrollWidgetWindowAnimation windowAnimation_; + WidgetList widgets_; + ScrollWidgetStyle style_; + AnimationManager& animManager_; + Rectangle bounds_; + Rectangle windowBounds_; + bool visible_; + bool focused_; +}; + +#endif \ No newline at end of file diff --git a/src/core/Application.cpp b/src/core/Application.cpp index ab4d333..1b12afa 100755 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -1,6 +1,16 @@ #include "core/Application.h" #include "scenes/IScene.h" +static Application* appInstance = nullptr; +static void resetInterruptHandler() +{ + if(!appInstance) + { + return; + } + appInstance->onResetInterrupt(); +} + Application::Application() : graphics_() , animationManager_() @@ -9,15 +19,21 @@ Application::Application() , sceneManager_(graphics_, animationManager_, fontManager_, tpakManager_) , sceneBounds_({0}) { + appInstance = this; } Application::~Application() { + tpakManager_.setPower(false); + unregister_RESET_handler(resetInterruptHandler); + graphics_.destroy(); display_close(); timer_close(); joypad_close(); + + appInstance = nullptr; } void Application::init() @@ -37,6 +53,8 @@ void Application::init() //display_init(RESOLUTION_320x240, DEPTH_16_BPP, 3, GAMMA_NONE, ANTIALIAS_RESAMPLE_FETCH_ALWAYS); sceneManager_.switchScene(SceneType::INIT_TRANSFERPAK); + + register_RESET_handler(resetInterruptHandler); } void Application::run() @@ -57,3 +75,8 @@ void Application::run() graphics_.finishAndShowFrame(); } } + +void Application::onResetInterrupt() +{ + tpakManager_.setPower(false); +} \ No newline at end of file diff --git a/src/core/common.cpp b/src/core/common.cpp index f06c0c0..41d1321 100755 --- a/src/core/common.cpp +++ b/src/core/common.cpp @@ -10,6 +10,26 @@ Rectangle addOffset(const Rectangle &a, const Rectangle &b) return Rectangle{.x = a.x + b.x, .y = a.y + b.y, .width = a.width, .height = a.height}; } +bool isPointInsideRectangle(const Rectangle& a, const Point& p) +{ + const bool isInsideHorizontally = (p.x >= a.x) && (p.x < (a.x + a.width)); + const bool isInsideVertically = (p.y >= a.y) && (p.y < (a.y + a.width)); + return (isInsideHorizontally && isInsideVertically); +} + +bool doRectanglesOverlap(const Rectangle& a, const Rectangle& b) +{ + if (a.x < (b.x + b.width) && (a.x + a.width) > b.x && + a.y + a.height > b.y && a.y < (b.y + b.height) ) + { + return true; + } + else + { + return false; + } +} + Dimensions getDimensions(const Rectangle &r) { return Dimensions{.width = r.width, .height = r.height}; diff --git a/src/menu/MenuEntries.cpp b/src/menu/MenuEntries.cpp index 3d3dbd6..c5489a1 100755 --- a/src/menu/MenuEntries.cpp +++ b/src/menu/MenuEntries.cpp @@ -28,6 +28,16 @@ MenuItemData gen2MenuEntries[] = { { .title = "PCNY Pokémon", .onConfirmAction = goToGen2PCNYDistributionPokemonMenu, + }, + { + .title = "Unlock Pikachu Bed", + .onConfirmAction = gen2SetEventFlag, + .itemParam = &GEN2_EVENTFLAG_DECORATION_PIKACHU_BED + }, + { + .title = "Unlock Tentacool Doll", + .onConfirmAction = gen2SetEventFlag, + .itemParam = &GEN2_EVENTFLAG_DECORATION_TENTACOOL_DOLL } }; @@ -45,6 +55,16 @@ MenuItemData gen2CrystalMenuEntries[] = { { .title = "Unlock GS Ball", .onConfirmAction = gen2ReceiveGSBall + }, + { + .title = "Unlock Pikachu Bed", + .onConfirmAction = gen2SetEventFlag, + .itemParam = &GEN2_EVENTFLAG_DECORATION_PIKACHU_BED + }, + { + .title = "Unlock Tentacool Doll", + .onConfirmAction = gen2SetEventFlag, + .itemParam = &GEN2_EVENTFLAG_DECORATION_TENTACOOL_DOLL } }; diff --git a/src/menu/MenuFunctions.cpp b/src/menu/MenuFunctions.cpp index 109ee71..9821aba 100755 --- a/src/menu/MenuFunctions.cpp +++ b/src/menu/MenuFunctions.cpp @@ -13,6 +13,10 @@ const Move MOVE_SURF = Move::SURF; const Move MOVE_FLY = Move::FLY; +// based on https://github.com/kwsch/PKHeX/blob/master/PKHeX.Core/Resources/text/script/gen2/flags_c_en.txt +const uint16_t GEN2_EVENTFLAG_DECORATION_PIKACHU_BED = 679; +const uint16_t GEN2_EVENTFLAG_DECORATION_TENTACOOL_DOLL = 715; + typedef struct Gen1TeachPikachuParams { Move moveType; @@ -71,9 +75,17 @@ static uint8_t gen1FindPikachuInParty(Gen1Party& party) return foundIndex; } -void printMessage(void* context, const void*) +static const char* convertGen2EventFlagToString(uint16_t eventFlagIndex) { - debugf((const char*)context); + switch(eventFlagIndex) + { + case GEN2_EVENTFLAG_DECORATION_PIKACHU_BED: + return "Pikachu Bed"; + case GEN2_EVENTFLAG_DECORATION_TENTACOOL_DOLL: + return "Tentacool doll"; + default: + return "UNKNOWN"; + } } void activateFrameLog(void* context, const void*) @@ -84,6 +96,12 @@ void activateFrameLog(void* context, const void*) gfx.triggerDebugFrame(); } +void advanceDialog(void* context, const void*) +{ + MenuScene* scene = static_cast(context); + scene->advanceDialog(); +} + void goToTestScene(void* context, const void* param) { MenuScene* scene = static_cast(context); @@ -330,5 +348,37 @@ void gen2ReceiveGSBall(void* context, const void* param) setDialogDataText(*messageData, "GS Ball event unlocked! Please go to the Golden Rod Pokémon Center and try to leave!", trainerName); + scene->showDialog(messageData); +} + +void gen2SetEventFlag(void* context, const void* param) +{ + MenuScene* scene = static_cast(context); + TransferPakManager& tpakManager = scene->getDependencies().tpakManager; + TransferPakRomReader romReader(tpakManager); + TransferPakSaveManager saveManager(tpakManager); + Gen2GameReader gameReader(romReader, saveManager, static_cast(scene->getDependencies().specificGenVersion)); + DialogData* messageData = new DialogData{ + .shouldDeleteWhenDone = true + }; + const uint16_t eventFlagIndex = *static_cast(param); + + tpakManager.setRAMEnabled(true); + + const char* trainerName = gameReader.getTrainerName(); + if(gameReader.getEventFlag(eventFlagIndex)) + { + setDialogDataText(*messageData, "%s already has %s!", trainerName, convertGen2EventFlagToString(eventFlagIndex)); + } + else + { + gameReader.setEventFlag(eventFlagIndex, true); + gameReader.finishSave(); + tpakManager.finishWrites(); + + setDialogDataText(*messageData, "%s has unlocked %s!", trainerName, convertGen2EventFlagToString(eventFlagIndex)); + } + + tpakManager.setRAMEnabled(false); scene->showDialog(messageData); } \ No newline at end of file diff --git a/src/scenes/AbstractUIScene.cpp b/src/scenes/AbstractUIScene.cpp index 3e56c07..633fd24 100755 --- a/src/scenes/AbstractUIScene.cpp +++ b/src/scenes/AbstractUIScene.cpp @@ -2,7 +2,7 @@ #include "core/DragonUtils.h" #include "widget/IWidget.h" -static uint16_t minimumTimeBetweenInputEventsInMs = 150; +const uint16_t AbstractUIScene::MINIMUM_TIME_BETWEEN_INPUT_EVENTS = 150; AbstractUIScene::AbstractUIScene(SceneDependencies& deps) : deps_(deps) @@ -23,7 +23,7 @@ void AbstractUIScene::processUserInput() } const uint64_t now = get_ticks(); - if(TICKS_TO_MS(now - lastInputHandleTime_) < minimumTimeBetweenInputEventsInMs) + if(TICKS_TO_MS(now - lastInputHandleTime_) < MINIMUM_TIME_BETWEEN_INPUT_EVENTS) { // not enough time has passed since last handled input event. Ignore return; diff --git a/src/scenes/InitTransferPakScene.cpp b/src/scenes/InitTransferPakScene.cpp index 855c132..8dd3326 100755 --- a/src/scenes/InitTransferPakScene.cpp +++ b/src/scenes/InitTransferPakScene.cpp @@ -77,7 +77,8 @@ void InitTransferPakScene::onDialogDone() { menuContext = new MenuSceneContext({ .menuEntries = gen1MenuEntries, - .numMenuEntries = static_cast(gen1MenuEntriesSize / sizeof(gen1MenuEntries[0])) + .numMenuEntries = static_cast(gen1MenuEntriesSize / sizeof(gen1MenuEntries[0])), + .bButtonMeansUserWantsToSwitchCartridge = true }); } else if(gen2Type != Gen2GameType::INVALID) @@ -86,14 +87,16 @@ void InitTransferPakScene::onDialogDone() { menuContext = new MenuSceneContext({ .menuEntries = gen2CrystalMenuEntries, - .numMenuEntries = static_cast(gen2CrystalMenuEntriesSize / sizeof(gen2CrystalMenuEntries[0])) + .numMenuEntries = static_cast(gen2CrystalMenuEntriesSize / sizeof(gen2CrystalMenuEntries[0])), + .bButtonMeansUserWantsToSwitchCartridge = true }); } else { menuContext = new MenuSceneContext({ .menuEntries = gen2MenuEntries, - .numMenuEntries = static_cast(gen2MenuEntriesSize / sizeof(gen2MenuEntries[0])) + .numMenuEntries = static_cast(gen2MenuEntriesSize / sizeof(gen2MenuEntries[0])), + .bButtonMeansUserWantsToSwitchCartridge = true }); } } diff --git a/src/scenes/MenuScene.cpp b/src/scenes/MenuScene.cpp index 40f5927..6d53187 100755 --- a/src/scenes/MenuScene.cpp +++ b/src/scenes/MenuScene.cpp @@ -1,6 +1,8 @@ #include "scenes/MenuScene.h" #include "core/FontManager.h" #include "scenes/SceneManager.h" +#include "transferpak/TransferPakManager.h" +#include "menu/MenuFunctions.h" #include @@ -78,7 +80,7 @@ void MenuScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds) .fontId = arialId_, .fontStyleId = fontStyleWhiteId_ }; - gfx.drawText(Rectangle{40, 10, 280, 16}, "PokeMe64 by risingPhil. Very early version...", renderSettings); + gfx.drawText(Rectangle{40, 10, 280, 16}, "PokeMe64 by risingPhil. Version 0.1", renderSettings); } bool MenuScene::handleUserInput(joypad_port_t port, const joypad_inputs_t& inputs) @@ -98,7 +100,22 @@ bool MenuScene::handleUserInput(joypad_port_t port, const joypad_inputs_t& input { // b button release occurred. Switch back to previous scene. bButtonPressed_ = false; - deps_.sceneManager.goBackToPreviousScene(); + + if(context_->bButtonMeansUserWantsToSwitchCartridge) + { + DialogData* diag = new DialogData{ + .shouldDeleteWhenDone = true + }; + + setDialogDataText(*diag, "Please turn the console off to switch gameboy cartridges!"); + + showDialog(diag); + } + else + { + // now do the actual switch back to the previous scene + deps_.sceneManager.goBackToPreviousScene(); + } return true; } return false; @@ -199,10 +216,9 @@ void MenuScene::setupDialog(DialogWidgetStyle& style) void MenuScene::showDialog(DialogData* diagData) { + SceneWithDialogWidget::showDialog(diagData); menuList_.setVisible(false); cursorWidget_.setVisible(false); - dialogWidget_.setVisible(true); - dialogWidget_.setData(diagData); setFocusChain(&dialogFocusChainSegment_); } diff --git a/src/scenes/SceneManager.cpp b/src/scenes/SceneManager.cpp index 1d6aadd..664d3d0 100755 --- a/src/scenes/SceneManager.cpp +++ b/src/scenes/SceneManager.cpp @@ -29,11 +29,16 @@ SceneManager::~SceneManager() unloadScene(scene_); } -void SceneManager::switchScene(SceneType type, void (*deleteContextFunc)(void*), void* sceneContext) +void SceneManager::switchScene(SceneType type, void (*deleteContextFunc)(void*), void* sceneContext, bool deleteHistory) { newSceneType_ = type; newSceneContext_ = sceneContext; + if(deleteHistory) + { + clearHistory(); + } + sceneHistory_.push_back(SceneHistorySegment{ .type = type, .context = sceneContext, diff --git a/src/scenes/SceneWithDialogWidget.cpp b/src/scenes/SceneWithDialogWidget.cpp index b072a99..9c3fc97 100755 --- a/src/scenes/SceneWithDialogWidget.cpp +++ b/src/scenes/SceneWithDialogWidget.cpp @@ -73,6 +73,12 @@ void SceneWithDialogWidget::advanceDialog() dialogWidget_.advanceDialog(); } +void SceneWithDialogWidget::showDialog(DialogData* diagData) +{ + dialogWidget_.setVisible(true); + dialogWidget_.setData(diagData); +} + void SceneWithDialogWidget::setupFonts() { arialId_ = deps_.fontManager.getFont("rom://Arial.font64"); diff --git a/src/transferpak/TransferPakManager.cpp b/src/transferpak/TransferPakManager.cpp index f910add..afc120e 100755 --- a/src/transferpak/TransferPakManager.cpp +++ b/src/transferpak/TransferPakManager.cpp @@ -10,7 +10,7 @@ static const uint16_t sramBankStartGBAddress = 0xA000; TransferPakManager::TransferPakManager() : port_(JOYPAD_PORT_1) - , wasPoweredAtLeastOnce_(false) + , isPoweredOn_(false) , currentSRAMBank_(0) , readBufferBankOffset_(0xFFFF) , writeBufferSRAMBankOffset_(0xFFFF) @@ -30,8 +30,11 @@ joypad_port_t TransferPakManager::getPort() const void TransferPakManager::setPort(joypad_port_t port) { + if(isPoweredOn_) + { + setPower(false); + } port_ = port; - wasPoweredAtLeastOnce_ = false; } bool TransferPakManager::hasTransferPak() @@ -47,31 +50,48 @@ bool TransferPakManager::hasTransferPak() return (type == JOYPAD_ACCESSORY_TYPE_TRANSFER_PAK); } +bool TransferPakManager::isPoweredOn() const +{ + return isPoweredOn_; +} + bool TransferPakManager::setPower(bool on) { + uint8_t status; int ret; - if(!wasPoweredAtLeastOnce_) + if(on) { - if(!on) - { - return true; - } ret = tpak_init(static_cast(port_)); if(ret) { debugf("[TransferPakManager]: %s: tpak_init got error %d\r\n", __FUNCTION__, ret); } - wasPoweredAtLeastOnce_ = true; + + status = getStatus(); + while(!(status | TPAK_STATUS_READY)) + { + debugf("[TransferPakManager]: %s: ERROR: transfer pak not ready yet. Current status is %hu\r\n", __FUNCTION__, status); + status = getStatus(); + } + isPoweredOn_ = true; } else { - ret = tpak_set_power(port_, on); + ret = tpak_set_access(port_, false); if(ret) { - debugf("[TransferPakManager]: %s: tpak_set_power got error %d\r\n", __FUNCTION__, ret); + debugf("[TransferPakManager]: %s: tpak_set_access got error %d\r\n", __FUNCTION__, ret); } + + ret = tpak_set_power(port_, false); + if(ret) + { + debugf("[TransferPakManager]: %s: tpak_set_access got error %d\r\n", __FUNCTION__, ret); + } + isPoweredOn_ = false; } + return (!ret); } diff --git a/src/widget/DialogWidget.cpp b/src/widget/DialogWidget.cpp index 9287ab2..f4371da 100755 --- a/src/widget/DialogWidget.cpp +++ b/src/widget/DialogWidget.cpp @@ -1,5 +1,4 @@ #include "widget/DialogWidget.h" -#include "widget/ListItemFiller.h" #include "core/RDPQGraphics.h" #include @@ -33,6 +32,7 @@ static void releaseEntry(DialogData* data, bool releaseAllEntries) DialogWidget::DialogWidget(AnimationManager& animationManager) : dialogOptionList_(animationManager) + , dialogOptionListFiller_(dialogOptionList_) , animationManager_(animationManager) , bounds_({0}) , style_({0}) @@ -76,6 +76,8 @@ void DialogWidget::setStyle(const DialogWidgetStyle& style) void DialogWidget::setData(DialogData* data) { dialogOptionList_.clearWidgets(); + // the filler is the owner of the widgets. They need to be properly destroyed + dialogOptionListFiller_.deleteWidgets(); dialogOptionList_.setVisible(false); dialogOptionList_.setFocused(false); releaseEntry(data_, true); @@ -84,9 +86,7 @@ void DialogWidget::setData(DialogData* data) if(data_ && data->options.items) { - ListItemFiller filler(dialogOptionList_); - - filler.addItems(data->options.items, data->options.number, style_.dialogOptions.style); + dialogOptionListFiller_.addItems(data->options.items, data->options.number, style_.dialogOptions.style); dialogOptionList_.setVisible(true); dialogOptionList_.setFocused(focused_); } @@ -171,13 +171,13 @@ void DialogWidget::advanceDialog() releaseEntry(oldEntry, false); dialogOptionList_.clearWidgets(); + // the filler is the owner of the widgets. They need to be properly destroyed + dialogOptionListFiller_.deleteWidgets(); dialogOptionList_.setFocused(false); dialogOptionList_.setVisible(false); if(data_->options.items) { - ListItemFiller filler(dialogOptionList_); - - filler.addItems(data_->options.items, data_->options.number, style_.dialogOptions.style); + dialogOptionListFiller_.addItems(data_->options.items, data_->options.number, style_.dialogOptions.style); dialogOptionList_.setVisible(true); dialogOptionList_.setFocused(focused_); } diff --git a/src/widget/MenuItemWidget.cpp b/src/widget/MenuItemWidget.cpp index 9057201..7519431 100755 --- a/src/widget/MenuItemWidget.cpp +++ b/src/widget/MenuItemWidget.cpp @@ -3,7 +3,7 @@ MenuItemWidget::MenuItemWidget() : data_() - , style_() + , style_({0}) , focused_(false) , visible_(true) , aButtonPressed_(false) @@ -70,9 +70,8 @@ bool MenuItemWidget::handleUserInput(const joypad_inputs_t& userInput) } else if(aButtonPressed_) { - execute(); aButtonPressed_ = false; - return true; + return execute(); } return false; } @@ -104,7 +103,12 @@ void MenuItemWidget::render(RDPQGraphics& gfx, const Rectangle& parentBounds) gfx.drawText(myBounds, data_.title, (focused_) ? style_.titleFocused : style_.titleNotFocused); } -void MenuItemWidget::execute() +bool MenuItemWidget::execute() { - data_.onConfirmAction(data_.context, data_.itemParam); + if(data_.onConfirmAction) + { + data_.onConfirmAction(data_.context, data_.itemParam); + return true; + } + return false; } \ No newline at end of file diff --git a/src/widget/ScrollWidget.cpp b/src/widget/ScrollWidget.cpp new file mode 100644 index 0000000..a6cca5f --- /dev/null +++ b/src/widget/ScrollWidget.cpp @@ -0,0 +1,310 @@ +#include "widget/ScrollWidget.h" +#include "core/common.h" +#include "scenes/AbstractUIScene.h" +#include "animations/AnimationManager.h" +#include "core/RDPQGraphics.h" + +#include + +static uint8_t ANALOG_STICK_MIN_THRESHOLD = 30; +static uint8_t ANALOG_STICK_MAX_THRESHOLD = 60; + +static FloatVector determineScrollDirection(const joypad_inputs_t& userInput) +{ + // this value should end up giving a semi-normalized vector when used for both x and y coords + const float DPAD_2_DIRECTIONS_PRESSED_COORD = 0.7071f; + const float STICK_RANGE_UNIT = 1.f / (ANALOG_STICK_MAX_THRESHOLD - ANALOG_STICK_MIN_THRESHOLD); + + if(userInput.btn.d_left) + { + if(userInput.btn.d_up) + { + return FloatPoint{ -DPAD_2_DIRECTIONS_PRESSED_COORD, -DPAD_2_DIRECTIONS_PRESSED_COORD}; + } + else if(userInput.btn.d_down) + { + return FloatPoint{ -DPAD_2_DIRECTIONS_PRESSED_COORD, DPAD_2_DIRECTIONS_PRESSED_COORD}; + } + else + { + return FloatPoint{-1.f, 0.f}; + } + } + else if(userInput.btn.d_right) + { + if(userInput.btn.d_up) + { + return FloatPoint{ DPAD_2_DIRECTIONS_PRESSED_COORD, -DPAD_2_DIRECTIONS_PRESSED_COORD}; + } + else if(userInput.btn.d_down) + { + return FloatPoint{ DPAD_2_DIRECTIONS_PRESSED_COORD, DPAD_2_DIRECTIONS_PRESSED_COORD}; + } + else + { + return FloatPoint{1.f, 0.f}; + } + } + else if(userInput.btn.d_up) + { + return FloatPoint{0.f, -1.f}; + } + else if(userInput.btn.d_down) + { + return FloatPoint{0.f, 1.f}; + } + else + { + // analog stick + int8_t absXVal = static_cast(abs(userInput.stick_x)); + int8_t absYVal = static_cast(abs(userInput.stick_y)); + if(absXVal < ANALOG_STICK_MIN_THRESHOLD && absYVal < ANALOG_STICK_MIN_THRESHOLD) + { + return FloatPoint{0.f, 0.f}; + } + + // clamp the x and y values to ANALOG_STICK_MAX_THRESHOLD + absXVal = (absXVal > ANALOG_STICK_MAX_THRESHOLD) ? ANALOG_STICK_MAX_THRESHOLD : absXVal; + absYVal = (absYVal > ANALOG_STICK_MAX_THRESHOLD) ? ANALOG_STICK_MAX_THRESHOLD : absYVal; + + // remove the dead zone and map the resulting range to 0.f-1.f + float resultX = static_cast(absXVal - ANALOG_STICK_MIN_THRESHOLD) * STICK_RANGE_UNIT; + float resultY = static_cast(absYVal - ANALOG_STICK_MIN_THRESHOLD) * STICK_RANGE_UNIT; + + if(userInput.stick_x < 0) + { + resultX = -resultX; + } + if(userInput.stick_y > 0) + { + // up in the UI means negative y values + resultY = -resultY; + } + return FloatPoint{resultX, resultY}; + } + + return FloatPoint{0.f, 0.f}; +} + +MoveScrollWidgetWindowAnimation::MoveScrollWidgetWindowAnimation(ScrollWidget* list) + : AbstractAnimation(1.f) + , list_(list) + , windowStartPoint_({0}) + , windowEndPoint_({0}) +{ +} + +MoveScrollWidgetWindowAnimation::~MoveScrollWidgetWindowAnimation() +{ +} + +AnimationDistanceTimeFunctionType MoveScrollWidgetWindowAnimation::getDistanceTimeFunctionType() const +{ + return AnimationDistanceTimeFunctionType::LINEAR; +} + +uint32_t MoveScrollWidgetWindowAnimation::getDurationInMs() const +{ + return AbstractUIScene::MINIMUM_TIME_BETWEEN_INPUT_EVENTS; +} + +void MoveScrollWidgetWindowAnimation::start(const Point& startPoint, const Point& endPoint) +{ + windowStartPoint_ = startPoint; + windowEndPoint_ = endPoint; +} + +void MoveScrollWidgetWindowAnimation::apply(float pos) +{ + const int px = static_cast(ceil(windowStartPoint_.x + (pos * (windowEndPoint_.x - windowStartPoint_.x)))); + const int py = static_cast(ceil(windowStartPoint_.y + (pos * (windowEndPoint_.y - windowStartPoint_.y)))); + list_->setWindowStart(Point{px, py}); +} + +ScrollWidget::ScrollWidget(AnimationManager& animManager) + : windowAnimation_(this) + , widgets_() + , style_({0}) + , animManager_(animManager) + , bounds_({0}) + , windowBounds_({0}) + , visible_(true) + , focused_(false) +{ + animManager_.add(&windowAnimation_); +} + +ScrollWidget::~ScrollWidget() +{ + animManager_.remove(&windowAnimation_); +} + +bool ScrollWidget::isFocused() const +{ + return focused_; +} + +void ScrollWidget::setFocused(bool isFocused) +{ + focused_ = isFocused; +} + +bool ScrollWidget::isVisible() const +{ + return visible_; +} + +void ScrollWidget::setVisible(bool visible) +{ + visible_ = visible; +} + +Rectangle ScrollWidget::getBounds() const +{ + return bounds_; +} + +void ScrollWidget::setBounds(const Rectangle& bounds) +{ + bounds_ = bounds; +} + +Dimensions ScrollWidget::getSize() const +{ + return Dimensions{.width = bounds_.width, .height = bounds_.height}; +} + +void ScrollWidget::setStyle(const ScrollWidgetStyle& style) +{ + style_ = style; +} + +void ScrollWidget::addWidget(IWidget* widget) +{ + widgets_.push_back(widget); + growWindow(widget); +} + +void ScrollWidget::removeWidget(IWidget* widget) +{ + auto it = std::find(widgets_.begin(), widgets_.end(), widget); + if(it != widgets_.end()) + { + widgets_.erase(it); + } + recalculateWindowSize(); +} + +bool ScrollWidget::handleUserInput(const joypad_inputs_t& userInput) +{ + if(!focused_ || style_.scrollStep == 0) + { + return false; + } + + const FloatVector scrollDirection = determineScrollDirection(userInput); + if(scrollDirection.x == 0.f && scrollDirection.y == 0.f) + { + return false; + } + if(!windowAnimation_.isFinished()) + { + windowAnimation_.skipToEnd(); + } + + const Point windowStartPoint = {.x = windowBounds_.x, .y = windowBounds_.y}; + Point windowEndPoint = {.x = windowBounds_.x, .y = windowBounds_.y}; + + windowEndPoint.x += static_cast(ceil(scrollDirection.x * style_.scrollStep)); + windowEndPoint.y += static_cast(ceil(scrollDirection.y * style_.scrollStep)); + windowAnimation_.start(windowStartPoint, windowEndPoint); + + return true; +} + +void ScrollWidget::render(RDPQGraphics& gfx, const Rectangle& parentBounds) +{ + if(!visible_) + { + return; + } + + const Rectangle myAbsoluteBounds = addOffset(bounds_, parentBounds); + + // we "correct" the x/y coords here because the child widget + // will use addOffset() to apply its relative bounds (which may have higher xy coords than the width and height of the window) to the parentBounds + // by doing this correction, we compensate for the window start offset + const Rectangle absoluteWindowStartBounds = Rectangle{ + .x = myAbsoluteBounds.x - windowBounds_.x, + .y = myAbsoluteBounds.y - windowBounds_.y, + .width = myAbsoluteBounds.width, + .height = myAbsoluteBounds.height + }; + + // the bounds of the widget instances are all relative to the top of the scroll widget. + // so is the windowStart_ + // so we need to define a rectangle to define the overlap in order to decide whether a widget needs to be rendered at all. + const Rectangle relativeWindowRect = { + .x = windowBounds_.x, + .y = windowBounds_.y, + .width = bounds_.width, + .height = bounds_.height + }; + + const Rectangle prevClip = gfx.getClippingRectangle(); + gfx.setClippingRectangle(myAbsoluteBounds); + + for(IWidget* widget : widgets_) + { + if(doRectanglesOverlap(relativeWindowRect, widget->getBounds())) + { + widget->render(gfx, absoluteWindowStartBounds); + } + } + gfx.setClippingRectangle(prevClip); +} + +void ScrollWidget::setWindowStart(const Point& windowStart) +{ + windowBounds_.x = windowStart.x; + windowBounds_.y = windowStart.y; +} + +float ScrollWidget::getWindowProgressX() const +{ + const float maxX = windowBounds_.width - bounds_.width; + return static_cast(windowBounds_.x) / maxX; +} + +float ScrollWidget::getWindowProgressY() const +{ + const double maxY = windowBounds_.height - bounds_.height; + return static_cast(windowBounds_.y) / maxY; +} + +void ScrollWidget::growWindow(IWidget* widget) +{ + const Rectangle widgetBounds = widget->getBounds(); + + const int widgetMaxX = widgetBounds.x + widgetBounds.width; + const int widgetMaxY = widgetBounds.y + widgetBounds.height; + + if(widgetMaxX > windowBounds_.width) + { + windowBounds_.width = widgetMaxX; + } + if(widgetMaxY > windowBounds_.height) + { + windowBounds_.height = widgetMaxY; + } +} + +void ScrollWidget::recalculateWindowSize() +{ + windowBounds_.width = 0; + windowBounds_.height = 0; + for(IWidget* widget : widgets_) + { + growWindow(widget); + } +} \ No newline at end of file