# pragma once
# include "Menu.h"
# include "MenuComponent.h"
# include "Crawler.h"
typedef Attribute A ;
class ScrollableWindowComponent : public MenuComponent {
protected :
Renderable r ;
std : : vector < MenuComponent * > components ;
MenuComponent * upButton = nullptr ;
MenuComponent * downButton = nullptr ;
geom2d : : rect < float > bounds ; //It's for the scrollbar.
float scrollBarHeight = 0 ;
float scrollBarTop = 0 ;
bool scrollBarSelected = false ;
float scrollBarHoverTime = 0 ;
private :
inline bool OnScreen ( MenuComponent * component ) {
return geom2d : : overlaps ( rect , geom2d : : rect < float > { component - > rect . pos + V ( A : : SCROLL_OFFSET ) + vf2d { 2 , 2 } , component - > rect . size - vf2d { 2 , 2 } } ) ;
}
public :
inline ScrollableWindowComponent ( MenuType parent , geom2d : : rect < float > rect , Decal * icon , MenuFunc onClick )
: MenuComponent ( parent , rect , " " , onClick , false ) {
r . Create ( rect . size . x , rect . size . y ) ;
}
virtual inline ~ ScrollableWindowComponent ( ) {
}
private :
virtual inline void AfterCreate ( ) override {
upButton = new MenuComponent ( parentMenu , { vf2d { rect . size . x - 12 , 0 } , { 12 , 12 } } , " ^ " , [ & ] ( MenuFuncData dat ) { V ( A : : SCROLL_OFFSET ) . y + = " ThemeGlobal.MenuButtonScrollSpeed " _I ; } , true , false ) ;
downButton = new MenuComponent ( parentMenu , { rect . size - vf2d { 12 , 12 } , { 12 , 12 } } , " v " , [ & ] ( MenuFuncData dat ) { V ( A : : SCROLL_OFFSET ) . y - = " ThemeGlobal.MenuButtonScrollSpeed " _I ; } , true , false ) ;
//Let's use the internal name of this component to add unique names for sub-components.
Menu : : menus [ parentMenu ] - > AddComponent ( name + upButton - > rect . pos . str ( ) + " _ " + upButton - > rect . size . str ( ) , upButton ) ;
Menu : : menus [ parentMenu ] - > AddComponent ( name + downButton - > rect . pos . str ( ) + " _ " + downButton - > rect . size . str ( ) , downButton ) ;
}
protected :
virtual inline void Update ( Crawler * game ) override {
MenuComponent : : Update ( game ) ;
vf2d windowAbsPos = Menu : : menus [ parentMenu ] - > pos + rect . pos ;
bool mouseOverScrollbar = geom2d : : overlaps ( geom2d : : rect < float > ( windowAbsPos + vf2d { rect . size . x - 12 , scrollBarTop + 12 } , { 12 , scrollBarHeight } ) , game - > GetMousePos ( ) ) ;
if ( mouseOverScrollbar | | scrollBarSelected ) {
scrollBarHoverTime = std : : min ( scrollBarHoverTime + game - > GetElapsedTime ( ) , " ThemeGlobal.HighlightTime " _F ) ;
if ( game - > GetMouse ( 0 ) . bPressed ) {
scrollBarSelected = true ;
}
if ( game - > GetMouse ( 0 ) . bReleased ) {
scrollBarSelected = false ;
}
if ( scrollBarSelected ) {
float spaceBetweenTopAndBottomArrows = rect . size . y - 24 ;
float viewHeight = rect . size . y ;
float totalContentHeight = bounds . size . y ;
float scrollBarScale = ( spaceBetweenTopAndBottomArrows / totalContentHeight ) ;
//The scroll amount moves centered on the position the mouse is at.
float newScrollbarTop = ( game - > GetMousePos ( ) . y - windowAbsPos . y - 12 ) - scrollBarHeight / 2 ;
V ( A : : SCROLL_OFFSET ) . y = ( - newScrollbarTop + 1 ) / scrollBarScale ; //Derived formula from the draw code.
}
} else {
scrollBarHoverTime = std : : max ( scrollBarHoverTime - game - > GetElapsedTime ( ) , 0.f ) ;
}
if ( game - > GetMouseWheel ( ) ! = 0 ) {
if ( game - > GetMouseWheel ( ) > 0 ) {
V ( A : : SCROLL_OFFSET ) . y + = " ThemeGlobal.MenuScrollWheelSpeed " _I ;
} else {
V ( A : : SCROLL_OFFSET ) . y - = " ThemeGlobal.MenuScrollWheelSpeed " _I ;
}
}
V ( A : : SCROLL_OFFSET ) . y = std : : clamp ( V ( A : : SCROLL_OFFSET ) . y , - ( bounds . size . y - rect . size . y ) , 0.f ) ;
for ( MenuComponent * component : components ) {
component - > disabled = ! OnScreen ( component ) ;
component - > _Update ( game ) ;
}
upButton - > disabled = false ;
downButton - > disabled = false ;
if ( geom2d : : contains ( rect , bounds ) ) { //This means we have no reason to show a scrollbar.
upButton - > disabled = true ;
downButton - > disabled = true ;
}
}
virtual inline void Draw ( Crawler * game , vf2d parentPos , bool focused ) override {
MenuComponent : : Draw ( game , parentPos , focused ) ;
Sprite * prevDrawTarget = game - > GetDrawTarget ( ) ;
game - > SetDrawTarget ( r . Sprite ( ) ) ;
game - > Clear ( BLANK ) ;
for ( MenuComponent * component : components ) {
component - > _Draw ( game , rect . pos + V ( A : : SCROLL_OFFSET ) , focused ) ;
}
game - > SetDrawTarget ( prevDrawTarget ) ;
game - > DrawSprite ( parentPos , r . Sprite ( ) ) ;
}
inline void DrawScrollbar ( Crawler * game , vf2d parentPos , bool focused ) {
float spaceBetweenTopAndBottomArrows = rect . size . y - 24 ;
float viewHeight = rect . size . y ;
float totalContentHeight = bounds . size . y ;
float scrollBarScale = ( spaceBetweenTopAndBottomArrows / totalContentHeight ) ;
scrollBarHeight = viewHeight * scrollBarScale - 1 ;
scrollBarTop = - V ( A : : SCROLL_OFFSET ) . y * scrollBarScale + 1 ;
float focusedWindowColorMult = ( focused ? 1 : " ThemeGlobal.MenuUnfocusedColorMult " _F ) ;
game - > FillRectDecal ( rect . pos + parentPos + vf2d { rect . size . x - 13 , scrollBarTop + 12 } , { 12 , scrollBarHeight } , PixelLerp ( Menu : : GetCurrentTheme ( ) . GetButtonCol ( ) , Menu : : GetCurrentTheme ( ) . GetHighlightCol ( ) , scrollBarHoverTime / " ThemeGlobal.HighlightTime " _F ) * focusedWindowColorMult ) ;
game - > DrawRectDecal ( rect . pos + parentPos + vf2d { rect . size . x - 13 , scrollBarTop + 12 } , { 12 , scrollBarHeight } , WHITE * focusedWindowColorMult ) ;
}
virtual inline void DrawDecal ( Crawler * game , vf2d parentPos , bool focused ) override {
MenuComponent : : DrawDecal ( game , parentPos , focused ) ;
game - > DrawRectDecal ( rect . pos + parentPos , rect . size ) ;
for ( MenuComponent * component : components ) {
component - > _DrawDecal ( game , rect . pos + parentPos + V ( A : : SCROLL_OFFSET ) , focused ) ;
}
DrawScrollbar ( game , parentPos , focused ) ;
}
virtual bool GetHoverState ( Crawler * game , MenuComponent * child ) override {
return geom2d : : overlaps ( geom2d : : rect < float > { Menu : : menus [ parentMenu ] - > pos + rect . pos + child - > rect . pos + V ( A : : SCROLL_OFFSET ) , child - > rect . size } , game - > GetMousePos ( ) ) ;
}
//Calculates the bounds of all components.
geom2d : : rect < float > inline CalculateBounds ( ) {
geom2d : : rect < float > bounds ;
for ( MenuComponent * component : components ) {
if ( component - > rect . pos . x < bounds . pos . x ) {
float sizeIncrease = bounds . pos . x - component - > rect . pos . x ;
bounds . size . x + = sizeIncrease ;
bounds . pos . x = component - > rect . pos . x ;
}
if ( component - > rect . right ( ) . start . x > bounds . right ( ) . start . x ) {
float sizeIncrease = component - > rect . right ( ) . start . x - bounds . right ( ) . start . x ;
bounds . size . x + = sizeIncrease ;
}
if ( component - > rect . pos . y < bounds . pos . y ) {
float sizeIncrease = bounds . pos . y - component - > rect . pos . y ;
bounds . size . y + = sizeIncrease ;
bounds . pos . y = component - > rect . pos . y ;
}
if ( component - > rect . bottom ( ) . start . y > bounds . bottom ( ) . start . y ) {
float sizeIncrease = component - > rect . bottom ( ) . start . y - bounds . bottom ( ) . start . y ;
bounds . size . y + = sizeIncrease ;
}
}
return bounds ;
}
public :
void inline AddComponent ( Menu * parentMenu , std : : string key , MenuComponent * button ) {
components . push_back ( button ) ;
button - > renderInMain = false ; //Now we are in control!
button - > parentComponent = this ;
if ( button - > rect . pos . x < bounds . pos . x ) {
float sizeIncrease = bounds . pos . x - button - > rect . pos . x ;
bounds . size . x + = sizeIncrease ;
bounds . pos . x = button - > rect . pos . x ;
}
if ( button - > rect . right ( ) . start . x > bounds . right ( ) . start . x ) {
float sizeIncrease = button - > rect . right ( ) . start . x - bounds . right ( ) . start . x ;
bounds . size . x + = sizeIncrease ;
}
if ( button - > rect . pos . y < bounds . pos . y ) {
float sizeIncrease = bounds . pos . y - button - > rect . pos . y ;
bounds . size . y + = sizeIncrease ;
bounds . pos . y = button - > rect . pos . y ;
}
if ( button - > rect . bottom ( ) . start . y > bounds . bottom ( ) . start . y ) {
float sizeIncrease = button - > rect . bottom ( ) . start . y - bounds . bottom ( ) . start . y ;
bounds . size . y + = sizeIncrease ;
}
parentMenu - > AddComponent ( key , button ) ;
}
virtual inline bool PointWithinParent ( MenuComponent * child , vi2d drawPos ) override {
return geom2d : : overlaps ( geom2d : : rect < float > { Menu : : menus [ parentMenu ] - > pos + rect . pos , rect . size } , drawPos ) ;
}
virtual inline bool HandleOutsideDisabledButtonSelection ( MenuComponent * disabledButton ) override {
//Set the offset so the center is highlighted by this button.
V ( A : : SCROLL_OFFSET ) . y = - ( disabledButton - > rect . pos . y - disabledButton - > rect . size . y / 2 ) ;
V ( A : : SCROLL_OFFSET ) . y + = rect . size . y / 2 ;
return true ;
} ;
virtual inline size_t ComponentCount ( ) {
return components . size ( ) ;
}
virtual inline void RemoveButton ( MenuComponent * button ) {
std : : vector < MenuComponent * > & buttonList = Menu : : menus [ button - > parentMenu ] - > buttons . at ( button - > GetPos ( ) . y ) ;
std : : vector < MenuComponent * > & keyboardButtonList = Menu : : menus [ button - > parentMenu ] - > keyboardButtons . at ( button - > GetPos ( ) . y ) ;
size_t removedCount = 0 ;
removedCount + = std : : erase ( buttonList , button ) ;
removedCount + = std : : erase ( keyboardButtonList , button ) ;
if ( removedCount ! = 2 ) {
std : : cout < < " WARNING! Attempted to remove buttons from button listing, but not found! " ;
throw ;
}
if ( buttonList . size ( ) = = 0 ) {
if ( ! Menu : : menus [ button - > parentMenu ] - > buttons . erase ( button - > GetPos ( ) . y ) ) {
std : : cout < < " WARNING! Attempted to erase key " < < button - > GetPos ( ) . y < < " from button map, but the list still exists! " ;
throw ;
}
}
if ( keyboardButtonList . size ( ) = = 0 ) {
if ( ! Menu : : menus [ button - > parentMenu ] - > keyboardButtons . erase ( button - > GetPos ( ) . y ) ) {
std : : cout < < " WARNING! Attempted to erase key " < < button - > GetPos ( ) . y < < " from button map, but the list still exists! " ;
throw ;
}
}
}
void inline RemoveEmptySlots ( ) {
//Algorithm will iterate through all slots, finding blank slots. Each time a blank slot is found, all items will shift over by one, and then the last item will be removed. Repeat until all slots iterated through.
for ( int i = 0 ; i < components . size ( ) ; i + + ) {
MenuComponent * button = components [ i ] ;
button - > Update ( game ) ; //We have to call update to update the validation state.
//HACK ALERT!! This only gets called on inventories...And only on inventory items, which would have the valid flag set. We only care about components that are inventory slots.
if ( ! button - > valid ) {
for ( int j = i ; j < components . size ( ) - 1 ; j + + ) {
//Take the item in the next slot and move it to this slot.
Menu : : menus [ components [ j ] - > parentMenu ] - > components . at ( components [ j ] - > name ) = components [ j + 1 ] ;
components [ j ] = components [ j + 1 ] ;
}
MenuComponent * lastButton = Menu : : menus [ components [ components . size ( ) - 1 ] - > parentMenu ] - > components . at ( components [ components . size ( ) - 1 ] - > name ) ;
//Now we have to fix up the keyboard button list.
RemoveButton ( lastButton ) ;
Menu : : menus [ components [ components . size ( ) - 1 ] - > parentMenu ] - > components . erase ( components [ components . size ( ) - 1 ] - > name ) ;
//Now delete the last slot.
components . erase ( components . end ( ) - 1 ) ;
i - - ; //Subtract one from the index so we don't accidently skip slots.
}
}
bounds = CalculateBounds ( ) ; //Recalculate the bounds as it's possible the width/height of the component has changed.
}
virtual void OnInventorySlotsUpdate ( ITCategory cat ) override {
std : : vector < std : : string > & inv = Inventory : : get ( cat ) ;
//We only want to refresh the inventory slots if the component count no longer matches what's actually in our inventory.
if ( ComponentCount ( ) < inv . size ( ) ) { //We need more space to display our items.
int invWidth = " ThemeGlobal.InventoryWidth " _I ;
int x = ( inv . size ( ) - 1 ) % invWidth ;
int y = ( inv . size ( ) - 1 ) / invWidth ;
int itemIndex = y * invWidth + x ;
int buttonSize = " ThemeGlobal.InventoryButtonSize " _I ;
int totalSpacing = " ThemeGlobal.InventoryItemSpacing " _I + buttonSize ;
MenuFunc useItemFunc = [ ] ( MenuFuncData data ) {
MenuItemButton * button = ( MenuItemButton * ) data . component ;
button - > UseItem ( ) ;
} ;
MenuItemButton * button = new MenuItemButton { parentMenu , { { float ( totalSpacing * x ) , float ( totalSpacing * y ) } , { float ( buttonSize ) , float ( buttonSize ) } } , Inventory : : get ( " Consumables " ) , itemIndex , useItemFunc } ;
AddComponent ( Menu : : menus [ parentMenu ] , " item " + std : : to_string ( itemIndex ) , button ) ;
} else
if ( ComponentCount ( ) > inv . size ( ) ) { //There are empty spots, so let's clean up.
RemoveEmptySlots ( ) ;
}
}
} ;