# include "Crawler.h"
# include "MenuComponent.h"
# include "DEFINES.h"
# include "safemap.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 ;
const vf2d Menu : : CENTERED = { - 456 , - 456 } ;
INCLUDE_GFX
extern vi2d WINDOW_SIZE ;
typedef Attribute A ;
Menu : : Menu ( vf2d pos , vf2d size )
: pos ( pos = = CENTERED ? WINDOW_SIZE / 2 - size / 2 : vi2d { pos } ) , size ( size ) {
r . Create ( size . x , size . y ) ;
overlay . Create ( WINDOW_SIZE . x , WINDOW_SIZE . y ) ;
}
void Menu : : InitializeMenus ( ) {
stack . reserve ( 32 ) ;
InitializeTestMenu ( ) ;
InitializeTestSubMenu ( ) ;
InitializeInventoryWindow ( ) ;
for ( MenuType type = TEST ; type < MenuType : : ENUM_END ; type = MenuType ( int ( type + 1 ) ) ) {
if ( menus . count ( type ) = = 0 ) {
std : : cout < < " WARNING! Menu Type " < < type < < " does not exist! " < < std : : endl ;
throw ;
}
for ( auto & key : menus [ type ] - > components ) {
MenuComponent * component = key . second ;
component - > AfterCreate ( ) ;
}
menus [ type ] - > components . SetInitialized ( ) ; //Lock all known components to prevent invalid access.
}
}
Menu * Menu : : CreateMenu ( MenuType type , vf2d pos , vf2d size ) {
menus [ type ] = new Menu ( pos , size ) ;
return menus . at ( type ) ;
}
void Menu : : AddComponent ( std : : string key , MenuComponent * button ) {
if ( button - > selectable ) {
buttons [ button - > rect . pos . y ] . push_back ( button ) ;
if ( button - > selectableViaKeyboard ) {
keyboardButtons [ button - > rect . pos . y ] . push_back ( button ) ;
}
} else {
displayComponents . push_back ( button ) ;
}
if ( components . count ( key ) ) {
std : : cout < < " WARNING! Key " < < key < < " for this sub-menu already exists! Key names must be unique! " < < std : : endl ;
throw ;
}
button - > name = key ;
components [ key ] = button ;
}
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 ;
buttons [ selection . y ] [ selection . x ] - > onClick ( MenuFuncData { * this , game , buttons [ selection . y ] [ selection . x ] } ) ;
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 {
std : : cout < < " WARNING! Exceeded menu stack size limit! " < < std : : endl ;
throw ;
}
}
}
void Menu : : Update ( Crawler * game ) {
if ( draggingComponent = = nullptr ) {
HoverMenuSelect ( game ) ;
}
for ( auto & key : buttons ) {
for ( auto & button : key . second ) {
if ( ! button - > disabled ) {
button - > hovered = false ;
}
}
}
bool itemHovered = false ;
if ( ! MOUSE_NAVIGATION ) {
if ( selection ! = vi2d { - 1 , - 1 } ) {
buttons [ selection . y ] [ selection . x ] - > hovered = true ;
itemHovered = true ;
}
} else {
selection = { - 1 , - 1 } ;
for ( auto & key : buttons ) {
int index = 0 ;
for ( auto & button : key . second ) {
if ( ! button - > disabled ) {
if ( button - > GetHoverState ( game ) ) {
button - > hovered = true ;
itemHovered = true ;
selection . y = key . first ;
selection . x = index ;
}
}
index + + ;
}
}
}
if ( itemHovered & & draggingComponent = = nullptr & & selection ! = vi2d { - 1 , - 1 } & & ( ( ( ! MOUSE_NAVIGATION & & ( 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 ( ! MOUSE_NAVIGATION ) {
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 : buttons ) {
for ( auto & button : key . second ) {
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 ) ;
} else {
DrawTiledWindowBackground ( game , pos ) ;
}
game - > SetDrawTarget ( r . Sprite ( ) ) ;
Pixel : : Mode prevMode = game - > GetPixelMode ( ) ;
game - > SetPixelMode ( Pixel : : MASK ) ;
game - > Clear ( BLANK ) ;
for ( auto & component : displayComponents ) {
if ( component - > renderInMain ) {
component - > _Draw ( game , { 0 , 0 } , this = = Menu : : stack . back ( ) ) ;
}
}
for ( auto & key : buttons ) {
for ( auto & button : key . second ) {
if ( button - > renderInMain ) {
button - > _Draw ( game , { 0 , 0 } , this = = Menu : : stack . back ( ) ) ;
}
}
}
game - > SetPixelMode ( prevMode ) ;
game - > SetDrawTarget ( nullptr ) ;
r . Decal ( ) - > Update ( ) ;
game - > DrawDecal ( pos , r . Decal ( ) ) ;
for ( auto & component : displayComponents ) {
if ( component - > renderInMain ) {
component - > _DrawDecal ( game , pos , this = = Menu : : stack . back ( ) ) ;
}
}
for ( auto & key : buttons ) {
for ( auto & button : key . second ) {
if ( button - > renderInMain ) {
button - > _DrawDecal ( game , pos , this = = Menu : : stack . back ( ) ) ;
}
}
}
if ( GetCurrentTheme ( ) . IsScaled ( ) ) {
DrawScaledWindowBorder ( game , pos ) ;
} else {
DrawTiledWindowBorder ( game , pos ) ;
}
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 ( ! MOUSE_NAVIGATION ) {
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 } , this = = Menu : : stack . back ( ) ) ;
} else {
draggingComponent - > Draw ( game , - offsetPos + game - > GetMousePos ( ) , this = = Menu : : stack . back ( ) ) ;
}
game - > SetPixelMode ( prevMode ) ;
game - > SetDrawTarget ( nullptr ) ;
overlay . Decal ( ) - > Update ( ) ;
game - > DrawDecal ( { 0 , 0 } , overlay . Decal ( ) ) ;
if ( ! MOUSE_NAVIGATION ) {
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 ) {
stack . clear ( ) ;
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 ;
MOUSE_NAVIGATION = 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 - - ;
if ( selection . x < 0 ) selection . x + = keyboardButtons [ selection . y ] . size ( ) ;
}
if ( game - > GetKey ( DOWN ) . bPressed | | game - > GetKey ( UP ) . bPressed ) {
if ( game - > GetKey ( DOWN ) . bPressed ) {
MOUSE_NAVIGATION = false ;
bool found = false ;
bool selectedItem = false ;
if ( selection = = vi2d { - 1 , - 1 } ) {
//Highlight first item.
for ( auto & key : keyboardButtons ) {
selection . y = key . first ;
break ;
}
} else {
for ( auto & key : keyboardButtons ) {
if ( found ) { //Once we discover the previous element, the next element becomes our next selection.
int previousButtonX = keyboardButtons [ selection . y ] [ selection . x ] - > rect . pos . x ;
selection . y = key . first ;
int index = 0 ;
for ( auto & button : key . second ) { //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 . first = = selection . y ) {
found = true ;
}
}
if ( ! selectedItem ) { //This means we need to loop around instead and pick the first one.
for ( auto & key : keyboardButtons ) {
selection . y = key . first ;
break ;
}
}
}
}
if ( game - > GetKey ( UP ) . bPressed ) {
MOUSE_NAVIGATION = false ;
if ( selection = = vi2d { - 1 , - 1 } ) {
//Highlight last item.
for ( auto & key : keyboardButtons ) {
selection . y = key . first ;
}
} else {
int prevInd = - 1 ;
for ( auto & key : keyboardButtons ) {
if ( key . first = = selection . y ) {
break ;
}
prevInd = key . first ;
}
if ( prevInd ! = - 1 ) {
int previousButtonX = 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 : keyboardButtons ) {
lastInd = key . first ;
}
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 ) {
MOUSE_NAVIGATION = game - > GetMouse ( 0 ) . bPressed ; //If a click occurs we use mouse controls.
if ( ! MOUSE_NAVIGATION ) {
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 : buttons ) {
if ( buttons [ key . first ] . size ( ) > 0 ) {
firstInd = key . first ;
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 : buttons ) {
int index = 0 ;
for ( auto & button : key . second ) {
if ( ! button - > disabled ) {
if ( geom2d : : overlaps ( geom2d : : rect < float > { button - > rect . pos + menuPos , button - > rect . size } , game - > GetMousePos ( ) ) ) {
selection = { index , key . first } ;
break ;
}
}
index + + ;
}
}
buttonHoldTime = 0 ;
}
}
if ( prevSelection ! = selection ) {
if ( selection ! = vi2d { - 1 , - 1 } & & buttons [ selection . y ] [ selection . x ] - > disabled ) {
bool handled = false ;
if ( ! MOUSE_NAVIGATION ) {
//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 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 , GetRenderColor ( ) ) ;
//Upper-Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , - patchSize . y } , patchSize , GetPatchPart ( 2 , 0 ) . Decal ( ) , { patchSize . x * 2 , patchSize . y * 0 } , patchSize , GetRenderColor ( ) ) ;
//Bottom-Left
game - > DrawPartialDecal ( menuPos + vf2d { - patchSize . x , size . y } , patchSize , GetPatchPart ( 0 , 2 ) . Decal ( ) , { patchSize . x * 0 , patchSize . y * 2 } , patchSize , GetRenderColor ( ) ) ;
//Bottom-Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , size . y } , patchSize , GetPatchPart ( 2 , 2 ) . Decal ( ) , { patchSize . x * 2 , patchSize . y * 2 } , patchSize , GetRenderColor ( ) ) ;
//Top
game - > DrawPartialDecal ( menuPos + vf2d { 0 , - patchSize . y } , vf2d { size . x , patchSize . y } , GetPatchPart ( 1 , 0 ) . Decal ( ) , { patchSize . x * 1 , patchSize . y * 0 } , patchSize , GetRenderColor ( ) ) ;
//Left
game - > DrawPartialDecal ( menuPos + vf2d { - patchSize . x , 0 } , vf2d { patchSize . x , size . y } , GetPatchPart ( 0 , 1 ) . Decal ( ) , { patchSize . x * 0 , patchSize . y * 1 } , patchSize , GetRenderColor ( ) ) ;
//Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , 0 } , vf2d { patchSize . x , size . y } , GetPatchPart ( 2 , 1 ) . Decal ( ) , { patchSize . x * 2 , patchSize . y * 1 } , patchSize , GetRenderColor ( ) ) ;
//Bottom
game - > DrawPartialDecal ( menuPos + vf2d { 0 , size . y } , vf2d { size . x , patchSize . y } , GetPatchPart ( 1 , 2 ) . Decal ( ) , { patchSize . x * 1 , patchSize . y * 2 } , patchSize , GetRenderColor ( ) ) ;
}
void Menu : : DrawTiledWindowBorder ( Crawler * game , vf2d menuPos ) {
vf2d patchSize = { " Interface.9PatchSize " _f [ 0 ] , " Interface.9PatchSize " _f [ 1 ] } ;
//Upper-Left
game - > DrawPartialDecal ( menuPos - patchSize , patchSize , GetPatchPart ( 0 , 0 ) . Decal ( ) , { 0 , 0 } , patchSize , GetRenderColor ( ) ) ;
//Upper-Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , - patchSize . y } , patchSize , GetPatchPart ( 2 , 0 ) . Decal ( ) , { 0 , 0 } , patchSize , GetRenderColor ( ) ) ;
//Bottom-Left
game - > DrawPartialDecal ( menuPos + vf2d { - patchSize . x , size . y } , patchSize , GetPatchPart ( 0 , 2 ) . Decal ( ) , { 0 , 0 } , patchSize , GetRenderColor ( ) ) ;
//Bottom-Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , size . y } , patchSize , GetPatchPart ( 2 , 2 ) . Decal ( ) , { 0 , 0 } , patchSize , GetRenderColor ( ) ) ;
//Top
game - > DrawPartialDecal ( menuPos + vf2d { 0 , - patchSize . y } , vf2d { size . x , patchSize . y } , GetPatchPart ( 1 , 0 ) . Decal ( ) , { 0 , 0 } , vf2d { size . x , patchSize . y } , GetRenderColor ( ) ) ;
//Left
game - > DrawPartialDecal ( menuPos + vf2d { - patchSize . x , 0 } , vf2d { patchSize . x , size . y } , GetPatchPart ( 0 , 1 ) . Decal ( ) , { 0 , 0 } , vf2d { patchSize . x , size . y } , GetRenderColor ( ) ) ;
//Right
game - > DrawPartialDecal ( menuPos + vf2d { size . x , 0 } , vf2d { patchSize . x , size . y } , GetPatchPart ( 2 , 1 ) . Decal ( ) , { 0 , 0 } , vf2d { patchSize . x , size . y } , GetRenderColor ( ) ) ;
//Bottom
game - > DrawPartialDecal ( menuPos + vf2d { 0 , size . y } , vf2d { size . x , patchSize . y } , GetPatchPart ( 1 , 2 ) . Decal ( ) , { 0 , 0 } , vf2d { size . x , patchSize . y } , GetRenderColor ( ) ) ;
}
void Menu : : DrawScaledWindowBackground ( Crawler * game , vf2d menuPos ) {
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 ( ) , GetRenderColor ( ) ) ;
} else {
game - > DrawPartialDecal ( menuPos , size , GetPatchPart ( 1 , 1 ) . Decal ( ) , { patchSize . x * 1 , patchSize . y * 1 } , patchSize , GetRenderColor ( ) ) ;
}
}
void Menu : : DrawTiledWindowBackground ( Crawler * game , vf2d menuPos ) {
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 , GetRenderColor ( ) ) ;
} else {
game - > DrawPartialDecal ( menuPos , size , GetPatchPart ( 1 , 1 ) . Decal ( ) , { 0 , 0 } , patchSize , GetRenderColor ( ) ) ;
}
}
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 ;
}
}