#pragma region License /* License (OLC-3) ~~~~~~~~~~~~~~~ Copyright 2024 Joshua Sigona Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions or derivations of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions or derivative works in binary form must reproduce the above copyright notice. This list of conditions and the following disclaimer must be reproduced in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Portions of this software are copyright © 2024 The FreeType Project (www.freetype.org). Please see LICENSE_FT.txt for more information. All rights reserved. */ #pragma endregion #include "Menu.h" #include "AdventuresInLestoria.h" #include "MenuLabel.h" #include "InventoryScrollableWindowComponent.h" #include "PopupMenuLabel.h" #include "Unlock.h" #include "State_OverworldMap.h" #include "ProgressBar.h" #include "SoundEffect.h" #include "State_LevelComplete.h" INCLUDE_game void Menu::InitializeLevelCompleteWindow(){ geom2d::rectwindowSize={{28,28},game->GetScreenSize()-vf2d{56,56}}; Menu*levelCompleteWindow=Menu::CreateMenu(LEVEL_COMPLETE,windowSize.pos,windowSize.size); levelCompleteWindow->ADD("Stage Complete Label",MenuLabel)(geom2d::rect{{0,-8},{windowSize.size.x-1.f,20}},"Stage Completed",2,ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND|ComponentAttr::SHADOW)END; levelCompleteWindow->ADD("XP Bar",ProgressBar)(geom2d::rect{vf2d{windowSize.size.x/2.f-96.f,18.f},{192.f,8.f}},Pixel{247,183,82},BLACK,game->GetPlayer()->CurrentXP(),game->GetPlayer()->NextLevelXPRequired(),"xp")END; levelCompleteWindow->ADD("Level Display",MenuLabel)(geom2d::rect{vf2d{windowSize.size.x/2.f-96.f-42.f,18.f},{42.f,8.f}},std::format("Lv{}",game->GetPlayer()->Level()),1.f,ComponentAttr::SHADOW|ComponentAttr::RIGHT_ALIGN)END; levelCompleteWindow->ADD("Monster Loot Outline",MenuComponent)(geom2d::rect{{0,32},{windowSize.size.x-80.f,72}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END; levelCompleteWindow->ADD("Monster Loot Label",MenuLabel)(geom2d::rect{{0,32},{windowSize.size.x-80.f,12}},"Monster Loot",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE)END; auto monsterLootWindow=levelCompleteWindow->ADD("Monster Loot Window",InventoryScrollableWindowComponent)(geom2d::rect{{0,44},{windowSize.size.x-80.f,60}},"Monster Loot Popup Item Name","Monster Loot Popup Item Description",DO_NOTHING,InventoryCreator::Player_InventoryUpdate)END; Menu::AddInventoryListener(monsterLootWindow,"Monster Loot"); levelCompleteWindow->ADD("Stage Loot Outline",MenuComponent)(geom2d::rect{{0,108},{windowSize.size.x-80.f,72}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END; levelCompleteWindow->ADD("Stage Loot Label",MenuLabel)(geom2d::rect{{0,108},{windowSize.size.x-80.f,12}},"Stage Loot",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE)END; auto stageLootWindow=levelCompleteWindow->ADD("Stage Loot Window",InventoryScrollableWindowComponent)(geom2d::rect{{0,120},{windowSize.size.x-80.f,60}},"Stage Loot Popup Item Name","Stage Loot Popup Item Description",DO_NOTHING,InventoryCreator::Player_InventoryUpdate)END; Menu::AddInventoryListener(stageLootWindow,"Stage Loot"); auto overworldMapAction=[](MenuFuncData data){ if(Component(LEVEL_COMPLETE,"Stage Complete Label")->GetLabel()!="Stage Summary"){ //If the label says stage summary, we didn't actually complete the level. Don't unlock anything new for the player. Unlock::UnlockArea(State_OverworldMap::GetCurrentConnectionPoint().map); Merchant::RandomizeTravelingMerchant(); } State_LevelComplete::TurnOffXPSound(); GameState::ChangeState(States::OVERWORLD_MAP,0.25f); return true; }; auto nextButtonAction=[](MenuFuncData data){ Unlock::UnlockArea(State_OverworldMap::GetCurrentConnectionPoint().map); Merchant::RandomizeTravelingMerchant(); State_LevelComplete::TurnOffXPSound(); GameState::ChangeState(States::GAME_HUB,0.25f); return true; }; levelCompleteWindow->ADD("Level Details Outline",MenuComponent)(geom2d::rect{{windowSize.size.x-72.f,32},{71,56}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE|ButtonAttr::FIT_TO_LABEL)END; levelCompleteWindow->ADD("Level EXP Gain Outline",MenuLabel)(geom2d::rect{{windowSize.size.x-72.f,88},{71,36}},"+ Exp",1,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END; levelCompleteWindow->ADD("Overworld Button",MenuComponent)(geom2d::rect{{windowSize.size.x-72.f,128},{71,24}},"Return\nto Map",overworldMapAction,ButtonAttr::FIT_TO_LABEL)END; levelCompleteWindow->ADD("Next Button",MenuComponent)(geom2d::rect{{windowSize.size.x-72.f,156},{71,24}},"Proceed\nto Camp",nextButtonAction,ButtonAttr::FIT_TO_LABEL)END; levelCompleteWindow->ADD("Monster Loot Popup Item Name",PopupMenuLabel)(geom2d::rect{{0,108},{windowSize.size.x-80.f,12}},"",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END; levelCompleteWindow->ADD("Monster Loot Popup Item Description",PopupMenuLabel)(geom2d::rect{{0,120},{windowSize.size.x-80.f,60}},"",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END; levelCompleteWindow->ADD("Stage Loot Popup Item Name",PopupMenuLabel)(geom2d::rect{{0,32},{windowSize.size.x-80.f,12}},"",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END; levelCompleteWindow->ADD("Stage Loot Popup Item Description",PopupMenuLabel)(geom2d::rect{{0,44},{windowSize.size.x-80.f,60}},"",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END; levelCompleteWindow->SetupKeyboardNavigation( [](MenuType type,Data&returnData){ //On Open auto monsterLoot=Component(type,"Monster Loot Window"); auto stageLoot=Component(type,"Stage Loot Window"); if(monsterLoot->GetComponents().size()>0){ returnData=monsterLoot->GetComponents().front(); }else if(stageLoot->GetComponents().size()>0){ returnData=stageLoot->GetComponents().front(); }else{ returnData="Next Button"; } }, { //Button Key {{game->KEY_SHOULDER2,Pressed},{[](MenuFuncData data){ auto monsterLoot=Component(data.menu.GetType(),"Monster Loot Window"); auto stageLoot=Component(data.menu.GetType(),"Stage Loot Window"); if(!data.parentComponent.expired()){ if(data.parentComponent.lock()->GetName()=="Monster Loot Window"&&monsterLoot->GetComponents().size()>0){ return "Stage Loot"; }else if(monsterLoot->GetComponents().size()>0){ return "Monster Loot"; } } return ""; },[](MenuType type){}}}, {{game->KEY_SHOULDER,Pressed},{[](MenuFuncData data){ auto monsterLoot=Component(data.menu.GetType(),"Monster Loot Window"); auto stageLoot=Component(data.menu.GetType(),"Stage Loot Window"); if(!data.parentComponent.expired()){ if(data.parentComponent.lock()->GetName()=="Monster Loot Window"&&monsterLoot->GetComponents().size()>0){ return "Stage Loot"; }else if(monsterLoot->GetComponents().size()>0){ return "Monster Loot"; } } return ""; },[](MenuType type){}}}, {{game->KEY_FASTSCROLLUP,Pressed},{"",[](MenuType type){ auto monsterLoot=Component(type,"Monster Loot Window"); auto stageLoot=Component(type,"Stage Loot Window"); auto selection=Menu::stack.back()->GetSelection(); if(!selection.expired()){ auto parent=selection.lock()->parentComponent; if(!parent.expired()){ if(parent.lock()->GetName()=="Monster Loot Window"&&stageLoot->GetComponents().size()>0){ Menu::stack.back()->SetSelection(stageLoot->GetComponents()[0]); SoundEffect::PlaySFX("Button Click",SoundEffect::CENTERED); }else if(monsterLoot->GetComponents().size()>0){ Menu::stack.back()->SetSelection(monsterLoot->GetComponents()[0]); SoundEffect::PlaySFX("Button Click",SoundEffect::CENTERED); } } } return ""; }}}, {{game->KEY_FASTSCROLLDOWN,Pressed},{"",[](MenuType type){ auto monsterLoot=Component(type,"Monster Loot Window"); auto stageLoot=Component(type,"Stage Loot Window"); auto selection=Menu::stack.back()->GetSelection(); if(!selection.expired()){ auto parent=selection.lock()->parentComponent; if(!parent.expired()){ if(parent.lock()->GetName()=="Monster Loot Window"&&stageLoot->GetComponents().size()>0){ Menu::stack.back()->SetSelection(stageLoot->GetComponents()[0]); SoundEffect::PlaySFX("Button Click",SoundEffect::CENTERED); }else if(monsterLoot->GetComponents().size()>0){ Menu::stack.back()->SetSelection(monsterLoot->GetComponents()[0]); SoundEffect::PlaySFX("Button Click",SoundEffect::CENTERED); } } } return ""; }}}, {game->KEY_SCROLL,{"View Items",[](MenuType type){}}}, {game->KEY_SELECT,{"Go to Overworld",[](MenuType type){ Component(type,"Overworld Button")->Click(); }}}, {game->KEY_START,{"Go to Camp",[](MenuType type){ Component(type,"Next Button")->Click(); }}}, } ,{ //Button Navigation Rules {"Monster Loot Window",{ .up=[](MenuType type,Data&returnData){ #pragma region Inventory Wrapping Handling Up Macros //Make sure before using this you have #define SUBCLASS with the children class for this inventory scrollable window component! #define DetectInventory(menuClass,menuType,inventoryComponentName) \ auto&selection=Menu::menus[menuType]->GetSelection(); \ auto&itemsList=Component(menuType,inventoryComponentName)->GetComponents(); \ auto itemsWindow=Component(menuType,inventoryComponentName); \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return comp.lock()==selection.lock();}); \ if(itemsList.size()>0){ \ if(component==itemsList.end()){ \ /*Set the selected button to the last element in the list.*/ \ returnData=itemsList.back(); \ }else{ \ int invWidth=int((itemsWindow->rect.size.x-12)/(float(itemsWindow->options.size.x)+itemsWindow->options.padding)); \ std::weak_ptrselectedButton=DYNAMIC_POINTER_CAST(*component); \ int newRowIndex=selectedButton.lock()->inventoryIndex-invWidth; /*Moving up moves the cursor up an entire row.*/ \ if(newRowIndex<0){ #define DefaultBehavior \ } \ if(newRowIndex<0||newRowIndex>=itemsList.size())ERR(std::format("New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ /*Select the component that matches this new number.*/ \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return DYNAMIC_POINTER_CAST(comp)->inventoryIndex==newRowIndex;}); \ if(component==itemsList.end())ERR(std::format("WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ returnData=*component; \ } \ } \ else #define DEFAULT_WRAPPING_BEHAVIOR newRowIndex=itemsList.size()-1; #pragma endregion #define SUBCLASS MenuItemButton DetectInventory(InventoryScrollableWindowComponent,type,"Monster Loot Window") { //This means we have to wrap around. We have access to itemsList if we needed to point to a specific item in the inventory. //By returning here, you are processing returnData yourself. Otherwise manipulate newRowIndex to point to an item in the itemsList when wrapping. int colIndex=selectedButton.lock()->inventoryIndex%invWidth; //Negative index, so we have to loop around. //Get the base row index of the new inventory. auto stageLootWindow=Component(type,"Stage Loot Window"); if(stageLootWindow->GetComponents().size()>0){ int rowBaseIndex=(stageLootWindow->GetComponents().back().lock()->inventoryIndex/invWidth)*invWidth; returnData=stageLootWindow->GetComponents()[std::clamp(rowBaseIndex+colIndex,0,int(stageLootWindow->GetComponents().size()-1))]; return; }else{ returnData=selectedButton; } } DefaultBehavior{ //When nothing is found... returnData="Overworld Button"; } }, .down=[](MenuType type,Data&returnData){ #pragma region Inventory Wrapping Handling Down Macros //Make sure before using this you have #define SUBCLASS with the children class for this inventory scrollable window component! #define DetectInventory(menuClass,menuType,inventoryComponentName) \ auto&selection=Menu::menus[menuType]->GetSelection(); \ auto&itemsList=Component(menuType,inventoryComponentName)->GetComponents(); \ auto itemsWindow=Component(menuType,inventoryComponentName); \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return comp.lock()==selection.lock();}); \ if(itemsList.size()>0){ \ if(component==itemsList.end()){ \ /*Set the selected button to the last element in the list.*/ \ returnData=itemsList.front();\ }else{ \ int invWidth=int((itemsWindow->rect.size.x-12)/(float(itemsWindow->options.size.x)+itemsWindow->options.padding)); \ std::weak_ptrselectedButton=DYNAMIC_POINTER_CAST(*component); \ int newRowIndex=selectedButton.lock()->inventoryIndex+invWidth; /*Moving down moves the cursor down an entire row.*/ \ if(newRowIndex>=itemsList.size()){ \ int currentRow=newRowIndex/invWidth; \ /*The logic here is, if we clamp this row index to the last possible index and we're still on the same row, it means there is at least an item on this row. So we go to that instead.*/ \ newRowIndex=std::clamp(newRowIndex,0,int(itemsList.size()-1)); \ int newRow=newRowIndex/invWidth; \ if(currentRow!=newRow) //This means we are on a different row, so the row we went down on didn't have any items. Simply drop down to the OK Button. #define DefaultBehavior \ } \ if(newRowIndex<0||newRowIndex>=itemsList.size())ERR(std::format("New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ /*Select the component that matches this new number.*/ \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return DYNAMIC_POINTER_CAST(comp)->inventoryIndex==newRowIndex;}); \ if(component==itemsList.end())ERR(std::format("WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ returnData=*component; \ } \ } \ else #define DEFAULT_WRAPPING_BEHAVIOR newRowIndex=0; #pragma endregion #define SUBCLASS MenuItemButton DetectInventory(InventoryScrollableWindowComponent,type,"Monster Loot Window") { //This means we have to wrap around. We have access to itemsList if we needed to point to a specific item in the inventory. //By returning here, you are processing returnData yourself. Otherwise manipulate newRowIndex to point to an item in the itemsList when wrapping. int colIndex=selectedButton.lock()->inventoryIndex%invWidth; //Get the base row index of the new inventory. auto stageLootWindow=Component(type,"Stage Loot Window"); if(stageLootWindow->GetComponents().size()>0){ returnData=stageLootWindow->GetComponents()[std::clamp(colIndex,0,int(stageLootWindow->GetComponents().size()-1))]; return; }else{ returnData=selectedButton; } } DefaultBehavior{ //When nothing is found... returnData="Overworld Button"; } }, .left=[](MenuType type,Data&returnData){ #pragma region Inventory Wrapping Handling Left Macros #define DetectInventory(menuClass,menuType,inventoryComponentName) \ auto&selection=Menu::menus[menuType]->GetSelection(); \ auto&itemsList=Component(menuType,inventoryComponentName)->GetComponents(); \ auto itemsWindow=Component(menuType,inventoryComponentName); \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return comp.lock()==selection.lock();}); \ if(itemsList.size()>0){ \ if(component==itemsList.end()){ \ /*Set the selected button to the last element in the list.*/ \ returnData=itemsList.back(); \ }else{ \ int invWidth=int((itemsWindow->rect.size.x-12)/(float(itemsWindow->options.size.x)+itemsWindow->options.padding)); \ std::weak_ptrselectedButton=DYNAMIC_POINTER_CAST(*component); \ int rowBaseIndex=(selectedButton.lock()->inventoryIndex/invWidth)*invWidth; \ int newRowIndex=selectedButton.lock()->inventoryIndex-1; \ if(newRowIndex=itemsList.size())ERR(std::format("New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ /*Select the component that matches this new number.*/ \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return DYNAMIC_POINTER_CAST(comp)->inventoryIndex==newRowIndex;}); \ if(component==itemsList.end())ERR(std::format("WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ returnData=*component; \ } \ }else #define DEFAULT_WRAPPING_BEHAVIOR newRowIndex+=invWidth; #pragma endregion #define SUBCLASS MenuItemButton DetectInventory(InventoryScrollableWindowComponent,type,"Monster Loot Window") { //This means we have to wrap around. We have access to itemsList if we needed to point to a specific item in the inventory. //By returning here, you are processing returnData yourself. Otherwise manipulate newRowIndex to point to an item in the itemsList when wrapping. returnData="Overworld Button"; return; } DefaultBehavior{ //When nothing is found... returnData="Overworld Button"; } }, .right=[](MenuType type,Data&returnData){ #pragma region Inventory Wrapping Handling Right Macros #define DetectInventory(menuClass,menuType,inventoryComponentName) \ auto&selection=Menu::menus[menuType]->GetSelection(); \ auto&itemsList=Component(menuType,inventoryComponentName)->GetComponents(); \ auto itemsWindow=Component(menuType,inventoryComponentName); \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return comp.lock()==selection.lock();}); \ if(itemsList.size()>0){ \ if(component==itemsList.end()){ \ /*Set the selected button to the last element in the list.*/ \ returnData=itemsList.front(); \ }else{ \ int invWidth=int((itemsWindow->rect.size.x-12)/(float(itemsWindow->options.size.x)+itemsWindow->options.padding)); \ std::weak_ptrselectedButton=DYNAMIC_POINTER_CAST(*component); \ int rowBaseIndex=(selectedButton.lock()->inventoryIndex/invWidth)*invWidth; \ int newRowIndex=selectedButton.lock()->inventoryIndex+1; \ int newRowBaseIndex = (newRowIndex / invWidth) * invWidth; \ if(rowBaseIndex!=newRowBaseIndex||newRowIndex>=itemsList.size()) #define DefaultBehavior \ newRowIndex=std::clamp(newRowIndex,0,int(itemsList.size()-1)); \ newRowIndex=std::min(int(itemsList.size())-1,newRowIndex); \ if(newRowIndex<0||newRowIndex>=itemsList.size())ERR(std::format("New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ /*Select the component that matches this new number.*/ \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return DYNAMIC_POINTER_CAST(comp)->inventoryIndex==newRowIndex;}); \ if(component==itemsList.end())ERR(std::format("WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ returnData=*component; \ } \ }else #define DEFAULT_WRAPPING_BEHAVIOR \ int rowBaseIndex=(selectedButton.lock()->inventoryIndex/invWidth)*invWidth; \ newRowIndex=rowBaseIndex; #pragma endregion #define SUBCLASS MenuItemButton DetectInventory(InventoryScrollableWindowComponent,type,"Monster Loot Window") { //This means we have to wrap around. We have access to itemsList if we needed to point to a specific item in the inventory. //By returning here, you are processing returnData yourself. Otherwise manipulate newRowIndex to point to an item in the itemsList when wrapping. returnData="Overworld Button"; return; } DefaultBehavior{ //When nothing is found... returnData="Overworld Button"; } },}}, {"Stage Loot Window",{ .up=[](MenuType type,Data&returnData){ #pragma region Inventory Wrapping Handling Up Macros //Make sure before using this you have #define SUBCLASS with the children class for this inventory scrollable window component! #define DetectInventory(menuClass,menuType,inventoryComponentName) \ auto&selection=Menu::menus[menuType]->GetSelection(); \ auto&itemsList=Component(menuType,inventoryComponentName)->GetComponents(); \ auto itemsWindow=Component(menuType,inventoryComponentName); \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return comp.lock()==selection.lock();}); \ if(itemsList.size()>0){ \ if(component==itemsList.end()){ \ /*Set the selected button to the last element in the list.*/ \ returnData=itemsList.back(); \ }else{ \ int invWidth=int((itemsWindow->rect.size.x-12)/(float(itemsWindow->options.size.x)+itemsWindow->options.padding)); \ std::weak_ptrselectedButton=DYNAMIC_POINTER_CAST(*component); \ int newRowIndex=selectedButton.lock()->inventoryIndex-invWidth; /*Moving up moves the cursor up an entire row.*/ \ if(newRowIndex<0){ #define DefaultBehavior \ } \ if(newRowIndex<0||newRowIndex>=itemsList.size())ERR(std::format("New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ /*Select the component that matches this new number.*/ \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return DYNAMIC_POINTER_CAST(comp)->inventoryIndex==newRowIndex;}); \ if(component==itemsList.end())ERR(std::format("WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ returnData=*component; \ } \ } \ else #define DEFAULT_WRAPPING_BEHAVIOR newRowIndex=itemsList.size()-1; #pragma endregion #define SUBCLASS MenuItemButton DetectInventory(InventoryScrollableWindowComponent,type,"Stage Loot Window") { //This means we have to wrap around. We have access to itemsList if we needed to point to a specific item in the inventory. //By returning here, you are processing returnData yourself. Otherwise manipulate newRowIndex to point to an item in the itemsList when wrapping. int colIndex=selectedButton.lock()->inventoryIndex%invWidth; //Get the base row index of the new inventory. auto stageLootWindow=Component(type,"Monster Loot Window"); if(stageLootWindow->GetComponents().size()>0){ int rowBaseIndex=(stageLootWindow->GetComponents().back().lock()->inventoryIndex/invWidth)*invWidth; returnData=stageLootWindow->GetComponents()[std::clamp(rowBaseIndex+colIndex,0,int(stageLootWindow->GetComponents().size()-1))]; return; }else{ returnData=selectedButton; } } DefaultBehavior{ //When nothing is found... returnData="Next Button"; } }, .down=[](MenuType type,Data&returnData){ #pragma region Inventory Wrapping Handling Down Macros //Make sure before using this you have #define SUBCLASS with the children class for this inventory scrollable window component! #define DetectInventory(menuClass,menuType,inventoryComponentName) \ auto&selection=Menu::menus[menuType]->GetSelection(); \ auto&itemsList=Component(menuType,inventoryComponentName)->GetComponents(); \ auto itemsWindow=Component(menuType,inventoryComponentName); \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return comp.lock()==selection.lock();}); \ if(itemsList.size()>0){ \ if(component==itemsList.end()){ \ /*Set the selected button to the last element in the list.*/ \ returnData=itemsList.front();\ }else{ \ int invWidth=int((itemsWindow->rect.size.x-12)/(float(itemsWindow->options.size.x)+itemsWindow->options.padding)); \ std::weak_ptrselectedButton=DYNAMIC_POINTER_CAST(*component); \ int newRowIndex=selectedButton.lock()->inventoryIndex+invWidth; /*Moving down moves the cursor down an entire row.*/ \ if(newRowIndex>=itemsList.size()){ \ int currentRow=newRowIndex/invWidth; \ /*The logic here is, if we clamp this row index to the last possible index and we're still on the same row, it means there is at least an item on this row. So we go to that instead.*/ \ newRowIndex=std::clamp(newRowIndex,0,int(itemsList.size()-1)); \ int newRow=newRowIndex/invWidth; \ if(currentRow!=newRow) //This means we are on a different row, so the row we went down on didn't have any items. Simply drop down to the OK Button. #define DefaultBehavior \ } \ if(newRowIndex<0||newRowIndex>=itemsList.size())ERR(std::format("New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ /*Select the component that matches this new number.*/ \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return DYNAMIC_POINTER_CAST(comp)->inventoryIndex==newRowIndex;}); \ if(component==itemsList.end())ERR(std::format("WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ returnData=*component; \ } \ } \ else #define DEFAULT_WRAPPING_BEHAVIOR newRowIndex=0; #pragma endregion #define SUBCLASS MenuItemButton DetectInventory(InventoryScrollableWindowComponent,type,"Stage Loot Window") { //This means we have to wrap around. We have access to itemsList if we needed to point to a specific item in the inventory. //By returning here, you are processing returnData yourself. Otherwise manipulate newRowIndex to point to an item in the itemsList when wrapping. int colIndex=selectedButton.lock()->inventoryIndex%invWidth; //Get the base row index of the new inventory. auto stageLootWindow=Component(type,"Monster Loot Window"); if(stageLootWindow->GetComponents().size()>0){ returnData=stageLootWindow->GetComponents()[std::clamp(colIndex,0,int(stageLootWindow->GetComponents().size()-1))]; return; }else{ returnData=selectedButton; } } DefaultBehavior{ //When nothing is found... returnData="Next Button"; } }, .left=[](MenuType type,Data&returnData){ #pragma region Inventory Wrapping Handling Left Macros #define DetectInventory(menuClass,menuType,inventoryComponentName) \ auto&selection=Menu::menus[menuType]->GetSelection(); \ auto&itemsList=Component(menuType,inventoryComponentName)->GetComponents(); \ auto itemsWindow=Component(menuType,inventoryComponentName); \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return comp.lock()==selection.lock();}); \ if(itemsList.size()>0){ \ if(component==itemsList.end()){ \ /*Set the selected button to the last element in the list.*/ \ returnData=itemsList.back(); \ }else{ \ int invWidth=int((itemsWindow->rect.size.x-12)/(float(itemsWindow->options.size.x)+itemsWindow->options.padding)); \ std::weak_ptrselectedButton=DYNAMIC_POINTER_CAST(*component); \ int rowBaseIndex=(selectedButton.lock()->inventoryIndex/invWidth)*invWidth; \ int newRowIndex=selectedButton.lock()->inventoryIndex-1; \ if(newRowIndex=itemsList.size())ERR(std::format("New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ /*Select the component that matches this new number.*/ \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return DYNAMIC_POINTER_CAST(comp)->inventoryIndex==newRowIndex;}); \ if(component==itemsList.end())ERR(std::format("WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ returnData=*component; \ } \ }else #define DEFAULT_WRAPPING_BEHAVIOR newRowIndex+=invWidth; #pragma endregion #define SUBCLASS MenuItemButton DetectInventory(InventoryScrollableWindowComponent,type,"Stage Loot Window") { //This means we have to wrap around. We have access to itemsList if we needed to point to a specific item in the inventory. //By returning here, you are processing returnData yourself. Otherwise manipulate newRowIndex to point to an item in the itemsList when wrapping. returnData="Next Button"; return; } DefaultBehavior{ //When nothing is found... returnData="Next Button"; } }, .right=[](MenuType type,Data&returnData){ #pragma region Inventory Wrapping Handling Right Macros #define DetectInventory(menuClass,menuType,inventoryComponentName) \ auto&selection=Menu::menus[menuType]->GetSelection(); \ auto&itemsList=Component(menuType,inventoryComponentName)->GetComponents(); \ auto itemsWindow=Component(menuType,inventoryComponentName); \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return comp.lock()==selection.lock();}); \ if(itemsList.size()>0){ \ if(component==itemsList.end()){ \ /*Set the selected button to the last element in the list.*/ \ returnData=itemsList.front(); \ }else{ \ int invWidth=int((itemsWindow->rect.size.x-12)/(float(itemsWindow->options.size.x)+itemsWindow->options.padding)); \ std::weak_ptrselectedButton=DYNAMIC_POINTER_CAST(*component); \ int rowBaseIndex=(selectedButton.lock()->inventoryIndex/invWidth)*invWidth; \ int newRowIndex=selectedButton.lock()->inventoryIndex+1; \ int newRowBaseIndex = (newRowIndex / invWidth) * invWidth; \ if(rowBaseIndex!=newRowBaseIndex||newRowIndex>=itemsList.size()) #define DefaultBehavior \ newRowIndex=std::clamp(newRowIndex,0,int(itemsList.size()-1)); \ newRowIndex=std::min(int(itemsList.size())-1,newRowIndex); \ if(newRowIndex<0||newRowIndex>=itemsList.size())ERR(std::format("New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ /*Select the component that matches this new number.*/ \ auto component=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&comp){return DYNAMIC_POINTER_CAST(comp)->inventoryIndex==newRowIndex;}); \ if(component==itemsList.end())ERR(std::format("WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING!",newRowIndex)); \ returnData=*component; \ } \ }else #define DEFAULT_WRAPPING_BEHAVIOR \ int rowBaseIndex=(selectedButton.lock()->inventoryIndex/invWidth)*invWidth; \ newRowIndex=rowBaseIndex; #pragma endregion #define SUBCLASS MenuItemButton DetectInventory(InventoryScrollableWindowComponent,type,"Stage Loot Window") { //This means we have to wrap around. We have access to itemsList if we needed to point to a specific item in the inventory. //By returning here, you are processing returnData yourself. Otherwise manipulate newRowIndex to point to an item in the itemsList when wrapping. returnData="Next Button"; return; } DefaultBehavior{ //When nothing is found... returnData="Next Button"; } },}}, {"Overworld Button",{ .up=[](MenuType type,Data&returnData){ auto monsterLoot=Component(type,"Monster Loot Window"); auto stageLoot=Component(type,"Stage Loot Window"); if(monsterLoot->GetComponents().size()>0){ int invWidth=monsterLoot->GetInventoryWidth(); returnData=monsterLoot->GetComponents()[0]; }else if(stageLoot->GetComponents().size()>0){ int invWidth=stageLoot->GetInventoryWidth(); returnData=stageLoot->GetComponents()[0]; }else{ returnData="Next Button"; } }, .down=[](MenuType type,Data&returnData){ returnData="Next Button"; }, .left=[](MenuType type,Data&returnData){ auto monsterLoot=Component(type,"Monster Loot Window"); auto stageLoot=Component(type,"Stage Loot Window"); if(monsterLoot->GetComponents().size()>0){ int invWidth=monsterLoot->GetInventoryWidth(); returnData=monsterLoot->GetComponents()[std::clamp(invWidth-1,0,int(monsterLoot->GetComponents().size()-1))]; }else if(stageLoot->GetComponents().size()>0){ int invWidth=stageLoot->GetInventoryWidth(); returnData=stageLoot->GetComponents()[std::clamp(invWidth-1,0,int(stageLoot->GetComponents().size()-1))]; }else{ returnData="Next Button"; } }, .right=[](MenuType type,Data&returnData){ auto monsterLoot=Component(type,"Monster Loot Window"); auto stageLoot=Component(type,"Stage Loot Window"); if(monsterLoot->GetComponents().size()>0){ returnData=monsterLoot->GetComponents()[0]; }else if(stageLoot->GetComponents().size()>0){ returnData=stageLoot->GetComponents()[0]; }else{ returnData="Next Button"; } },}}, {"Next Button",{ .up=[](MenuType type,Data&returnData){ returnData="Overworld Button"; }, .down=[](MenuType type,Data&returnData){ returnData="Overworld Button"; }, .left=[](MenuType type,Data&returnData){ auto monsterLoot=Component(type,"Monster Loot Window"); auto stageLoot=Component(type,"Stage Loot Window"); if(monsterLoot->GetComponents().size()>0){ int invWidth=monsterLoot->GetInventoryWidth(); returnData=monsterLoot->GetComponents()[std::clamp(invWidth-1,0,int(monsterLoot->GetComponents().size()-1))]; }else if(stageLoot->GetComponents().size()>0){ int invWidth=stageLoot->GetInventoryWidth(); returnData=stageLoot->GetComponents()[std::clamp(invWidth-1,0,int(stageLoot->GetComponents().size()-1))]; }else{ returnData="Next Button"; } }, .right=[](MenuType type,Data&returnData){ auto monsterLoot=Component(type,"Monster Loot Window"); auto stageLoot=Component(type,"Stage Loot Window"); if(monsterLoot->GetComponents().size()>0){ returnData=monsterLoot->GetComponents()[0]; }else if(stageLoot->GetComponents().size()>0){ returnData=stageLoot->GetComponents()[0]; }else{ returnData="Next Button"; } },}}, }); }