# 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 © 2024 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# include "AdventuresInLestoria.h"
# include "safemap.h"
# include "Item.h"
# include "MenuItemButton.h"
# include "MenuLabel.h"
# include "InventoryScrollableWindowComponent.h"
using A = Attribute ;
void Menu : : InitializeConsumableInventoryWindow ( ) {
int itemSpacing = 24 ;
int totalSpacing = 32 ;
vf2d windowSize = { float ( 160 + 16 ) , float ( 96 + 78 ) } ; //Need space for the button.
Menu * inventoryWindow = CreateMenu ( INVENTORY_CONSUMABLES , CENTERED , windowSize ) ;
inventoryWindow - > I ( A : : LOADOUT_SLOT ) = 0 ;
auto consumableWindow = inventoryWindow - > ADD ( " inventory " , InventoryScrollableWindowComponent ) ( geom2d : : rect < float > { { 0 , 15 } , { windowSize . x , 96.f } } , " itemName " , " itemDescription " ,
[ & ] ( MenuFuncData data ) {
std : : weak_ptr < MenuItemButton > button = DYNAMIC_POINTER_CAST < MenuItemButton > ( data . component . lock ( ) ) ;
data . game - > ClearLoadoutItem ( data . menu . I ( A : : LOADOUT_SLOT ) ) ;
for ( std : : weak_ptr < MenuComponent > component : data . parentComponent . lock ( ) - > GetComponents ( ) ) { //HACK ALERT! If we are accessing a parent component, it's because we are dealing with a scrolling window component, which has sub-components. So this should be a safe cast to make.
if ( component . lock ( ) - > GetName ( ) . starts_with ( " item " ) ) {
std : : weak_ptr < MenuItemButton > button2 = DYNAMIC_POINTER_CAST < MenuItemButton > ( component . lock ( ) ) ; //HACK ALERT! This is probably an item since we populated item lists using this name for the components. So we assume these are MenuItemButton classes.
if ( button2 . expired ( ) ) ERR ( " Could not cast item to a MenuItemButton*! " ) ;
if ( & * button2 . lock ( ) = = & * button . lock ( ) ) {
if ( button2 . lock ( ) - > selected ! = - 1 ) {
data . game - > ClearLoadoutItem ( button2 . lock ( ) - > selected ) ;
}
button2 . lock ( ) - > selected = - 1 ;
}
if ( button2 . lock ( ) - > selected = = data . menu . I ( A : : LOADOUT_SLOT ) ) {
button2 . lock ( ) - > selected = - 1 ;
}
}
}
button . lock ( ) - > selected = data . menu . I ( A : : LOADOUT_SLOT ) ;
data . game - > SetLoadoutItem ( button . lock ( ) - > selected , button . lock ( ) - > GetItem ( ) . lock ( ) - > ActualName ( ) ) ;
return true ;
} , InventoryCreator : : Player_InventoryUpdate ) END ;
Menu : : AddInventoryListener ( consumableWindow , " Consumables " ) ;
//We don't have to actually populate the inventory list because now when an item gets added, it will automatically add the correct component in for us.
inventoryWindow - > ADD ( " Inventory Type Label " , MenuLabel ) ( geom2d : : rect < float > { { 0 , - 5 } , { windowSize . x - 1 , 18 } } , " Consumables " , 2 , ComponentAttr : : SHADOW | ComponentAttr : : BACKGROUND | ComponentAttr : : OUTLINE ) END ;
inventoryWindow - > ADD ( " itemName " , MenuLabel ) ( geom2d : : rect < float > ( vf2d { 2 , 90.f } , { windowSize . x - 4 , windowSize . y - 108 } ) , " " , 1 , ComponentAttr : : SHADOW ) END ;
inventoryWindow - > ADD ( " itemDescription " , MenuLabel ) ( geom2d : : rect < float > ( vf2d { 2 , 117.f } , { windowSize . x - 4 , windowSize . y - 108 } ) , " " , 1 , ComponentAttr : : SHADOW ) END ;
auto okButton = inventoryWindow - > ADD ( " OK Button " , MenuComponent ) ( geom2d : : rect < float > { { windowSize . x / 2 - 24 , 173.f } , { 48 , 12 } } , " Ok " , [ ] ( MenuFuncData data ) { Menu : : CloseMenu ( ) ; return true ; } ) END ;
# pragma region ScrollWindow macro lambda
# define ScrollWindow(amount) \
[ ] ( MenuType type ) { \
auto scrollWindow = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) ; \
scrollWindow - > Scroll ( amount ) ; \
int invWidth = int ( ( scrollWindow - > rect . size . x - 12 ) / ( float ( scrollWindow - > options . size . x ) + scrollWindow - > options . padding ) ) ; /*The inventory width determines how many items to skip at a time.*/ \
scrollWindow - > SetSelectionSkipIncrement ( invWidth ) ; \
float scrollAmt = amount * game - > GetElapsedTime ( ) * " Interface.AnalogScrollSpeed " _F ; \
scrollWindow - > IncreaseSelectionIndex ( scrollAmt / 24.f ) ; \
}
# pragma endregion
inventoryWindow - > SetupKeyboardNavigation (
[ ] ( MenuType type , Data & returnData ) { //On Open
//Try to find the component that matches the loadout item we have on us.
std : : vector < std : : weak_ptr < MenuComponent > > & components = Component < InventoryScrollableWindowComponent > ( INVENTORY_CONSUMABLES , " inventory " ) - > GetComponents ( ) ;
auto inventory = Component < InventoryScrollableWindowComponent > ( INVENTORY_CONSUMABLES , " inventory " ) ;
auto foundComponent = std : : find_if ( components . begin ( ) , components . end ( ) , [ & ] ( auto & component ) {
if ( ! ISBLANK ( DYNAMIC_POINTER_CAST < MenuItemButton > ( component ) - > GetItem ( ) . lock ( ) ) & & DYNAMIC_POINTER_CAST < MenuItemButton > ( component ) - > GetItem ( ) . lock ( ) - > ActualName ( ) = = game - > GetLoadoutItem ( Menu : : menus [ type ] - > I ( A : : LOADOUT_SLOT ) ) . lock ( ) - > ActualName ( ) ) {
return true ;
}
return false ;
} ) ;
if ( foundComponent ! = components . end ( ) ) {
returnData = * foundComponent ;
} else
if ( components . size ( ) > 0 ) {
returnData = components . front ( ) ;
} else {
returnData = " OK Button " ;
}
} ,
{ //Button Key
{ game - > KEY_CONFIRM , { " Set Loadout Item " , [ ] ( MenuType type ) { } } } ,
{ game - > KEY_BACK , { " Back " , [ ] ( MenuType type ) {
Menu : : CloseMenu ( ) ;
} } } ,
{ { game - > KEY_SCROLL , Analog } , { " Scroll " , ScrollWindow ( game - > KEY_SCROLL . Analog ( ) ) } } ,
{ { game - > KEY_SCROLLUP , Held } , { " Scroll Up " , ScrollWindow ( - 1.0f ) } } ,
{ { game - > KEY_SCROLLDOWN , Held } , { " Scroll Down " , ScrollWindow ( 1.0f ) } } ,
}
, { //Button Navigation Rules
{ " OK Button " , {
. up = [ ] ( MenuType type , Data & returnData ) {
auto & itemsList = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) - > GetComponents ( ) ;
if ( itemsList . size ( ) > 0 ) {
returnData = itemsList . back ( ) ;
}
} ,
. down = [ ] ( MenuType type , Data & returnData ) {
auto & itemsList = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) - > GetComponents ( ) ;
if ( itemsList . size ( ) > 0 ) {
returnData = itemsList . front ( ) ;
}
} ,
. left = [ ] ( MenuType type , Data & returnData ) {
auto & itemsList = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) - > GetComponents ( ) ;
if ( itemsList . size ( ) > 0 ) {
returnData = itemsList . back ( ) ;
}
} ,
. right = [ ] ( MenuType type , Data & returnData ) {
auto & itemsList = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) - > GetComponents ( ) ;
if ( itemsList . size ( ) > 0 ) {
returnData = itemsList . front ( ) ;
}
} ,
} } ,
{ " inventory " , {
. up = [ ] ( MenuType type , Data & returnData ) {
auto & selection = Menu : : menus [ type ] - > GetSelection ( ) ;
auto & itemsList = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) - > GetComponents ( ) ;
auto itemsWindow = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) ;
auto component = std : : find_if ( itemsList . begin ( ) , itemsList . end ( ) , [ & ] ( auto & comp ) { return comp . lock ( ) = = selection . lock ( ) ; } ) ;
if ( itemsList . size ( ) > 0 ) {
if ( component = = itemsList . end ( ) ) {
//Set the selected button to the last element in the list.
returnData = itemsList . back ( ) ;
} else {
int invWidth = int ( ( itemsWindow - > rect . size . x - 12 ) / ( float ( itemsWindow - > options . size . x ) + itemsWindow - > options . padding ) ) ;
std : : weak_ptr < MenuItemButton > selectedButton = DYNAMIC_POINTER_CAST < MenuItemButton > ( * component ) ;
int newRowIndex = selectedButton . lock ( ) - > inventoryIndex - invWidth ; //Moving up moves the cursor up an entire row.
if ( newRowIndex < 0 ) {
//This means we have to wrap around.
returnData = " OK Button " ;
return ;
}
if ( newRowIndex < 0 | | newRowIndex > = itemsList . size ( ) ) ERR ( std : : format ( " New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING! " , newRowIndex ) ) ;
//Select the component that matches this new number.
auto component = std : : find_if ( itemsList . begin ( ) , itemsList . end ( ) , [ & ] ( auto & comp ) { return DYNAMIC_POINTER_CAST < MenuItemButton > ( comp ) - > inventoryIndex = = newRowIndex ; } ) ;
if ( component = = itemsList . end ( ) ) ERR ( std : : format ( " WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING! " , newRowIndex ) ) ;
returnData = * component ;
}
} else {
returnData = " OK Button " ;
}
} ,
. down = [ ] ( MenuType type , Data & returnData ) {
auto & selection = Menu : : menus [ type ] - > GetSelection ( ) ;
auto & itemsList = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) - > GetComponents ( ) ;
auto itemsWindow = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) ;
auto component = std : : find_if ( itemsList . begin ( ) , itemsList . end ( ) , [ & ] ( auto & comp ) { return comp . lock ( ) = = selection . lock ( ) ; } ) ;
if ( itemsList . size ( ) > 0 ) {
if ( component = = itemsList . end ( ) ) {
//Set the selected button to the first element in the list.
returnData = itemsList . front ( ) ;
} else {
int invWidth = int ( ( itemsWindow - > rect . size . x - 12 ) / ( float ( itemsWindow - > options . size . x ) + itemsWindow - > options . padding ) ) ;
std : : weak_ptr < MenuItemButton > selectedButton = DYNAMIC_POINTER_CAST < MenuItemButton > ( * component ) ;
int newRowIndex = selectedButton . lock ( ) - > inventoryIndex + invWidth ; //Moving down moves the cursor down an entire row.
if ( newRowIndex > = itemsList . size ( ) ) {
//This means we have to wrap around.
returnData = " OK Button " ;
return ;
}
if ( newRowIndex < 0 | | newRowIndex > = itemsList . size ( ) ) ERR ( std : : format ( " New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING! " , newRowIndex ) ) ;
//Select the component that matches this new number.
auto component = std : : find_if ( itemsList . begin ( ) , itemsList . end ( ) , [ & ] ( auto & comp ) { return DYNAMIC_POINTER_CAST < MenuItemButton > ( comp ) - > inventoryIndex = = newRowIndex ; } ) ;
if ( component = = itemsList . end ( ) ) ERR ( std : : format ( " WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING! " , newRowIndex ) ) ;
returnData = * component ;
}
} else {
returnData = " OK Button " ;
}
} ,
. left = [ ] ( MenuType type , Data & returnData ) {
auto & selection = Menu : : menus [ type ] - > GetSelection ( ) ;
auto & itemsList = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) - > GetComponents ( ) ;
auto itemsWindow = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) ;
auto component = std : : find_if ( itemsList . begin ( ) , itemsList . end ( ) , [ & ] ( auto & comp ) { return comp . lock ( ) = = selection . lock ( ) ; } ) ;
if ( itemsList . size ( ) > 0 ) {
if ( component = = itemsList . end ( ) ) {
//Set the selected button to the last element in the list.
returnData = itemsList . back ( ) ;
} else {
int invWidth = int ( ( itemsWindow - > rect . size . x - 12 ) / ( float ( itemsWindow - > options . size . x ) + itemsWindow - > options . padding ) ) ;
std : : weak_ptr < MenuItemButton > selectedButton = DYNAMIC_POINTER_CAST < MenuItemButton > ( * component ) ;
int rowBaseIndex = ( selectedButton . lock ( ) - > inventoryIndex / invWidth ) * invWidth ;
int newRowIndex = selectedButton . lock ( ) - > inventoryIndex - 1 ;
if ( newRowIndex < rowBaseIndex ) newRowIndex + = invWidth ;
newRowIndex = std : : min ( int ( itemsList . size ( ) ) - 1 , newRowIndex ) ;
if ( newRowIndex < 0 ) {
//This means we have to wrap around.
newRowIndex + = itemsList . size ( ) ;
}
if ( newRowIndex < 0 | | newRowIndex > = itemsList . size ( ) ) ERR ( std : : format ( " New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING! " , newRowIndex ) ) ;
//Select the component that matches this new number.
auto component = std : : find_if ( itemsList . begin ( ) , itemsList . end ( ) , [ & ] ( auto & comp ) { return DYNAMIC_POINTER_CAST < MenuItemButton > ( comp ) - > inventoryIndex = = newRowIndex ; } ) ;
if ( component = = itemsList . end ( ) ) ERR ( std : : format ( " WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING! " , newRowIndex ) ) ;
returnData = * component ;
}
} else {
returnData = " OK Button " ;
}
} ,
. right = [ ] ( MenuType type , Data & returnData ) {
auto & selection = Menu : : menus [ type ] - > GetSelection ( ) ;
auto & itemsList = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) - > GetComponents ( ) ;
auto itemsWindow = Component < InventoryScrollableWindowComponent > ( type , " inventory " ) ;
auto component = std : : find_if ( itemsList . begin ( ) , itemsList . end ( ) , [ & ] ( auto & comp ) { return comp . lock ( ) = = selection . lock ( ) ; } ) ;
if ( itemsList . size ( ) > 0 ) {
if ( component = = itemsList . end ( ) ) {
//Set the selected button to the first element in the list.
returnData = itemsList . front ( ) ;
} else {
int invWidth = int ( ( itemsWindow - > rect . size . x - 12 ) / ( float ( itemsWindow - > options . size . x ) + itemsWindow - > options . padding ) ) ;
std : : weak_ptr < MenuItemButton > selectedButton = DYNAMIC_POINTER_CAST < MenuItemButton > ( * component ) ;
int rowBaseIndex = ( selectedButton . lock ( ) - > inventoryIndex / invWidth ) * invWidth ;
int newRowIndex = selectedButton . lock ( ) - > inventoryIndex + 1 ;
if ( newRowIndex > rowBaseIndex + invWidth - 1 ) newRowIndex - = invWidth ;
newRowIndex = std : : clamp ( newRowIndex , 0 , int ( itemsList . size ( ) - 1 ) ) ;
if ( newRowIndex < 0 | | newRowIndex > = itemsList . size ( ) ) ERR ( std : : format ( " New Row Index ended up out-of-bounds! newRowIndex={}. THIS SHOULD NOT BE HAPPENING! " , newRowIndex ) ) ;
//Select the component that matches this new number.
auto component = std : : find_if ( itemsList . begin ( ) , itemsList . end ( ) , [ & ] ( auto & comp ) { return DYNAMIC_POINTER_CAST < MenuItemButton > ( comp ) - > inventoryIndex = = newRowIndex ; } ) ;
if ( component = = itemsList . end ( ) ) ERR ( std : : format ( " WARNING! Could not find row index {} in items list while navigating! THIS SHOULD NOT BE HAPPENING! " , newRowIndex ) ) ;
returnData = * component ;
}
} else {
returnData = " OK Button " ;
}
} ,
} } ,
} ) ;
}