# pragma region License
/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Copyright 2024 Joshua Sigona < sigonasr2 @ gmail . 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 AiL ;
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 {
///////////////////////////////////////////////////////////
/*DO NOT REMOVE!!*/ ENUM_START , ///////////////////////////////
///////////////////////////////////////////////////////////
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 ,
BLACKSMITH ,
CRAFT_ITEM ,
CRAFT_CONSUMABLE ,
CONSUMABLE_CRAFT_ITEM ,
SAVE_FILE_NAME ,
LOAD_GAME ,
USER_ID ,
///////////////////////////////////////////////////////////
/*DO NOT REMOVE!!*/ ENUM_END ////////////////////////////////
///////////////////////////////////////////////////////////
} ;
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 ( ) ;
static void InitializeBlacksmithCraftingWindow ( ) ;
static void InitializeCraftItemWindow ( ) ;
static void InitializeConsumableCraftingWindow ( ) ;
static void InitializeConsumableCraftItemWindow ( ) ;
static void InitializeSaveFileWindow ( ) ;
static void InitializeLoadGameWindow ( ) ;
static void InitializeUserIDWindow ( ) ;
friend class AiL ;
friend class ItemInfo ;
friend class EntityStats ;
float buttonHoldTime = 0 ;
vi2d selection = { - 1 , - 1 } ;
vi2d lastActiveMousePos = { } ;
int componentCount = 0 ;
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()).
static std : : vector < MenuComponent * > chapterListeners ; //All menu components that care about story chapter updates subscribe to this list indirectly (See Menu::AddChapterListener()).
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 ( AiL * game ) ;
void Draw ( AiL * 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 ( ) ;
const MenuType GetType ( ) const ;
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 ( ) ;
static 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.
static void AddChapterListener ( MenuComponent * component ) ; //Adds a component to be in a chapter listener. Will receive updates anytime the chapter in-game changes.
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 ( AiL * game ) ;
void MenuSelect ( AiL * game ) ;
void CheckClickAndPerformMenuSelect ( AiL * 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 ( AiL * game , vf2d menuPos ) ;
static void DrawScaledWindowBackground ( AiL * game , vf2d menuPos , vf2d size , Pixel renderColor ) ;
static void DrawTiledWindowBackground ( AiL * game , vf2d menuPos , vf2d size , Pixel renderColor ) ;
static void DrawScaledWindowBorder ( AiL * game , vf2d menuPos , vf2d size , Pixel renderColor ) ;
static void DrawTiledWindowBorder ( AiL * 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 ] ) ;
return tmp ;
}
struct MenuFuncData {
Menu & menu ;
AiL * const game ;
MenuComponent * const component ;
ScrollableWindowComponent * const parentComponent = nullptr ;
MenuFuncData ( Menu & menu , AiL * const game , MenuComponent * const component , ScrollableWindowComponent * const parentComponent = nullptr ) ;
} ;
using MenuFunc = std : : function < bool ( MenuFuncData ) > ;