# 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
# include "Crawler.h"
# include "MenuComponent.h"
# include "DEFINES.h"
# include "safemap.h"
# include "Item.h"
# include "MenuItemButton.h"
# include "ScrollableWindowComponent.h"
bool Menu : : MOUSE_NAVIGATION = true ;
std : : vector < Menu * > Menu : : stack ;
std : : map < MenuType , Menu * > Menu : : menus ;
std : : string Menu : : themeSelection = " BlueDefault " ;
safeunorderedmap < std : : string , Theme > Menu : : themes ;
safemap < ITCategory , std : : vector < MenuComponent * > > Menu : : inventoryListeners ;
std : : vector < MenuComponent * > Menu : : equipStatListeners ;
const vf2d Menu : : CENTERED = { - 456 , - 456 } ;
std : : vector < MenuComponent * > Menu : : unhandledComponents ;
//////////////////////////////////////////////////////////////////////////////////////////////////
///__/////////////////////////////////////////////////////////////////////////////////////////////
//| |////////////////////////////////////////////////////////////////////////////////////////////
//| |/////WARNING! If you are adding something here you likely are adding another container with MenuComponent pointers in it right now.
//| |/////Because we are handling raw pointers, you must also add this container to the list of iterating search removal containers that occur in the
//| |/////DESTRUCTOR of MenuComponents!!!!! (Go to MenuComponent::~MenuComponent()) THIS IS NOT A DRILL!
//|__|////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
///__/////////////////////////////////////////////////////////////////////////////////////////////
//|__/////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
MenuType Menu : : lastMenuTypeCreated ;
std : : string Menu : : lastRegisteredComponent ;
INCLUDE_game
INCLUDE_GFX
extern vi2d WINDOW_SIZE ;
using A = Attribute ;
Menu : : Menu ( vf2d pos , vf2d size )
: pos ( pos = = CENTERED ? WINDOW_SIZE / 2 - size / 2 : vi2d { pos } ) , size ( size ) {
r . Create ( uint32_t ( size . x ) , uint32_t ( size . y ) ) ;
overlay . Create ( WINDOW_SIZE . x , WINDOW_SIZE . y ) ;
}
Menu : : ~ Menu ( ) {
for ( auto & [ key , value ] : components ) {
delete value ;
}
}
void Menu : : InitializeMenus ( ) {
# define MAX_MENUS 32
stack . reserve ( MAX_MENUS ) ;
InitializeConsumableInventoryWindow ( ) ;
InitializeClassSelectionWindow ( ) ;
InitializeClassInfoWindow ( ) ;
InitializeMainMenuWindow ( ) ;
InitializeOverworldMapLevelWindow ( ) ;
InitializeItemLoadoutWindow ( ) ;
InitializeLevelCompleteWindow ( ) ;
InitializeOverworldMenuWindow ( ) ;
InitializeCharacterMenuWindow ( ) ;
InitializeInventoryWindow ( ) ;
for ( MenuType type = MenuType ( int ( MenuType : : ENUM_START ) + 1 ) ; type < MenuType : : ENUM_END ; type = MenuType ( int ( type + 1 ) ) ) {
if ( menus . count ( type ) = = 0 ) {
ERR ( " WARNING! Menu Type " < < type < < " does not exist! " )
}
//Lock up everything once it's done.
menus [ type ] - > buttons . SetInitialized ( ) ;
menus [ type ] - > keyboardButtons . SetInitialized ( ) ;
for ( auto & [ key , value ] : menus [ type ] - > components ) {
MenuComponent * component = value ;
component - > AfterCreate ( ) ;
}
if ( menus . size ( ) > MAX_MENUS ) ERR ( " WARNING! Exceeded maximum expected menu count of " < < MAX_MENUS < < " ! " ) ;
}
if ( Menu : : unhandledComponents . size ( ) > 0 ) {
std : : cout < < " WARNING! There are " < < Menu : : unhandledComponents . size ( ) < < " components that were not added to any Menu! These have been leaked and should not be happening! " < < std : : endl ;
std : : cout < < " See below for a report of unhandled components: " < < std : : endl ;
int count = 0 ;
for ( MenuComponent * component : Menu : : unhandledComponents ) {
std : : cout < < " \t Component " < < ( count + 1 ) < < " : " < < std : : endl ;
std : : cout < < " \t -Parent Menu: " < < component - > parentMenu < < " Label: " < < component - > label < < std : : endl ;
std : : cout < < " \t \t Created Inside of Menu: " < < component - > memoryLeakInfo . first < < " // Last Component: " < < component - > memoryLeakInfo . second < < std : : endl ;
count + + ;
}
ERR ( " " )
}
}
Menu * Menu : : CreateMenu ( MenuType type , vf2d pos , vf2d size ) {
menus [ type ] = NEW Menu ( pos , size ) ;
menus [ type ] - > type = type ;
lastMenuTypeCreated = type ;
return menus . at ( type ) ;
}
void Menu : : CheckClickAndPerformMenuSelect ( Crawler * game ) {
if ( game - > GetMouse ( Mouse : : LEFT ) . bReleased | | game - > GetKey ( SPACE ) . bReleased | | game - > GetKey ( ENTER ) . bReleased ) {
MenuSelect ( game ) ;
}
}
void Menu : : HoverMenuSelect ( Crawler * game ) {
if ( selection = = vi2d { - 1 , - 1 } | | buttons [ selection . y ] [ selection . x ] - > disabled ) return ;
if ( buttons [ selection . y ] [ selection . x ] - > draggable ) {
if ( buttonHoldTime < " ThemeGlobal.MenuHoldTime " _F ) {
CheckClickAndPerformMenuSelect ( game ) ;
} else {
draggingComponent = buttons [ selection . y ] [ selection . x ] - > PickUpDraggableItem ( ) ;
buttonHoldTime = 0 ;
}
} else {
CheckClickAndPerformMenuSelect ( game ) ;
}
}
void Menu : : MenuSelect ( Crawler * game ) {
if ( selection = = vi2d { - 1 , - 1 } | | buttons [ selection . y ] [ selection . x ] - > disabled ) return ;
bool buttonStillValid = buttons [ selection . y ] [ selection . x ] - > onClick ( MenuFuncData { * this , game , buttons [ selection . y ] [ selection . x ] , ( ScrollableWindowComponent * ) buttons [ selection . y ] [ selection . x ] - > parentComponent } ) ;
if ( buttonStillValid ) {
if ( buttons [ selection . y ] [ selection . x ] - > menuDest ! = MenuType : : ENUM_END ) {
if ( stack . size ( ) < 32 ) {
stack . push_back ( menus [ buttons [ selection . y ] [ selection . x ] - > menuDest ] ) ; //Navigate to the next menu.
} else {
ERR ( " WARNING! Exceeded menu stack size limit! " )
}
}
}
}
void Menu : : Update ( Crawler * game ) {
if ( draggingComponent = = nullptr ) {
HoverMenuSelect ( game ) ;
}
if ( ! UsingMouseNavigation ( ) & & geom2d : : line < float > ( lastActiveMousePos , game - > GetMousePos ( ) ) . length ( ) > = " ThemeGlobal.MouseActivationDistance " _F | | UsingMouseNavigation ( ) ) {
SetMouseNavigation ( true ) ;
}
for ( auto & [ key , value ] : buttons ) {
for ( auto & button : value ) {
if ( ! button - > disabled ) {
button - > hovered = false ;
}
}
}
bool itemHovered = false ;
if ( ! UsingMouseNavigation ( ) ) {
if ( selection ! = vi2d { - 1 , - 1 } ) {
buttons [ selection . y ] [ selection . x ] - > hovered = true ;
itemHovered = true ;
}
} else {
selection = { - 1 , - 1 } ;
for ( auto & [ key , value ] : buttons ) {
int index = 0 ;
for ( auto & button : value ) {
if ( ! button - > disabled ) {
if ( button - > GetHoverState ( game ) ) {
button - > hovered = true ;
itemHovered = true ;
selection . y = key ;
selection . x = index ;
}
}
index + + ;
}
}
}
if ( itemHovered & & draggingComponent = = nullptr & & selection ! = vi2d { - 1 , - 1 } & & ( ( ( ! UsingMouseNavigation ( ) & & ( game - > GetKey ( ENTER ) . bHeld ) | | game - > GetKey ( SPACE ) . bHeld ) ) | | game - > GetMouse ( Mouse : : LEFT ) . bHeld ) ) {
buttonHoldTime + = game - > GetElapsedTime ( ) ;
} else {
buttonHoldTime = 0 ;
}
if ( draggingComponent ! = nullptr ) {
MenuComponent * selectedComponent = nullptr ;
if ( selection ! = vi2d { - 1 , - 1 } ) {
selectedComponent = buttons [ selection . y ] [ selection . x ] ;
}
auto ClearDraggingComponent = [ & ] ( ) {
delete draggingComponent ; //We know we allocated a new instance of this, so we will now free it.
draggingComponent = nullptr ;
} ;
if ( ! UsingMouseNavigation ( ) ) {
if ( game - > GetKey ( ENTER ) . bReleased | | game - > GetKey ( SPACE ) . bReleased ) {
if ( selectedComponent = = nullptr ) { //Dropping over an empty area.
ClearDraggingComponent ( ) ;
} else
if ( selectedComponent - > DropDraggableItem ( draggingComponent ) ) {
ClearDraggingComponent ( ) ;
}
}
} else {
if ( game - > GetMouse ( Mouse : : LEFT ) . bReleased ) {
if ( selectedComponent = = nullptr ) { //Dropping over an empty area.
ClearDraggingComponent ( ) ;
} else
if ( selectedComponent - > DropDraggableItem ( draggingComponent ) ) {
ClearDraggingComponent ( ) ;
}
}
}
}
KeyboardButtonNavigation ( game , pos ) ;
for ( auto & [ key , value ] : buttons ) {
for ( auto & button : value ) {
if ( button - > renderInMain ) {
button - > BeforeUpdate ( game ) ;
}
}
}
for ( auto & component : displayComponents ) {
if ( component - > renderInMain ) {
component - > BeforeUpdate ( game ) ;
}
}
for ( auto & [ key , value ] : buttons ) {
for ( auto & button : value ) {
if ( button - > renderInMain ) {
button - > _Update ( game ) ;
}
}
}
for ( auto & component : displayComponents ) {
if ( component - > renderInMain ) {
component - > _Update ( game ) ;
}
}
} ;
void Menu : : Draw ( Crawler * game ) {
if ( GetCurrentTheme ( ) . IsScaled ( ) ) {
DrawScaledWindowBackground ( game , pos , size , GetRenderColor ( ) ) ;
} else {
DrawTiledWindowBackground ( game , pos , size , GetRenderColor ( ) ) ;
}
game - > SetDrawTarget ( r . Sprite ( ) ) ;
Pixel : : Mode prevMode = game - > GetPixelMode ( ) ;
game - > SetPixelMode ( Pixel : : MASK ) ;
game - > Clear ( BLANK ) ;
std : : vector < MenuComponent * > allComponents ;
std : : copy ( displayComponents . begin ( ) , displayComponents . end ( ) , std : : back_inserter ( allComponents ) ) ;
std : : for_each ( buttons . begin ( ) , buttons . end ( ) , [ & ] ( auto & pair ) { std : : copy ( pair . second . begin ( ) , pair . second . end ( ) , std : : back_inserter ( allComponents ) ) ; } ) ;
std : : sort ( allComponents . begin ( ) , allComponents . end ( ) , [ ] ( MenuComponent * c1 , MenuComponent * c2 ) { return c1 - > depth > c2 - > depth ; } ) ;
for ( const auto & component : allComponents ) {
if ( component - > renderInMain ) {
component - > _Draw ( game ) ;
}
}
game - > SetPixelMode ( prevMode ) ;
game - > SetDrawTarget ( nullptr ) ;
if ( GetCurrentTheme ( ) . IsScaled ( ) ) {
DrawScaledWindowBorder ( game , pos , size , GetRenderColor ( ) ) ;
} else {
DrawTiledWindowBorder ( game , pos , size , GetRenderColor ( ) ) ;
}
r . Decal ( ) - > Update ( ) ;
game - > DrawDecal ( pos , r . Decal ( ) ) ;
for ( const auto & component : allComponents ) {
if ( component - > renderInMain ) {
component - > _DrawDecal ( game , this = = Menu : : stack . back ( ) ) ;
}
}
if ( draggingComponent ! = nullptr ) {
game - > SetDrawTarget ( overlay . Sprite ( ) ) ;
Pixel : : Mode prevMode = game - > GetPixelMode ( ) ;
game - > SetPixelMode ( Pixel : : MASK ) ;
game - > Clear ( BLANK ) ;
vf2d offsetPos = draggingComponent - > rect . pos ;
if ( ! UsingMouseNavigation ( ) ) {
MenuComponent * selectedComponent = buttons [ selection . y ] [ selection . x ] ;
vf2d drawOffset { } ;
if ( selectedComponent - > parentComponent ! = nullptr ) {
drawOffset + = selectedComponent - > parentComponent - > V ( A : : SCROLL_OFFSET ) ;
}
draggingComponent - > Draw ( game , drawOffset + pos - offsetPos + selectedComponent - > rect . pos + vi2d { 1 , - 4 } ) ;
} else {
draggingComponent - > Draw ( game , - offsetPos + game - > GetMousePos ( ) ) ;
}
game - > SetPixelMode ( prevMode ) ;
game - > SetDrawTarget ( nullptr ) ;
overlay . Decal ( ) - > Update ( ) ;
game - > DrawDecal ( { 0 , 0 } , overlay . Decal ( ) , { 1 , 1 } , this = = Menu : : stack . back ( ) ? WHITE : WHITE * " ThemeGlobal.MenuUnfocusedColorMult " _F ) ;
if ( ! UsingMouseNavigation ( ) ) {
MenuComponent * selectedComponent = buttons [ selection . y ] [ selection . x ] ;
vf2d drawOffset { } ;
if ( selectedComponent - > parentComponent ! = nullptr ) {
drawOffset + = selectedComponent - > parentComponent - > V ( A : : SCROLL_OFFSET ) ;
}
draggingComponent - > DrawDecal ( game , drawOffset + pos - offsetPos + selectedComponent - > rect . pos + vi2d { 1 , - 4 } , this = = Menu : : stack . back ( ) ) ;
} else {
draggingComponent - > DrawDecal ( game , - offsetPos + game - > GetMousePos ( ) , this = = Menu : : stack . back ( ) ) ;
}
}
} ;
void Menu : : OpenMenu ( MenuType menu , bool cover ) {
menus [ menu ] - > cover = cover ;
stack . push_back ( menus [ menu ] ) ;
}
void Menu : : KeyboardButtonNavigation ( Crawler * game , vf2d menuPos ) {
vi2d prevSelection = selection ;
if ( game - > GetKey ( RIGHT ) . bPressed ) {
if ( selection = = vi2d { - 1 , - 1 } ) return ;
SetMouseNavigation ( false ) ;
selection . x = ( size_t ( selection . x ) + 1 ) % keyboardButtons [ selection . y ] . size ( ) ;
}
if ( game - > GetKey ( LEFT ) . bPressed ) {
if ( selection = = vi2d { - 1 , - 1 } ) return ;
selection . x - - ;
SetMouseNavigation ( false ) ;
if ( selection . x < 0 ) selection . x + = int32_t ( keyboardButtons [ selection . y ] . size ( ) ) ;
}
if ( game - > GetKey ( DOWN ) . bPressed | | game - > GetKey ( UP ) . bPressed ) {
if ( game - > GetKey ( DOWN ) . bPressed ) {
SetMouseNavigation ( false ) ;
bool found = false ;
bool selectedItem = false ;
if ( selection = = vi2d { - 1 , - 1 } ) {
//Highlight first item.
for ( auto & [ key , value ] : keyboardButtons ) {
selection . y = key ;
break ;
}
} else {
for ( auto & [ key , value ] : keyboardButtons ) {
if ( found ) { //Once we discover the previous element, the next element becomes our next selection.
int previousButtonX = int ( keyboardButtons [ selection . y ] [ selection . x ] - > rect . pos . x ) ;
selection . y = key ;
int index = 0 ;
for ( auto & button : value ) { //Try to match a button in the same column as this button first.
if ( previousButtonX = = button - > rect . pos . x ) {
selection . x = index ;
break ;
}
index + + ;
}
selectedItem = true ;
break ;
}
if ( key = = selection . y
//It's entirely possible this button was selected from the button selection list and may be out-of-bounds here.
& & selection . x > = 0 & & selection . x < keyboardButtons [ selection . y ] . size ( ) ) {
found = true ;
}
}
if ( ! selectedItem ) { //This means we need to loop around instead and pick the first one.
for ( auto & [ key , value ] : keyboardButtons ) {
selection . y = key ;
break ;
}
}
}
}
if ( game - > GetKey ( UP ) . bPressed ) {
SetMouseNavigation ( false ) ;
if ( selection = = vi2d { - 1 , - 1 } ) {
//Highlight last item.
for ( auto & [ key , value ] : keyboardButtons ) {
selection . y = key ;
}
} else {
int prevInd = - 1 ;
for ( auto & [ key , value ] : keyboardButtons ) {
if ( key = = selection . y & &
//It's entirely possible this button was selected from the button selection list and may be out-of-bounds here.
selection . x > = 0 & & selection . x < keyboardButtons [ selection . y ] . size ( ) ) {
break ;
}
prevInd = key ;
}
if ( prevInd ! = - 1 ) {
int previousButtonX = int ( keyboardButtons [ selection . y ] [ selection . x ] - > rect . pos . x ) ;
selection . y = prevInd ;
int index = 0 ;
for ( auto & button : keyboardButtons [ prevInd ] ) { //Try to match a button in the same column as this button first.
if ( previousButtonX = = button - > rect . pos . x ) {
selection . x = index ;
break ;
}
index + + ;
}
} else { //Since we didn't find it, it means we're at the top of the list or the list is empty. Go to the last element and use that one.
int lastInd = - 1 ;
for ( auto & [ key , value ] : keyboardButtons ) {
lastInd = key ;
}
selection . y = lastInd ;
}
}
}
//In both cases, we should clamp the X index to make sure it's still valid.
if ( selection . y ! = - 1 ) {
selection . x = std : : clamp ( selection . x , 0 , int ( keyboardButtons [ selection . y ] . size ( ) ) - 1 ) ;
} else {
selection . x = - 1 ;
}
}
if ( game - > GetMouse ( 0 ) . bPressed | | game - > GetKey ( ENTER ) . bPressed | | game - > GetKey ( SPACE ) . bPressed ) {
SetMouseNavigation ( game - > GetMouse ( 0 ) . bPressed ) ; //If a click occurs we use mouse controls.
if ( ! UsingMouseNavigation ( ) ) {
buttonHoldTime = 0 ;
//Key presses automatically highlight the first button if it's not highlighted.
if ( selection = = vi2d { - 1 , - 1 } & & buttons . size ( ) > 0 ) {
//Find the first possible button entry in the map...
int firstInd = - 1 ;
for ( auto & [ key , value ] : buttons ) {
if ( buttons [ key ] . size ( ) > 0 ) {
firstInd = key ;
break ;
}
}
if ( firstInd ! = - 1 ) { //This means we found a valid menu item. If we didn't find one don't highlight any menu item...
selection = { 0 , firstInd } ;
}
}
} else { //Mouse click.
selection = { - 1 , - 1 } ;
for ( auto & [ key , value ] : buttons ) {
int index = 0 ;
for ( auto & button : value ) {
if ( ! button - > disabled ) {
if ( geom2d : : overlaps ( geom2d : : rect < float > { button - > rect . pos + menuPos , button - > rect . size } , game - > GetMousePos ( ) ) ) {
selection = { index , key } ;
break ;
}
}
index + + ;
}
}
buttonHoldTime = 0 ;
}
}
if ( prevSelection ! = selection ) {
if ( selection ! = vi2d { - 1 , - 1 } & & buttons [ selection . y ] [ selection . x ] - > disabled ) {
bool handled = false ;
if ( ! UsingMouseNavigation ( ) ) {
//Let's transfer some information about our selection being off the screen. Our intention with keyboard controls is that the screen will scroll to the correct location instead.
//If we return false, then we handled it ourselves, no need to go back to the previous selection.
if ( HandleOutsideDisabledButtonSelection ( buttons [ selection . y ] [ selection . x ] ) ) {
handled = true ;
}
}
if ( ! handled ) {
// If the new selection of a button on this frame is disabled for some reason and we didn't handle it, we need to go back to what we had selected before.
selection = prevSelection ;
}
}
}
}
void Menu : : DrawScaledWindowBorder ( Crawler * game , vf2d menuPos , vf2d size , Pixel renderColor ) {
vf2d patchSize = { " Interface.9PatchSize " _f [ 0 ] , " Interface.9PatchSize " _f [ 1 ] } ;
//Upper-Left
game - > DrawPartialDecal ( menuPos - patchSize , patchSize , GetPatchPart ( 0 , 0 ) . Decal ( ) , { patchSize . x * 0 , patchSize . y * 0 } , patchSize , renderColor ) ;
//Upper-Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , - patchSize . y } , patchSize , GetPatchPart ( 2 , 0 ) . Decal ( ) , { patchSize . x * 2 , patchSize . y * 0 } , patchSize , renderColor ) ;
//Bottom-Left
game - > DrawPartialDecal ( menuPos + vf2d { - patchSize . x , size . y } , patchSize , GetPatchPart ( 0 , 2 ) . Decal ( ) , { patchSize . x * 0 , patchSize . y * 2 } , patchSize , renderColor ) ;
//Bottom-Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , size . y } , patchSize , GetPatchPart ( 2 , 2 ) . Decal ( ) , { patchSize . x * 2 , patchSize . y * 2 } , patchSize , renderColor ) ;
//Top
game - > DrawPartialDecal ( menuPos + vf2d { 0 , - patchSize . y } , vf2d { size . x , patchSize . y } , GetPatchPart ( 1 , 0 ) . Decal ( ) , { patchSize . x * 1 , patchSize . y * 0 } , patchSize , renderColor ) ;
//Left
game - > DrawPartialDecal ( menuPos + vf2d { - patchSize . x , 0 } , vf2d { patchSize . x , size . y } , GetPatchPart ( 0 , 1 ) . Decal ( ) , { patchSize . x * 0 , patchSize . y * 1 } , patchSize , renderColor ) ;
//Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , 0 } , vf2d { patchSize . x , size . y } , GetPatchPart ( 2 , 1 ) . Decal ( ) , { patchSize . x * 2 , patchSize . y * 1 } , patchSize , renderColor ) ;
//Bottom
game - > DrawPartialDecal ( menuPos + vf2d { 0 , size . y } , vf2d { size . x , patchSize . y } , GetPatchPart ( 1 , 2 ) . Decal ( ) , { patchSize . x * 1 , patchSize . y * 2 } , patchSize , renderColor ) ;
}
void Menu : : DrawTiledWindowBorder ( Crawler * game , vf2d menuPos , vf2d size , Pixel renderColor ) {
vf2d patchSize = { " Interface.9PatchSize " _f [ 0 ] , " Interface.9PatchSize " _f [ 1 ] } ;
//Upper-Left
game - > DrawPartialDecal ( menuPos - patchSize , patchSize , GetPatchPart ( 0 , 0 ) . Decal ( ) , { 0 , 0 } , patchSize , renderColor ) ;
//Upper-Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , - patchSize . y } , patchSize , GetPatchPart ( 2 , 0 ) . Decal ( ) , { 0 , 0 } , patchSize , renderColor ) ;
//Bottom-Left
game - > DrawPartialDecal ( menuPos + vf2d { - patchSize . x , size . y } , patchSize , GetPatchPart ( 0 , 2 ) . Decal ( ) , { 0 , 0 } , patchSize , renderColor ) ;
//Bottom-Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , size . y } , patchSize , GetPatchPart ( 2 , 2 ) . Decal ( ) , { 0 , 0 } , patchSize , renderColor ) ;
//Top
game - > DrawPartialDecal ( menuPos + vf2d { 0 , - patchSize . y } , vf2d { size . x , patchSize . y } , GetPatchPart ( 1 , 0 ) . Decal ( ) , { 0 , 0 } , vf2d { size . x , patchSize . y } , renderColor ) ;
//Left
game - > DrawPartialDecal ( menuPos + vf2d { - patchSize . x , 0 } , vf2d { patchSize . x , size . y } , GetPatchPart ( 0 , 1 ) . Decal ( ) , { 0 , 0 } , vf2d { patchSize . x , size . y } , renderColor ) ;
//Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , 0 } , vf2d { patchSize . x , size . y } , GetPatchPart ( 2 , 1 ) . Decal ( ) , { 0 , 0 } , vf2d { patchSize . x , size . y } , renderColor ) ;
//Bottom
game - > DrawPartialDecal ( menuPos + vf2d { 0 , size . y } , vf2d { size . x , patchSize . y } , GetPatchPart ( 1 , 2 ) . Decal ( ) , { 0 , 0 } , vf2d { size . x , patchSize . y } , renderColor ) ;
}
void Menu : : DrawScaledWindowBackground ( Crawler * game , vf2d menuPos , vf2d size , Pixel renderColor ) {
vf2d patchSize = { " Interface.9PatchSize " _f [ 0 ] , " Interface.9PatchSize " _f [ 1 ] } ;
//Center
if ( GetCurrentTheme ( ) . HasBackground ( ) ) {
Decal * back = GetCurrentTheme ( ) . GetBackground ( ) ;
game - > DrawPartialDecal ( menuPos , size , back , { 0 , 0 } , back - > sprite - > Size ( ) , renderColor ) ;
} else {
game - > DrawPartialDecal ( menuPos , size , GetPatchPart ( 1 , 1 ) . Decal ( ) , { patchSize . x * 1 , patchSize . y * 1 } , patchSize , renderColor ) ;
}
}
void Menu : : DrawTiledWindowBackground ( Crawler * game , vf2d menuPos , vf2d size , Pixel renderColor ) {
vf2d patchSize = { " Interface.9PatchSize " _f [ 0 ] , " Interface.9PatchSize " _f [ 1 ] } ;
//Center
if ( GetCurrentTheme ( ) . HasBackground ( ) ) {
Decal * back = GetCurrentTheme ( ) . GetBackground ( ) ;
game - > DrawPartialDecal ( menuPos , size , back , { 0 , 0 } , size , renderColor ) ;
} else {
game - > DrawPartialDecal ( menuPos , size , GetPatchPart ( 1 , 1 ) . Decal ( ) , { 0 , 0 } , patchSize , renderColor ) ;
}
}
Renderable & Menu : : GetPatchPart ( int x , int y ) {
return GFX [ themeSelection + " _ " + std : : to_string ( x ) + std : : to_string ( y ) + " .png " ] ;
}
Theme & Menu : : GetCurrentTheme ( ) {
return themes [ themeSelection ] ;
}
Pixel Menu : : GetRenderColor ( ) {
bool focused = Menu : : stack . back ( ) = = this ;
Pixel col = WHITE ;
if ( ! focused ) {
col = WHITE * " ThemeGlobal.MenuUnfocusedColorMult " _F ;
}
return col ;
}
bool Menu : : HandleOutsideDisabledButtonSelection ( MenuComponent * disabledButton ) {
if ( disabledButton - > parentComponent ! = nullptr ) {
return disabledButton - > parentComponent - > HandleOutsideDisabledButtonSelection ( disabledButton ) ;
} else {
return false ;
}
}
bool Menu : : UsingMouseNavigation ( ) {
return MOUSE_NAVIGATION ;
} ;
void Menu : : SetMouseNavigation ( bool mouseNavigation ) {
if ( MOUSE_NAVIGATION & & ! mouseNavigation ) {
//When mouse navigation was enabled and now needs to be disabled, we store the mouse position.
INCLUDE_game
lastActiveMousePos = game - > GetMousePos ( ) ;
}
MOUSE_NAVIGATION = mouseNavigation ;
} ;
void Menu : : InventorySlotsUpdated ( ITCategory cat ) {
//Update the inventory with a new inventory slot, since there's one additional item to interact with now.
for ( MenuComponent * component : inventoryListeners . at ( cat ) ) {
component - > OnInventorySlotsUpdate ( cat ) ;
}
}
void Menu : : AddInventoryListener ( MenuComponent * component , ITCategory category ) {
if ( inventoryListeners . count ( category ) ) {
std : : vector < MenuComponent * > & listenerList = inventoryListeners . at ( category ) ;
if ( std : : find ( listenerList . begin ( ) , listenerList . end ( ) , component ) ! = listenerList . end ( ) ) {
ERR ( " WARNING! Component " < < component - > name < < " has already been added to the " < < category < < " listener list! There should not be any duplicates!! " )
}
listenerList . push_back ( component ) ;
} else {
ERR ( " WARNING! Inventory category " < < category < < " does not exist! " )
}
}
void Menu : : AddEquipStatListener ( MenuComponent * component ) {
if ( std : : find ( equipStatListeners . begin ( ) , equipStatListeners . end ( ) , component ) ! = equipStatListeners . end ( ) ) {
ERR ( " WARNING! Component " < < component - > name < < " has already been added to the Equip Stat listener list! There should not be any duplicates!! " )
}
equipStatListeners . push_back ( component ) ;
}
vf2d Menu : : center ( ) {
return size / 2 ;
}
void Menu : : CloseMenu ( ) {
if ( stack . size ( ) > 0 ) {
stack . pop_back ( ) ;
} else {
ERR ( " WARNING! Trying to close out no menu?? Why are we doing this? " )
}
}
std : : pair < MenuType , std : : string > Menu : : GetMemoryLeakReportInfo ( ) {
return { lastMenuTypeCreated , lastRegisteredComponent } ;
}
void Menu : : CloseAllMenus ( ) {
if ( stack . size ( ) > 0 ) {
stack . clear ( ) ;
} else {
ERR ( " WARNING! Trying to close out no menu?? Why are we doing this? " )
}
}
bool Menu : : IsMenuOpen ( ) {
return stack . size ( ) > 0 ;
}
void Menu : : CleanupAllMenus ( ) {
for ( auto & [ key , value ] : Menu : : menus ) {
Menu * menu = value ;
for ( auto & componentKey : menu - > components ) {
MenuComponent * component = componentKey . second ;
component - > Cleanup ( ) ;
delete component ;
}
menu - > components . Reset ( ) ;
menu - > Cleanup ( ) ;
delete menu ;
}
Menu : : menus . clear ( ) ;
}
void Menu : : Cleanup ( ) { }
void Menu : : DrawThemedWindow ( vf2d menuPos , vf2d size , Pixel renderColor ) {
if ( GetCurrentTheme ( ) . IsScaled ( ) ) {
DrawScaledWindowBackground ( game , menuPos , size , renderColor ) ;
DrawScaledWindowBorder ( game , menuPos , size , renderColor ) ;
} else {
DrawTiledWindowBackground ( game , menuPos , size , renderColor ) ;
DrawTiledWindowBorder ( game , menuPos , size , renderColor ) ;
}
}
void Menu : : RecalculateComponentCount ( ) {
componentCount = displayComponents . size ( ) + buttons . size ( ) ;
}