# 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"
# include "InputHelper.h"
class AiL ;
class MenuComponent ;
class ScrollableWindowComponent ;
struct Navigation ;
class Menu ;
struct MenuFuncData {
Menu & menu ;
AiL * const game ;
const std : : weak_ptr < MenuComponent > component ;
const std : : weak_ptr < ScrollableWindowComponent > parentComponent = { } ;
MenuFuncData ( Menu & menu , AiL * const game , std : : weak_ptr < MenuComponent > component , std : : weak_ptr < ScrollableWindowComponent > parentComponent = { } ) ;
} ;
struct ToggleFuncData : public MenuFuncData {
bool checked ;
ToggleFuncData ( Menu & menu , AiL * const game , std : : weak_ptr < MenuComponent > component , std : : weak_ptr < ScrollableWindowComponent > parentComponent , bool checked ) ;
} ;
using MenuFunc = std : : function < bool ( MenuFuncData ) > ;
using ToggleFunc = std : : function < bool ( ToggleFuncData ) > ;
//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,std::make_shared<componentType>
# define END )
# define DEPTH ,
# define DEFAULT_DEPTH -999999
# define STARTING_DEPTH 999999
using Data = std : : variant < ButtonName , std : : weak_ptr < MenuComponent > > ;
using MenuDataFunc = std : : function < void ( MenuType , Data & ) > ;
using ButtonNavigationGroups = std : : map < ButtonName , Navigation > ;
struct Navigation {
std : : variant < ButtonName , MenuDataFunc > up ;
std : : variant < ButtonName , MenuDataFunc > down ;
std : : variant < ButtonName , MenuDataFunc > left ;
std : : variant < ButtonName , MenuDataFunc > right ;
} ;
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 ( ) ;
static void InitializeSettingsWindow ( ) ;
friend class AiL ;
friend class ItemInfo ;
friend class EntityStats ;
float buttonHoldTime = 0 ;
static vi2d lastActiveMousePos ;
int componentCount = 0 ;
float componentSelectionIndex = 0.f ;
std : : unique_ptr < MenuComponent > draggingComponent ;
ViewPort window ;
static safemap < ITCategory , std : : vector < std : : weak_ptr < MenuComponent > > > inventoryListeners ; //All menu components that care about inventory updates subscribe to this list indirectly (See Menu::AddInventoryListener()).
static safemap < ITCategory , std : : vector < std : : weak_ptr < MenuComponent > > > merchantInventoryListeners ; //All menu components that care about merchant inventory updates subscribe to this list indirectly (See Menu::AddMerchantInventoryListener()).
static std : : vector < std : : weak_ptr < MenuComponent > > equipStatListeners ; //All menu components that care about stat/equip updates subscribe to this list indirectly (See Menu::AddStatListener()).
static std : : vector < std : : weak_ptr < 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 ;
//DO NOT USE DIRECTLY! You should be utilizing the ADD macro for adding components.
template < class T >
std : : shared_ptr < T > _AddComponent ( std : : string componentKey , std : : shared_ptr < T > component , int depth = DEFAULT_DEPTH ) {
component - > parentMenu = type ;
if ( depth = = DEFAULT_DEPTH ) {
component - > depth = STARTING_DEPTH - componentCount ;
} else {
component - > depth = depth ;
}
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 ;
RecalculateComponentCount ( ) ;
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 const vf2d CENTERED ;
static bool IsMenuOpen ( ) ;
const MenuType GetType ( ) const ;
safemap < std : : string , std : : shared_ptr < MenuComponent > > components ; //A friendly way to interrogate any component we are interested in.
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
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 ( std : : weak_ptr < MenuComponent > component , ITCategory category ) ; //Adds a component to be in a given listener category.
static void AddMerchantInventoryListener ( std : : weak_ptr < MenuComponent > component , ITCategory category ) ; //Adds a component to be in a given listener category.
static void AddEquipStatListener ( std : : weak_ptr < MenuComponent > component ) ; //Adds a component to be in an equip stat listener. Will receive updates whenever stats are updated via equips.
static void AddChapterListener ( std : : weak_ptr < 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 ( ) ;
void SetSelection ( std : : string_view button , const bool scroll = true , const bool reset = false ) ; // Use the reset parameter when a window is opening up, as this will cause the window now to scroll to its previous target.
void SetSelection ( std : : weak_ptr < MenuComponent > button , const bool scroll = true , const bool reset = false ) ; // Use the reset parameter when a window is opening up, as this will cause the window now to scroll to its previous target.
void SetSelection ( std : : variant < std : : string , std : : weak_ptr < MenuComponent > > button , const bool reset = false ) ; // Use the reset parameter when a window is opening up, as this will cause the window now to scroll to its previous target.
const std : : weak_ptr < MenuComponent > GetSelection ( ) const ;
const std : : weak_ptr < MenuComponent > GetKeySelection ( ) const ;
void IncreaseSelectionIndex ( const float val ) ;
void DecreaseSelectionIndex ( const float val ) ;
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 SetupKeyboardNavigation ( MenuDataFunc onOpen , MenuInputGroups inputGroups , ButtonNavigationGroups navigationGroups ) ;
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 ( std : : weak_ptr < MenuComponent > disabledButton ) ;
Pixel GetRenderColor ( ) ;
MenuType type ;
MenuInputGroups inputGroups ;
ButtonNavigationGroups navigationGroups ;
MenuDataFunc onOpenFunc ;
std : : weak_ptr < MenuComponent > selection ;
std : : weak_ptr < MenuComponent > keyboardSelection ;
InputHelper helpDisplay ;
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 >
std : : shared_ptr < T > Component ( MenuType menu , std : : string componentName ) {
return DYNAMIC_POINTER_CAST < T > ( Menu : : menus [ menu ] - > components [ componentName ] ) ;
}