The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'!
https://forums.lestoria.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
12 KiB
261 lines
12 KiB
#pragma region License
|
|
/*
|
|
License (OLC-3)
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Copyright 2018 - 2023 OneLoneCoder.com
|
|
|
|
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 © 2023 The FreeType
|
|
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
|
All rights reserved.
|
|
*/
|
|
#pragma endregion
|
|
#pragma once
|
|
#include "Item.h"
|
|
#include <stack>
|
|
#include "safemap.h"
|
|
#include "Theme.h"
|
|
#include "Attributable.h"
|
|
#include "olcUTIL_Geometry2D.h"
|
|
#include "olcPGEX_ViewPort.h"
|
|
|
|
class Crawler;
|
|
class MenuComponent;
|
|
class ScrollableWindowComponent;
|
|
|
|
//Add a component to a menu using this macro. Follow-up with END at the end of it.
|
|
#define ADD(key,componentType) _AddComponent<componentType>(key,NEW componentType
|
|
#define END )
|
|
#define DEPTH ,
|
|
|
|
#define DEFAULT_DEPTH -999999
|
|
#define STARTING_DEPTH 999999
|
|
|
|
enum MenuType{
|
|
#pragma region Enum Start //DO NOT REMOVE
|
|
///////////////////////////////////////////////////////////
|
|
/*DO NOT REMOVE!!*/ENUM_START,///////////////////////////////
|
|
///////////////////////////////////////////////////////////
|
|
#pragma endregion
|
|
INVENTORY_CONSUMABLES,
|
|
CLASS_INFO,
|
|
CLASS_SELECTION,
|
|
MAIN_MENU,
|
|
OVERWORLD_LEVEL_SELECT,
|
|
ITEM_LOADOUT,
|
|
LEVEL_COMPLETE,
|
|
OVERWORLD_MENU,
|
|
CHARACTER_MENU,
|
|
INVENTORY,
|
|
MERCHANT,
|
|
BUY_ITEM,
|
|
SELL_ITEM,
|
|
#pragma region Enum End //DO NOT REMOVE
|
|
///////////////////////////////////////////////////////////
|
|
/*DO NOT REMOVE!!*/ENUM_END////////////////////////////////
|
|
///////////////////////////////////////////////////////////
|
|
#pragma endregion
|
|
};
|
|
|
|
class Menu:public IAttributable{
|
|
static void InitializeConsumableInventoryWindow();
|
|
static void InitializeClassInfoWindow();
|
|
static void InitializeClassSelectionWindow();
|
|
static void InitializeMainMenuWindow();
|
|
static void InitializeOverworldMapLevelWindow();
|
|
static void InitializeItemLoadoutWindow();
|
|
static void InitializeLevelCompleteWindow();
|
|
static void InitializeOverworldMenuWindow();
|
|
static void InitializeCharacterMenuWindow();
|
|
static void InitializeInventoryWindow();
|
|
static void InitializeMerchantWindow();
|
|
static void InitializeBuyItemWindow();
|
|
static void InitializeSellItemWindow();
|
|
|
|
friend class Crawler;
|
|
friend struct Player;
|
|
friend class ItemInfo;
|
|
friend class PlayerStats;
|
|
|
|
float buttonHoldTime=0;
|
|
vi2d selection={-1,-1};
|
|
vi2d lastActiveMousePos={};
|
|
int componentCount;
|
|
|
|
MenuComponent*draggingComponent=nullptr;
|
|
ViewPort window;
|
|
static safemap<ITCategory,std::vector<MenuComponent*>>inventoryListeners; //All menu components that care about inventory updates subscribe to this list indirectly (See Menu::AddInventoryListener()).
|
|
static safemap<ITCategory,std::vector<MenuComponent*>>merchantInventoryListeners; //All menu components that care about merchant inventory updates subscribe to this list indirectly (See Menu::AddMerchantInventoryListener()).
|
|
static std::vector<MenuComponent*>equipStatListeners; //All menu components that care about stat/equip updates subscribe to this list indirectly (See Menu::AddStatListener()).
|
|
public:
|
|
//The constructor is private. Use CreateMenu() instead!
|
|
Menu()=default;
|
|
~Menu();
|
|
//DO NOT USE DIRECTLY! You should be utilizing the ADD macro for adding components.
|
|
template<class T>
|
|
T*_AddComponent(std::string componentKey,T*component,int depth=DEFAULT_DEPTH){
|
|
component->parentMenu=type;
|
|
if(depth==DEFAULT_DEPTH){
|
|
component->depth=STARTING_DEPTH-componentCount;
|
|
}else{
|
|
component->depth=depth;
|
|
}
|
|
if(component->selectable){
|
|
buttons.Unlock();
|
|
if(buttons.count(int(component->rect.pos.y))){
|
|
buttons.at(int(component->rect.pos.y)).push_back(component);
|
|
}else{
|
|
buttons[int(component->rect.pos.y)].push_back(component);
|
|
}
|
|
if(component->selectableViaKeyboard){
|
|
keyboardButtons.Unlock();
|
|
if(keyboardButtons.count(int(component->rect.pos.y))){
|
|
keyboardButtons.at(int(component->rect.pos.y)).push_back(component);
|
|
}else{
|
|
keyboardButtons[int(component->rect.pos.y)].push_back(component);
|
|
}
|
|
}
|
|
|
|
//We must lock the values before calling sort. Sort seems to try and create new accesses.
|
|
buttons.SetInitialized();
|
|
keyboardButtons.SetInitialized();
|
|
|
|
//We make an assumption that menu components are supposed to be in left-to-right order. Sometimes we may add things out-of-order, so this fixes the problem by sorting the items afterwards.
|
|
std::sort(buttons[int(component->rect.pos.y)].begin(),buttons[int(component->rect.pos.y)].end(),[](auto c1,auto c2){
|
|
return c1->GetPos().x<c2->GetPos().x;
|
|
});
|
|
if(keyboardButtons.count(int(component->rect.pos.y))){ //Keyboard buttons may not necessarily contain this key...Let's be sure.
|
|
std::sort(keyboardButtons[int(component->rect.pos.y)].begin(),keyboardButtons[int(component->rect.pos.y)].end(),[](auto c1,auto c2){
|
|
return c1->GetPos().x<c2->GetPos().x;
|
|
});
|
|
}
|
|
}else{
|
|
displayComponents.push_back(component);
|
|
}
|
|
|
|
RecalculateComponentCount();
|
|
|
|
if(components.count(componentKey)){
|
|
ERR("WARNING! Key "<<componentKey<<" for this sub-menu already exists! Key names must be unique!")
|
|
}
|
|
component->name=componentKey;
|
|
components.Unlock(); //It's possible we can add a component later on, so we will make sure we remove the lock first.
|
|
components[componentKey]=component;
|
|
components.SetInitialized();
|
|
lastRegisteredComponent=componentKey;
|
|
std::erase_if(Menu::unhandledComponents,[&](auto b1){return b1==component;});
|
|
|
|
return component;
|
|
}
|
|
void Update(Crawler*game);
|
|
void Draw(Crawler*game);
|
|
static void InitializeMenuListenerCategory(const std::string&category);
|
|
static void InitializeMenus();
|
|
static void LockInListeners();
|
|
static void OpenMenu(MenuType menu,bool cover=true);
|
|
static void CloseMenu();
|
|
static void CloseAllMenus();
|
|
static void CleanupAllMenus();
|
|
static std::vector<Menu*>stack;
|
|
static std::string themeSelection;
|
|
static safeunorderedmap<std::string,Theme>themes;
|
|
static std::vector<MenuComponent*>unhandledComponents; //This list contains MenuComponents that are created and haven't been assigned via _AddComponent. If we get to the end of menu initialization and there are any components in this vector, we have leaked memory and will report this.
|
|
static const vf2d CENTERED;
|
|
static bool IsMenuOpen();
|
|
MenuType GetType();
|
|
safemap<std::string,MenuComponent*>components; //A friendly way to interrogate any component we are interested in.
|
|
std::vector<MenuComponent*>displayComponents; //Components that are only for displaying purposes.
|
|
static std::map<MenuType,Menu*>menus;
|
|
vf2d pos; //Specify the upper-left corner of the window. Using CENTERED will always put this where the upper-left corner would center the window.
|
|
vf2d size; //Size in tiles (24x24), every menu will be tile-based
|
|
safemap<int/*Y*/,std::vector<MenuComponent*>>buttons; //Buttons are stored in rows followed by their column order.
|
|
safemap<int/*Y*/,std::vector<MenuComponent*>>keyboardButtons; //Button ordered storage for keyboard/menu
|
|
|
|
static Theme&GetCurrentTheme();
|
|
bool UsingMouseNavigation();
|
|
void SetMouseNavigation(bool mouseNavigation);
|
|
static void InventorySlotsUpdated(ITCategory cat); //Called whenever the player's inventory gets modified.
|
|
static void MerchantInventorySlotsUpdated(ITCategory cat); //Called whenever a traveling merchant's inventory item gets updated.
|
|
static void AddInventoryListener(MenuComponent*component,ITCategory category); //Adds a component to be in a given listener category.
|
|
static void AddMerchantInventoryListener(MenuComponent*component,ITCategory category); //Adds a component to be in a given listener category.
|
|
static void AddEquipStatListener(MenuComponent*component); //Adds a component to be in an equip stat listener. Will receive updates whenever stats are updated via equips.
|
|
vf2d center();
|
|
//Returns the last menu type created and last registered component, in case a component is detected as memory leaking, provides this information to each component for safety.
|
|
static std::pair<MenuType,std::string>GetMemoryLeakReportInfo();
|
|
virtual void Cleanup();
|
|
|
|
static void DrawThemedWindow(vf2d menuPos,vf2d size,Pixel renderColor=WHITE);
|
|
|
|
//X (0-2), Y (0-2) for specific 9-patch tile (tiled version).
|
|
static Renderable&GetPatchPart(int x,int y);
|
|
void RecalculateComponentCount();
|
|
private:
|
|
Menu(vf2d pos,vf2d size);
|
|
static MenuType lastMenuTypeCreated;
|
|
static std::string lastRegisteredComponent;
|
|
void HoverMenuSelect(Crawler*game);
|
|
void MenuSelect(Crawler*game);
|
|
void CheckClickAndPerformMenuSelect(Crawler*game);
|
|
//Mandatory before any menu operations! This creates and sets up the menu in memory.
|
|
static Menu*CreateMenu(MenuType type,vf2d pos,vf2d size);
|
|
|
|
void KeyboardButtonNavigation(Crawler*game,vf2d menuPos);
|
|
static void DrawScaledWindowBackground(Crawler*game,vf2d menuPos,vf2d size,Pixel renderColor);
|
|
static void DrawTiledWindowBackground(Crawler*game,vf2d menuPos,vf2d size,Pixel renderColor);
|
|
static void DrawScaledWindowBorder(Crawler*game,vf2d menuPos,vf2d size,Pixel renderColor);
|
|
static void DrawTiledWindowBorder(Crawler*game,vf2d menuPos,vf2d size,Pixel renderColor);
|
|
|
|
//This triggers if we use a keyboard/controller input to try and select some off-screen menu item. We should ideally follow the menu cursor.
|
|
bool HandleOutsideDisabledButtonSelection(MenuComponent*disabledButton);
|
|
|
|
Pixel GetRenderColor();
|
|
MenuType type;
|
|
|
|
static bool MOUSE_NAVIGATION;
|
|
bool cover; //A black cover for when a menu pops up to fade out the stuff behind it.
|
|
};
|
|
|
|
template<typename T>
|
|
T*Component(MenuType menu,std::string componentName){
|
|
T*tmp=dynamic_cast<T*>(Menu::menus[menu]->components[componentName]);
|
|
if(tmp!=nullptr){
|
|
return tmp;
|
|
}else{
|
|
ERR("WARNING! Attempting to cast a button that isn't a "<<typeid(T).name()<<"!");
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
struct MenuFuncData{
|
|
Menu&menu;
|
|
Crawler*game;
|
|
MenuComponent*component;
|
|
ScrollableWindowComponent*parentComponent;
|
|
};
|
|
|
|
using MenuFunc=std::function<bool(MenuFuncData)>; |