diff --git a/include/menu/MenuFunctions.h b/include/menu/MenuFunctions.h index 72d3eff..fc5a760 100755 --- a/include/menu/MenuFunctions.h +++ b/include/menu/MenuFunctions.h @@ -1,6 +1,12 @@ #ifndef _MENUFUNCTIONS_H #define _MENUFUNCTIONS_H +#include "Moves.h" + +// 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); void activateFrameLog(void* context, const void* param); @@ -10,6 +16,8 @@ void goToGen1DistributionPokemonMenu(void* context, const void* param); void goToGen2DistributionPokemonMenu(void* context, const void* param); 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); #endif \ No newline at end of file diff --git a/include/scenes/MenuScene.h b/include/scenes/MenuScene.h index 9de98e7..6ea598d 100755 --- a/include/scenes/MenuScene.h +++ b/include/scenes/MenuScene.h @@ -38,18 +38,11 @@ public: SceneDependencies& getDependencies(); - /** - * This is a helper function to show a single message to the user. - * It should only be used for simple situations. (feedback on a function executed directly in the menu for example) - */ - void showSingleMessage(const DialogData& messageData); + virtual void showDialog(DialogData* diagData); protected: virtual void setupMenu(); - void setupFonts() override; void setupDialog(DialogWidgetStyle& style) override; - virtual void showDialog(DialogData* diagData); - MenuSceneContext* context_; sprite_t* menu9SliceSprite_; sprite_t* cursorSprite_; @@ -57,10 +50,8 @@ protected: CursorWidget cursorWidget_; ListItemFiller menuListFiller_; WidgetFocusChainSegment listFocusChainSegment_; - uint8_t fontStyleYellowId_; bool bButtonPressed_; private: - DialogData singleMessageDialog_; }; void deleteMenuSceneContext(void* context); diff --git a/include/scenes/SceneWithDialogWidget.h b/include/scenes/SceneWithDialogWidget.h index f80ab7b..9260b6f 100755 --- a/include/scenes/SceneWithDialogWidget.h +++ b/include/scenes/SceneWithDialogWidget.h @@ -14,6 +14,8 @@ public: void destroy() override; void render(RDPQGraphics& gfx, const Rectangle& sceneBounds) override; + + void advanceDialog(); protected: virtual void setupFonts(); virtual void setupDialog(DialogWidgetStyle& style); @@ -22,6 +24,7 @@ protected: WidgetFocusChainSegment dialogFocusChainSegment_; uint8_t arialId_; uint8_t fontStyleWhiteId_; + uint8_t fontStyleYellowId_; private: }; diff --git a/include/widget/DialogWidget.h b/include/widget/DialogWidget.h index 5495a8f..a1dc6f4 100755 --- a/include/widget/DialogWidget.h +++ b/include/widget/DialogWidget.h @@ -1,7 +1,8 @@ #ifndef _DIALOGWIDGET_H #define _DIALOGWIDGET_H -#include "widget/IWidget.h" +#include "widget/VerticalList.h" +#include "widget/MenuItemWidget.h" #include "core/Sprite.h" #include "core/RDPQGraphics.h" @@ -13,32 +14,52 @@ typedef struct DialogData { char text[DIALOG_TEXT_SIZE]; // optional sprite of a character that is saying the dialog text - sprite_t* characterSprite; - SpriteRenderSettings characterSpriteSettings; - // bounds of the character sprite relative to the widget - Rectangle characterSpriteBounds; - bool characterSpriteVisible; - sprite_t* buttonSprite; - SpriteRenderSettings buttonSpriteSettings; - // bounds of the button sprite relative to the widget - Rectangle buttonSpriteBounds; - bool buttonSpriteVisible; + struct { + sprite_t* sprite; + SpriteRenderSettings spriteSettings; + // bounds of the character sprite relative to the widget + Rectangle spriteBounds; + bool spriteVisible; + } character; + // optional button sprite + struct { + sprite_t* sprite; + SpriteRenderSettings spriteSettings; + // bounds of the button sprite relative to the widget + Rectangle spriteBounds; + bool spriteVisible; + } button; + // use this struct if you want to provide dialog options + struct { + MenuItemData* items; + uint8_t number; + bool shouldDeleteWhenDone; + } options; + // The next Dialog struct DialogData* next; - bool shouldReleaseWhenDone; + bool shouldDeleteWhenDone; bool userAdvanceBlocked; //TODO: dialog sound } DialogData; typedef struct DialogWidgetStyle { - sprite_t* backgroundSprite; - SpriteRenderSettings backgroundSpriteSettings; + struct { + sprite_t* sprite; + SpriteRenderSettings spriteSettings; + } background; + struct { + Rectangle bounds; + MenuItemStyle style; + } dialogOptions; TextRenderSettings textSettings; - int marginLeft; - int marginRight; - int marginTop; - int marginBottom; + struct { + int left; + int right; + int top; + int bottom; + } margin; } DialogWidgetStyle; /** @@ -95,6 +116,7 @@ private: */ bool isAdvanceAllowed() const; + VerticalList dialogOptionList_; AnimationManager& animationManager_; Rectangle bounds_; DialogWidgetStyle style_; diff --git a/include/widget/MenuItemWidget.h b/include/widget/MenuItemWidget.h index c66e11c..f3a570d 100755 --- a/include/widget/MenuItemWidget.h +++ b/include/widget/MenuItemWidget.h @@ -40,30 +40,34 @@ typedef struct MenuItemStyle * width and height for the MenuItemWidget */ Dimensions size; - /** - * (optional) background sprite - */ - sprite_t* backgroundSprite; - /* - * RenderSettings that influence how the backgroundSprite is - * being rendered - */ - SpriteRenderSettings backgroundSpriteSettings; + struct { + /** + * (optional) background sprite + */ + sprite_t* sprite; + /* + * RenderSettings that influence how the backgroundSprite is + * being rendered + */ + SpriteRenderSettings spriteSettings; + } background; - /** - * (optional) icon sprite - */ - sprite_t* iconSprite; + struct { + /** + * (optional) icon sprite + */ + sprite_t* sprite; - /** - * RenderSettings that influence how the iconSprite is being rendered - */ - SpriteRenderSettings iconSpriteSettings; - - /** - * relative bounds of the icon sprite in relation to the MenuItem widget - */ - Rectangle iconSpriteBounds; + /** + * RenderSettings that influence how the iconSprite is being rendered + */ + SpriteRenderSettings spriteSettings; + + /** + * relative bounds of the icon sprite in relation to the MenuItem widget + */ + Rectangle spriteBounds; + } icon; /** * These are the text settings for when the MenuItemWidget is NOT focused by the user */ diff --git a/include/widget/VerticalList.h b/include/widget/VerticalList.h index f4a70f5..8874eb8 100755 --- a/include/widget/VerticalList.h +++ b/include/widget/VerticalList.h @@ -53,39 +53,62 @@ private: */ typedef struct VerticalListStyle { - /** - * @brief (optional) a background sprite to render the background of - * the widget - */ - sprite_t* backgroundSprite; - /** - * @brief (optional) render settings for rendering the background sprite (if any) - * - */ - SpriteRenderSettings backgroundSpriteSettings; - /** - * @brief left margin -> the widgets will start rendering after this x spacing offset from the left edge of the list - */ - int marginLeft; - /** - * @brief right margin -> the widgets will stop rendering x pixels from the right of this list - */ - int marginRight; - /** - * @brief top margin -> the widgets will start rendering after this y spacing offset from the top edge of the list - * - */ - int marginTop; - /** - * @brief bottom margin -> the widgets will stop rendering y pixels from the bottom edge of this list. - * - */ - int marginBottom; + struct { + /** + * @brief (optional) a background sprite to render the background of + * the widget + */ + sprite_t* sprite; + /** + * @brief (optional) render settings for rendering the background sprite (if any) + * + */ + SpriteRenderSettings spriteSettings; + } background; + + struct { + /** + * @brief left margin -> the widgets will start rendering after this x spacing offset from the left edge of the list + */ + int left; + /** + * @brief right margin -> the widgets will stop rendering x pixels from the right of this list + */ + int right; + /** + * @brief top margin -> the widgets will start rendering after this y spacing offset from the top edge of the list + */ + int top; + /** + * @brief bottom margin -> the widgets will stop rendering y pixels from the bottom edge of this list. + */ + int bottom; + } margin; /** * @brief the amount of spacing (in pixels) between 2 list widgets (default: 0) */ int verticalSpacingBetweenWidgets; + + /** + * @brief should grow automaticaly with each item + */ + struct { + /** + * Whether the list should grow its bounds whenever items are added + */ + bool enabled; + + /** + * defines the direction the list should grow in. true for upwards, false for downwards + */ + bool shouldGrowUpWards; + + /** + * Maximum height the list should grow towards + */ + uint16_t maxHeight; + } autogrow; } VerticalListStyle; /** @@ -117,7 +140,9 @@ public: void addWidget(IWidget* widget); void clearWidgets(); + VerticalListStyle& getStyle(); void setStyle(const VerticalListStyle& style); + void setViewWindowStartY(uint32_t windowStartY); bool isFocused() const override; @@ -145,6 +170,7 @@ private: int32_t scrollWindowToFocusedWidget(); void moveWindow(int32_t yAmount); void notifyFocusListeners(const FocusChangeStatus& status); + void autoGrowBounds(); MoveVerticalListWindowAnimation moveWindowAnimation_; IWidgetList widgetList_; diff --git a/libpokemegb b/libpokemegb index 747bdd4..b275d12 160000 --- a/libpokemegb +++ b/libpokemegb @@ -1 +1 @@ -Subproject commit 747bdd4b4b27c8d1bd9c44f0d651d4c8c439b90c +Subproject commit b275d12a974bb1df92edf38e5bb32267339469ad diff --git a/src/menu/MenuEntries.cpp b/src/menu/MenuEntries.cpp index ccf6943..710a19a 100755 --- a/src/menu/MenuEntries.cpp +++ b/src/menu/MenuEntries.cpp @@ -5,6 +5,16 @@ MenuItemData gen1MenuEntries[] = { { .title = "Event Pokémon", .onConfirmAction = goToGen1DistributionPokemonMenu + }, + { + .title = "Teach Pikachu Surf", + .onConfirmAction = gen1PrepareToTeachPikachu, + .itemParam = &MOVE_SURF + }, + { + .title = "Teach Pikachu Fly", + .onConfirmAction = gen1PrepareToTeachPikachu, + .itemParam = &MOVE_FLY } }; diff --git a/src/menu/MenuFunctions.cpp b/src/menu/MenuFunctions.cpp index 50b151f..2d92794 100755 --- a/src/menu/MenuFunctions.cpp +++ b/src/menu/MenuFunctions.cpp @@ -10,20 +10,36 @@ #define POKEMON_CRYSTAL_ITEM_ID_GS_BALL 0x73 -#if 0 -static void goToMenu(void* context, MenuItemData* menuEntries, uint32_t numMenuEntries) +const Move MOVE_SURF = Move::SURF; +const Move MOVE_FLY = Move::FLY; + +typedef struct Gen1TeachPikachuParams { - MenuScene* scene = static_cast(context); - SceneManager& sceneManager = scene->getDependencies().sceneManager; + Move moveType; + uint8_t partyIndex; + uint8_t moveIndex; + Gen1TrainerPokemon poke; +} Gen1TeachPikachuParams; - MenuSceneContext* sceneContext = new MenuSceneContext{ - .menuEntries = menuEntries, - .numMenuEntries = numMenuEntries - }; - - sceneManager.switchScene(SceneType::MENU, deleteMenuSceneContext, sceneContext); -} -#endif +// here are a few structs I use to pass around parameters to the gen1TeachPikachu() function +// i want to avoid dynamic allocation for this purpose. +// we need 4 of them because we will have 4 menu options, each with a different moveIndex +static Gen1TeachPikachuParams teachParamsMove1 = { + .moveType = Move::SURF, // dummy value. Will be replaced later + .moveIndex = 0 +}; +static Gen1TeachPikachuParams teachParamsMove2 = { + .moveType = Move::SURF, // dummy value. Will be replaced later + .moveIndex = 1 +}; +static Gen1TeachPikachuParams teachParamsMove3 = { + .moveType = Move::SURF, // dummy value. Will be replaced later + .moveIndex = 2 +}; +static Gen1TeachPikachuParams teachParamsMove4 = { + .moveType = Move::SURF, // dummy value. Will be replaced later + .moveIndex = 3 +}; static void goToDistributionPokemonListMenu(void* context, DistributionPokemonListType type) { @@ -36,6 +52,25 @@ static void goToDistributionPokemonListMenu(void* context, DistributionPokemonLi sceneManager.switchScene(SceneType::DISTRIBUTION_POKEMON_LIST, deleteDistributionPokemonListSceneContext, sceneContext); } +static uint8_t gen1FindPikachuInParty(Gen1Party& party) +{ + const uint8_t PIKACHU_INDEX_CODE = 0x54; + uint8_t foundIndex = 0xFF; + const uint8_t numberOfPokemon = party.getNumberOfPokemon(); + + for(uint8_t i=0; i < numberOfPokemon; ++i) + { + const uint8_t speciesIndex = party.getSpeciesAtIndex(i); + if(speciesIndex == PIKACHU_INDEX_CODE) + { +// debugf("Found Pikachu at index %hu\r\n", i); + foundIndex = i; + break; + } + } + return foundIndex; +} + void printMessage(void* context, const void*) { debugf((const char*)context); @@ -73,6 +108,205 @@ void goToGen2PCNYDistributionPokemonMenu(void* context, const void* param) goToDistributionPokemonListMenu(context, DistributionPokemonListType::GEN2_POKEMON_CENTER_NEW_YORK); } +void gen1PrepareToTeachPikachu(void* context, const void* param) +{ + MenuScene* scene = static_cast(context); + TransferPakManager& tpakManager = scene->getDependencies().tpakManager; + TransferPakRomReader romReader(tpakManager); + TransferPakSaveManager saveManager(tpakManager); + Gen1GameReader gameReader(romReader, saveManager, static_cast(scene->getDependencies().specificGenVersion)); + DialogData* msg1 = nullptr; + DialogData* msg2 = nullptr; + uint8_t foundIndex; + const Move moveType = *static_cast(param); + + tpakManager.setRAMEnabled(true); + + Gen1TrainerPokemon poke; + Gen1Party party = gameReader.getParty(); + + foundIndex = gen1FindPikachuInParty(party); + + if(foundIndex == 0xFF) + { + tpakManager.setRAMEnabled(false); + msg1 = new DialogData{ + .shouldDeleteWhenDone = true + }; + setDialogDataText(*msg1, "I see you don't have Pikachu in your party. Please come back once you do!"); + scene->showDialog(msg1); + return; + } + + if(!party.getPokemon(foundIndex, poke, false)) + { + tpakManager.setRAMEnabled(false); + debugf("%s: ERROR while retrieving pokemon from party at index %hu\r\n", __FUNCTION__, foundIndex); + return; + } + + tpakManager.setRAMEnabled(false); + + if(poke.index_move1 == (uint8_t)moveType || poke.index_move2 == (uint8_t)moveType || poke.index_move3 == (uint8_t)moveType || poke.index_move4 == (uint8_t)moveType) + { + msg1 = new DialogData{ + .shouldDeleteWhenDone = true + }; + setDialogDataText(*msg1, "Your Pikachu already knows %s!", getMoveString(moveType)); + scene->showDialog(msg1); + return; + } + + teachParamsMove1.moveType = teachParamsMove2.moveType = teachParamsMove3.moveType = teachParamsMove4.moveType = moveType; + teachParamsMove1.partyIndex = teachParamsMove2.partyIndex = teachParamsMove3.partyIndex = teachParamsMove4.partyIndex = foundIndex; + teachParamsMove1.poke = teachParamsMove2.poke = teachParamsMove3.poke = teachParamsMove4.poke = poke; + + if(!poke.index_move1) + { + gen1TeachPikachu(context, &teachParamsMove1); + return; + } + else if(!poke.index_move2) + { + gen1TeachPikachu(context, &teachParamsMove2); + return; + } + else if(!poke.index_move3) + { + gen1TeachPikachu(context, &teachParamsMove3); + return; + } + else if(!poke.index_move4) + { + gen1TeachPikachu(context, &teachParamsMove4); + return; + } + + msg2 = new DialogData { + .options = { + .items = new MenuItemData[4]{ + { + .title = getMoveString(static_cast(poke.index_move1)), + .onConfirmAction = gen1TeachPikachu, + .context = context, + .itemParam = &teachParamsMove1 + }, + { + .title = getMoveString(static_cast(poke.index_move2)), + .onConfirmAction = gen1TeachPikachu, + .context = context, + .itemParam = &teachParamsMove2 + }, + { + .title = getMoveString(static_cast(poke.index_move3)), + .onConfirmAction = gen1TeachPikachu, + .context = context, + .itemParam = &teachParamsMove3 + }, + { + .title = getMoveString(static_cast(poke.index_move4)), + .onConfirmAction = gen1TeachPikachu, + .context = context, + .itemParam = &teachParamsMove4 + } + }, + .number = 4, + .shouldDeleteWhenDone = true + }, + .shouldDeleteWhenDone = true + }; + + msg1 = new DialogData{ + .next = msg2, + .shouldDeleteWhenDone = true + }; + setDialogDataText(*msg1, "Pikachu is trying to learn %s. But Pikachu already knows 4 moves.", getMoveString(moveType)); + setDialogDataText(*msg2, "Delete an older move to make room for %s?", getMoveString(moveType)); + scene->showDialog(msg1); +} + +void gen1TeachPikachu(void* context, const void* param) +{ + MenuScene* scene = static_cast(context); + TransferPakManager& tpakManager = scene->getDependencies().tpakManager; + TransferPakRomReader romReader(tpakManager); + TransferPakSaveManager saveManager(tpakManager); + Gen1GameReader gameReader(romReader, saveManager, static_cast(scene->getDependencies().specificGenVersion)); + + const Gen1TeachPikachuParams* params = static_cast(param); + Gen1TrainerPokemon poke = params->poke; + uint8_t previousMove; + uint8_t newMove = static_cast(params->moveType); + +// debugf("%s: newMove %hu, moveIndex %hu, partyIndex %hu\r\n", __FUNCTION__, newMove, params->moveIndex, params->partyIndex); + + switch(params->moveIndex) + { + case 0: + previousMove = poke.index_move1; + poke.index_move1 = newMove; + poke.pp_move1 = 0; + break; + case 1: + previousMove = poke.index_move2; + poke.index_move2 = newMove; + poke.pp_move2 = 0; + break; + case 2: + previousMove = poke.index_move3; + poke.index_move3 = newMove; + poke.pp_move3 = 0; + break; + case 3: + previousMove = poke.index_move4; + poke.index_move4 = newMove; + poke.pp_move4 = 0; + break; + default: + scene->advanceDialog(); + return; + } + + tpakManager.setRAMEnabled(true); + + Gen1Party party = gameReader.getParty(); + if(!party.setPokemon(params->partyIndex, poke)) + { + tpakManager.setRAMEnabled(false); + debugf("%s: ERROR: can't update Pikachu at partyIndex %hu\r\n", __FUNCTION__, params->partyIndex); + return; + } + gameReader.updateMainChecksum(); + tpakManager.finishWrites(); + tpakManager.setRAMEnabled(false); + + DialogData* msg1 = new DialogData{ + .shouldDeleteWhenDone = true + }; + + if(previousMove) + { + DialogData* msg3 = new DialogData{ + .shouldDeleteWhenDone = true + }; + DialogData* msg2 = new DialogData{ + .next = msg3, + .shouldDeleteWhenDone = true + }; + msg1->next = msg2; + + setDialogDataText(*msg1, "1..2..3..Poof!"); + setDialogDataText(*msg2, "Pikachu forgot %s!", getMoveString(static_cast(previousMove))); + setDialogDataText(*msg3, "Pikachu learned %s!", getMoveString(params->moveType)); + } + else + { + setDialogDataText(*msg1, "Pikachu learned %s!", getMoveString(params->moveType)); + } + + scene->showDialog(msg1); +} + void gen2ReceiveGSBall(void* context, const void* param) { MenuScene* scene = static_cast(context); @@ -80,7 +314,9 @@ void gen2ReceiveGSBall(void* context, const void* param) TransferPakRomReader romReader(tpakManager); TransferPakSaveManager saveManager(tpakManager); Gen2GameReader gameReader(romReader, saveManager, Gen2GameType::CRYSTAL); - DialogData messageData = {0}; + DialogData* messageData = new DialogData{ + .shouldDeleteWhenDone = true + }; bool alreadyHasOne = false; tpakManager.setRAMEnabled(true); @@ -106,15 +342,13 @@ void gen2ReceiveGSBall(void* context, const void* param) if(alreadyHasOne) { - setDialogDataText(messageData, "It appears you already have one!"); + setDialogDataText(*messageData, "It appears you already have one!"); } else { keyItemPocket.add(POKEMON_CRYSTAL_ITEM_ID_GS_BALL, 1); gameReader.finishSave(); - setDialogDataText(messageData, "%s obtained a GS Ball!", trainerName); + setDialogDataText(*messageData, "%s obtained a GS Ball!", trainerName); } - scene->showSingleMessage(messageData); - - tpakManager.setRAMEnabled(false); + scene->showDialog(messageData); } \ No newline at end of file diff --git a/src/scenes/DistributionPokemonListScene.cpp b/src/scenes/DistributionPokemonListScene.cpp index 8893e8a..661a4c0 100755 --- a/src/scenes/DistributionPokemonListScene.cpp +++ b/src/scenes/DistributionPokemonListScene.cpp @@ -162,12 +162,16 @@ void DistributionPokemonListScene::onDialogDone() void DistributionPokemonListScene::setupMenu() { const VerticalListStyle listStyle = { - .backgroundSprite = menu9SliceSprite_, - .backgroundSpriteSettings = { - .renderMode = SpriteRenderMode::NINESLICE, - .srcRect = { 6, 6, 6, 6 } + .background = { + .sprite = menu9SliceSprite_, + .spriteSettings = { + .renderMode = SpriteRenderMode::NINESLICE, + .srcRect = { 6, 6, 6, 6 } + } }, - .marginTop = 5, + .margin = { + .top = 5 + } }; menuList_.setStyle(listStyle); diff --git a/src/scenes/InitTransferPakScene.cpp b/src/scenes/InitTransferPakScene.cpp index e6c8aff..855c132 100755 --- a/src/scenes/InitTransferPakScene.cpp +++ b/src/scenes/InitTransferPakScene.cpp @@ -149,8 +149,8 @@ void InitTransferPakScene::setupTPakDetectWidget() void InitTransferPakScene::setupDialog(DialogWidgetStyle& style) { - style.backgroundSprite = menu9SliceSprite_; - style.backgroundSpriteSettings = { + style.background.sprite = menu9SliceSprite_; + style.background.spriteSettings = { .renderMode = SpriteRenderMode::NINESLICE, .srcRect = { 6, 6, 6, 6 } }; diff --git a/src/scenes/MenuScene.cpp b/src/scenes/MenuScene.cpp index 4b8b706..40f5927 100755 --- a/src/scenes/MenuScene.cpp +++ b/src/scenes/MenuScene.cpp @@ -21,9 +21,7 @@ MenuScene::MenuScene(SceneDependencies& deps, void* context) , listFocusChainSegment_(WidgetFocusChainSegment{ .current = &menuList_ }) - , fontStyleYellowId_(1) , bButtonPressed_(false) - , singleMessageDialog_({0}) { } @@ -131,33 +129,19 @@ SceneDependencies& MenuScene::getDependencies() return deps_; } -void MenuScene::showSingleMessage(const DialogData& messageData) -{ - singleMessageDialog_ = messageData; - showDialog(&singleMessageDialog_); -} - -void MenuScene::setupFonts() -{ - SceneWithDialogWidget::setupFonts(); - - const rdpq_fontstyle_t arialYellow = { - .color = RGBA32(0xFF, 0xFF, 0x00, 0xFF), - .outline_color = RGBA32(0, 0, 0, 0xFF) - }; - - deps_.fontManager.registerFontStyle(arialId_, fontStyleYellowId_, arialYellow); -} - void MenuScene::setupMenu() { const VerticalListStyle listStyle = { - .backgroundSprite = menu9SliceSprite_, - .backgroundSpriteSettings = { - .renderMode = SpriteRenderMode::NINESLICE, - .srcRect = { 6, 6, 6, 6 } + .background = { + .sprite = menu9SliceSprite_, + .spriteSettings = { + .renderMode = SpriteRenderMode::NINESLICE, + .srcRect = { 6, 6, 6, 6 } + } }, - .marginTop = 5 + .margin = { + .top = 5 + } }; const CursorStyle cursorStyle = { @@ -201,8 +185,8 @@ void MenuScene::setupMenu() void MenuScene::setupDialog(DialogWidgetStyle& style) { - style.backgroundSprite = menu9SliceSprite_; - style.backgroundSpriteSettings = { + style.background.sprite = menu9SliceSprite_; + style.background.spriteSettings = { .renderMode = SpriteRenderMode::NINESLICE, .srcRect = { 6, 6, 6, 6 } }; diff --git a/src/scenes/SceneWithDialogWidget.cpp b/src/scenes/SceneWithDialogWidget.cpp index 7217f2c..b072a99 100755 --- a/src/scenes/SceneWithDialogWidget.cpp +++ b/src/scenes/SceneWithDialogWidget.cpp @@ -10,6 +10,7 @@ SceneWithDialogWidget::SceneWithDialogWidget(SceneDependencies& deps) }) , arialId_(1) , fontStyleWhiteId_(0) + , fontStyleYellowId_(1) { } @@ -20,14 +21,32 @@ SceneWithDialogWidget::~SceneWithDialogWidget() void SceneWithDialogWidget::init() { DialogWidgetStyle style = { + .dialogOptions = { + .bounds = Rectangle{190, 179, 120, 0}, + .style = { + .size = {140, 16}, + .titleNotFocused = { + .fontId = arialId_, + .fontStyleId = fontStyleWhiteId_ + }, + .titleFocused = { + .fontId = arialId_, + .fontStyleId = fontStyleYellowId_ + }, + .leftMargin = 5, + .topMargin = 1 + } + }, .textSettings = { .fontId = arialId_, .fontStyleId = fontStyleWhiteId_ }, - .marginLeft = 10, - .marginRight = 10, - .marginTop = 10, - .marginBottom = 10 + .margin = { + .left = 10, + .right = 10, + .top = 10, + .bottom = 10 + } }; setupFonts(); @@ -49,15 +68,26 @@ void SceneWithDialogWidget::render(RDPQGraphics& gfx, const Rectangle& sceneBoun dialogWidget_.render(gfx, sceneBounds); } +void SceneWithDialogWidget::advanceDialog() +{ + dialogWidget_.advanceDialog(); +} + void SceneWithDialogWidget::setupFonts() { arialId_ = deps_.fontManager.getFont("rom://Arial.font64"); + const rdpq_fontstyle_t arialWhite = { .color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF), .outline_color = RGBA32(0, 0, 0, 0xFF) }; + const rdpq_fontstyle_t arialYellow = { + .color = RGBA32(0xFF, 0xFF, 0x00, 0xFF), + .outline_color = RGBA32(0, 0, 0, 0xFF) + }; deps_.fontManager.registerFontStyle(arialId_, fontStyleWhiteId_, arialWhite); + deps_.fontManager.registerFontStyle(arialId_, fontStyleYellowId_, arialYellow); } void SceneWithDialogWidget::setupDialog(DialogWidgetStyle& style) diff --git a/src/transferpak/TransferPakSaveManager.cpp b/src/transferpak/TransferPakSaveManager.cpp index 1fff84f..eeb0889 100755 --- a/src/transferpak/TransferPakSaveManager.cpp +++ b/src/transferpak/TransferPakSaveManager.cpp @@ -18,6 +18,8 @@ TransferPakSaveManager::TransferPakSaveManager(TransferPakManager& pakManager) : pakManager_(pakManager) , sramOffset_(0) { + // reset back to sram bank 0 + pakManager_.switchGBSRAMBank(0); } TransferPakSaveManager::~TransferPakSaveManager() diff --git a/src/widget/DialogWidget.cpp b/src/widget/DialogWidget.cpp index ab4361e..9287ab2 100755 --- a/src/widget/DialogWidget.cpp +++ b/src/widget/DialogWidget.cpp @@ -1,10 +1,39 @@ #include "widget/DialogWidget.h" +#include "widget/ListItemFiller.h" #include "core/RDPQGraphics.h" #include +/** + * @brief Releases either a single DialogData entry or its entire chain. + */ +static void releaseEntry(DialogData* data, bool releaseAllEntries) +{ + DialogData* cur = data; + DialogData* next = nullptr; + do + { + if(cur) + { + next = cur->next; + if(cur->options.shouldDeleteWhenDone && cur->options.items) + { + delete[] cur->options.items; + cur->options.items = nullptr; + } + if(cur->shouldDeleteWhenDone) + { + delete cur; + } + } + cur = next; + + } while(releaseAllEntries && cur); +} + DialogWidget::DialogWidget(AnimationManager& animationManager) - : animationManager_(animationManager) + : dialogOptionList_(animationManager) + , animationManager_(animationManager) , bounds_({0}) , style_({0}) , data_(nullptr) @@ -14,10 +43,13 @@ DialogWidget::DialogWidget(AnimationManager& animationManager) , visible_(true) , btnAPressedOnPrevCheck_(false) { + dialogOptionList_.setVisible(false); } DialogWidget::~DialogWidget() { + releaseEntry(data_, true); + data_ = nullptr; } const DialogWidgetStyle& DialogWidget::getStyle() const @@ -28,11 +60,36 @@ const DialogWidgetStyle& DialogWidget::getStyle() const void DialogWidget::setStyle(const DialogWidgetStyle& style) { style_ = style; + + VerticalListStyle& listStyle = dialogOptionList_.getStyle(); + listStyle.background.sprite = style.background.sprite; + listStyle.background.spriteSettings = style.background.spriteSettings; + listStyle.margin.left = style.margin.left; + listStyle.margin.right = style.margin.right; + listStyle.margin.top = style.margin.top; + listStyle.margin.bottom = style.margin.bottom; + listStyle.autogrow.enabled = true; + listStyle.autogrow.shouldGrowUpWards = true; + dialogOptionList_.setBounds(style.dialogOptions.bounds); } void DialogWidget::setData(DialogData* data) { + dialogOptionList_.clearWidgets(); + dialogOptionList_.setVisible(false); + dialogOptionList_.setFocused(false); + releaseEntry(data_, true); + data_ = data; + + if(data_ && data->options.items) + { + ListItemFiller filler(dialogOptionList_); + + filler.addItems(data->options.items, data->options.number, style_.dialogOptions.style); + dialogOptionList_.setVisible(true); + dialogOptionList_.setFocused(focused_); + } } void DialogWidget::appendDialogData(DialogData* data) @@ -59,6 +116,11 @@ bool DialogWidget::isFocused() const void DialogWidget::setFocused(bool focused) { focused_ = focused; + + if(data_ && data_->options.number) + { + dialogOptionList_.setFocused(focused); + } } bool DialogWidget::isVisible() const @@ -102,18 +164,33 @@ void DialogWidget::advanceDialog() } return; } - const DialogData* oldEntry = data_; + + DialogData* oldEntry = data_; data_ = data_->next; + + releaseEntry(oldEntry, false); - if(oldEntry->shouldReleaseWhenDone) + dialogOptionList_.clearWidgets(); + dialogOptionList_.setFocused(false); + dialogOptionList_.setVisible(false); + if(data_->options.items) { - delete oldEntry; - oldEntry = nullptr; + ListItemFiller filler(dialogOptionList_); + + filler.addItems(data_->options.items, data_->options.number, style_.dialogOptions.style); + dialogOptionList_.setVisible(true); + dialogOptionList_.setFocused(focused_); } + } bool DialogWidget::handleUserInput(const joypad_inputs_t& userInput) { + // if dialog options are displayed and focused, the VerticalList widget needs to handle the key events + if(dialogOptionList_.isFocused()) + { + return dialogOptionList_.handleUserInput(userInput); + } // make sure the user needs to release the button before handling the A button again // if we don't do that, a different component might react to the same a button press if(btnAPressedOnPrevCheck_) @@ -141,9 +218,9 @@ void DialogWidget::render(RDPQGraphics& gfx, const Rectangle& parentBounds) const Rectangle myBounds = addOffset(bounds_, parentBounds); // render the background first, if any. - if(style_.backgroundSprite) + if(style_.background.sprite) { - gfx.drawSprite(myBounds, style_.backgroundSprite, style_.backgroundSpriteSettings); + gfx.drawSprite(myBounds, style_.background.sprite, style_.background.spriteSettings); } if(!data_) @@ -151,29 +228,31 @@ void DialogWidget::render(RDPQGraphics& gfx, const Rectangle& parentBounds) return; } - if(data_->characterSprite && data_->characterSpriteVisible) + if(data_->character.sprite && data_->character.spriteVisible) { - const Rectangle absoluteCharBounds = addOffset(data_->characterSpriteBounds, myBounds); - gfx.drawSprite(absoluteCharBounds, data_->characterSprite, data_->characterSpriteSettings); + const Rectangle absoluteCharBounds = addOffset(data_->character.spriteBounds, myBounds); + gfx.drawSprite(absoluteCharBounds, data_->character.sprite, data_->character.spriteSettings); } - if(data_->buttonSprite && data_->buttonSpriteVisible) + if(data_->button.sprite && data_->button.spriteVisible) { - const Rectangle absoluteButtonSpriteBounds = addOffset(data_->buttonSpriteBounds, myBounds); - gfx.drawSprite(absoluteButtonSpriteBounds, data_->buttonSprite, data_->buttonSpriteSettings); + const Rectangle absoluteButtonSpriteBounds = addOffset(data_->button.spriteBounds, myBounds); + gfx.drawSprite(absoluteButtonSpriteBounds, data_->button.sprite, data_->button.spriteSettings); } if(data_->text[0] != '\0') { const Rectangle textBounds = { - .x = myBounds.x + style_.marginLeft, - .y = myBounds.y + style_.marginTop, - .width = myBounds.width - style_.marginLeft - style_.marginRight, - .height = myBounds.height - style_.marginTop - style_.marginBottom + .x = myBounds.x + style_.margin.left, + .y = myBounds.y + style_.margin.top, + .width = myBounds.width - style_.margin.left - style_.margin.right, + .height = myBounds.height - style_.margin.top - style_.margin.bottom }; gfx.drawText(textBounds, data_->text, style_.textSettings); } + + dialogOptionList_.render(gfx, parentBounds); } bool DialogWidget::isAdvanceAllowed() const diff --git a/src/widget/MenuItemWidget.cpp b/src/widget/MenuItemWidget.cpp index c84a29e..9057201 100755 --- a/src/widget/MenuItemWidget.cpp +++ b/src/widget/MenuItemWidget.cpp @@ -84,15 +84,15 @@ void MenuItemWidget::render(RDPQGraphics& gfx, const Rectangle& parentBounds) return; } Rectangle myBounds = {.x = parentBounds.x, .y = parentBounds.y, .width = style_.size.width, .height = style_.size.height}; - if(style_.backgroundSprite) + if(style_.background.sprite) { - gfx.drawSprite(myBounds, style_.backgroundSprite, style_.backgroundSpriteSettings); + gfx.drawSprite(myBounds, style_.background.sprite, style_.background.spriteSettings); } - if(style_.iconSprite) + if(style_.icon.sprite) { - const Rectangle iconSpriteBounds = addOffset(style_.iconSpriteBounds, myBounds); - gfx.drawSprite(iconSpriteBounds, style_.iconSprite, style_.iconSpriteSettings); + const Rectangle iconSpriteBounds = addOffset(style_.icon.spriteBounds, myBounds); + gfx.drawSprite(iconSpriteBounds, style_.icon.sprite, style_.icon.spriteSettings); } myBounds.x += style_.leftMargin; diff --git a/src/widget/VerticalList.cpp b/src/widget/VerticalList.cpp index 7b0049b..3b014ee 100755 --- a/src/widget/VerticalList.cpp +++ b/src/widget/VerticalList.cpp @@ -137,7 +137,7 @@ bool VerticalList::focusNext() ++focusedWidgetIndex_; changeStatus.curFocus = widgetList_[focusedWidgetIndex_]; - changeStatus.focusBounds = calculateListWidgetBounds(widgetBoundsList_[focusedWidgetIndex_], windowMinY_, bounds_.x + listStyle_.marginLeft, bounds_.y + listStyle_.marginTop); + changeStatus.focusBounds = calculateListWidgetBounds(widgetBoundsList_[focusedWidgetIndex_], windowMinY_, bounds_.x + listStyle_.margin.left, bounds_.y + listStyle_.margin.top); widgetList_[focusedWidgetIndex_]->setFocused(true); const int32_t scrollAmountY = scrollWindowToFocusedWidget(); @@ -165,7 +165,7 @@ bool VerticalList::focusPrevious() --focusedWidgetIndex_; changeStatus.curFocus = widgetList_[focusedWidgetIndex_]; - changeStatus.focusBounds = calculateListWidgetBounds(widgetBoundsList_[focusedWidgetIndex_], windowMinY_, bounds_.x + listStyle_.marginLeft, bounds_.y + listStyle_.marginTop); + changeStatus.focusBounds = calculateListWidgetBounds(widgetBoundsList_[focusedWidgetIndex_], windowMinY_, bounds_.x + listStyle_.margin.left, bounds_.y + listStyle_.margin.top); widgetList_[focusedWidgetIndex_]->setFocused(true); const int32_t scrollAmountY = scrollWindowToFocusedWidget(); @@ -187,7 +187,7 @@ void VerticalList::addWidget(IWidget *widget) widget->setFocused(focused_); FocusChangeStatus changeStatus = { - .focusBounds = calculateListWidgetBounds(widgetBoundsList_[focusedWidgetIndex_], windowMinY_, bounds_.x + listStyle_.marginLeft, bounds_.y + listStyle_.marginTop), + .focusBounds = calculateListWidgetBounds(widgetBoundsList_[focusedWidgetIndex_], windowMinY_, bounds_.x + listStyle_.margin.left, bounds_.y + listStyle_.margin.top), .prevFocus = nullptr, .curFocus = widget }; @@ -198,12 +198,19 @@ void VerticalList::addWidget(IWidget *widget) const Rectangle lastWidgetBounds = widgetBoundsList_.back(); widgetBoundsList_.push_back(Rectangle{.x = 0, .y = lastWidgetBounds.y + lastWidgetBounds.height + listStyle_.verticalSpacingBetweenWidgets, .width = widgetSize.width, .height = widgetSize.height}); } + autoGrowBounds(); } void VerticalList::clearWidgets() { widgetList_.clear(); widgetBoundsList_.clear(); + focusedWidgetIndex_ = 0; +} + +VerticalListStyle& VerticalList::getStyle() +{ + return listStyle_; } void VerticalList::setStyle(const VerticalListStyle& style) @@ -232,7 +239,7 @@ void VerticalList::setFocused(bool isFocused) widgetList_[focusedWidgetIndex_]->setFocused(focused_); FocusChangeStatus changeStatus = { - .focusBounds = calculateListWidgetBounds(widgetBoundsList_[focusedWidgetIndex_], windowMinY_, bounds_.x + listStyle_.marginLeft, bounds_.y + listStyle_.marginTop) + .focusBounds = calculateListWidgetBounds(widgetBoundsList_[focusedWidgetIndex_], windowMinY_, bounds_.x + listStyle_.margin.left, bounds_.y + listStyle_.margin.top) }; if(isFocused) @@ -318,11 +325,11 @@ void VerticalList::render(RDPQGraphics& gfx, const Rectangle& parentBounds) return; } - const uint32_t innerListHeight = getInnerListHeight(bounds_, listStyle_.marginTop, listStyle_.marginBottom); + const uint32_t innerListHeight = getInnerListHeight(bounds_, listStyle_.margin.top, listStyle_.margin.bottom); uint32_t i; const Rectangle myBounds = addOffset(bounds_, parentBounds); - const int topX = myBounds.x + listStyle_.marginLeft; - const int topY = myBounds.y + listStyle_.marginTop; + const int topX = myBounds.x + listStyle_.margin.left; + const int topY = myBounds.y + listStyle_.margin.top; // store previous clipping rectangle to restore later // const Rectangle prevClipRect = gfx.getClippingRectangle(); @@ -330,9 +337,9 @@ void VerticalList::render(RDPQGraphics& gfx, const Rectangle& parentBounds) // gfx.setClippingRectangle(myBounds); // render the background first, if any. - if(listStyle_.backgroundSprite) + if(listStyle_.background.sprite) { - gfx.drawSprite(myBounds, listStyle_.backgroundSprite, listStyle_.backgroundSpriteSettings); + gfx.drawSprite(myBounds, listStyle_.background.sprite, listStyle_.background.spriteSettings); } if(widgetList_.empty()) @@ -416,7 +423,7 @@ int32_t VerticalList::scrollWindowToFocusedWidget() // the reason is the use of isWidgetInsideWindow() inside the render() function to cull entries from the render window. // We could potentially eliminate this by expanding the check to allow a partially visible entry to be rendered. // But for my goals, this is currently not needed, so I claim YAGNI for now. - const uint32_t innerListHeight = getInnerListHeight(bounds_, listStyle_.marginTop, listStyle_.marginBottom); + const uint32_t innerListHeight = getInnerListHeight(bounds_, listStyle_.margin.top, listStyle_.margin.bottom); const int32_t windowScrollYNeeded = getVerticalWindowScrollNeededToMakeWidgetFullyVisible(widgetBoundsList_[focusedWidgetIndex_], innerListHeight, windowMinY_); if(windowScrollYNeeded != 0) { @@ -436,4 +443,27 @@ void VerticalList::notifyFocusListeners(const FocusChangeStatus& status) { listener->focusChanged(status); } +} + +void VerticalList::autoGrowBounds() +{ + if(!listStyle_.autogrow.enabled) + { + return; + } + + const Rectangle& lastWidgetBounds = widgetBoundsList_.back(); + int heightToConsider = lastWidgetBounds.y + lastWidgetBounds.height + listStyle_.margin.top + listStyle_.margin.bottom; + if(listStyle_.autogrow.maxHeight && listStyle_.autogrow.maxHeight < heightToConsider) + { + heightToConsider = listStyle_.autogrow.maxHeight; + } + + if(listStyle_.autogrow.shouldGrowUpWards) + { + const int heightDiff = abs(heightToConsider - bounds_.height); + bounds_.y -= heightDiff; + } + + bounds_.height = heightToConsider; } \ No newline at end of file