The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'!
https://forums.lestoria.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
696 lines
24 KiB
696 lines
24 KiB
#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"
|
|
#include "RowInventoryScrollableWindowComponent.h"
|
|
#include "MenuItemItemButton.h"
|
|
|
|
INCLUDE_ITEM_CATEGORIES
|
|
INCLUDE_DATA
|
|
|
|
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;
|
|
safemap<ITCategory,std::vector<MenuComponent*>>Menu::merchantInventoryListeners;
|
|
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
|
|
INCLUDE_WINDOW_SIZE
|
|
|
|
using A=Attribute;
|
|
|
|
Menu::Menu(vf2d pos,vf2d size)
|
|
:pos(pos==CENTERED?WINDOW_SIZE/2-size/2:vi2d{pos}),size(size){
|
|
this->window=ViewPort::rectViewPort({-24,-24},this->size+vi2d{48,48},this->pos);
|
|
}
|
|
|
|
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();
|
|
InitializeMerchantWindow();
|
|
InitializeBuyItemWindow();
|
|
InitializeSellItemWindow();
|
|
|
|
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<<"\tComponent "<<(count+1)<<": "<<std::endl;
|
|
std::cout<<"\t-Parent Menu:"<<component->parentMenu<<" Label: "<<component->label<<std::endl;
|
|
std::cout<<"\t\tCreated 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(!game->IsFocused()||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(!game->IsFocused()||selection==vi2d{-1,-1}||(buttons[selection.y][selection.x]->disabled||buttons[selection.y][selection.x]->grayedOut))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&&!button->grayedOut){
|
|
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());
|
|
}
|
|
|
|
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;});
|
|
|
|
if(GetCurrentTheme().IsScaled()){
|
|
DrawScaledWindowBorder(game,pos,size,GetRenderColor());
|
|
}else{
|
|
DrawTiledWindowBorder(game,pos,size,GetRenderColor());
|
|
}
|
|
|
|
for(const auto&component:allComponents){
|
|
if(component->renderInMain){
|
|
component->_DrawDecal(window,this==Menu::stack.back());
|
|
}
|
|
}
|
|
|
|
if(draggingComponent!=nullptr){
|
|
vf2d offsetPos=draggingComponent->rect.pos;
|
|
if(!UsingMouseNavigation()){
|
|
MenuComponent*selectedComponent=buttons[selection.y][selection.x];
|
|
vf2d drawOffset{};
|
|
if(selectedComponent->parentComponent!=nullptr){
|
|
ScrollableWindowComponent*scrollableComponent=dynamic_cast<ScrollableWindowComponent*>(selectedComponent->parentComponent);
|
|
if(scrollableComponent!=nullptr){
|
|
drawOffset+=scrollableComponent->GetScrollAmount();
|
|
}
|
|
}
|
|
draggingComponent->V(A::DRAW_OFFSET)=drawOffset+pos-offsetPos+selectedComponent->rect.pos+vi2d{1,-4};
|
|
draggingComponent->DrawDecal(window,this==Menu::stack.back());
|
|
}else{
|
|
draggingComponent->V(A::DRAW_OFFSET)-offsetPos+game->GetMousePos();
|
|
draggingComponent->DrawDecal(window,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{
|
|
buttonHoldTime=0;
|
|
}
|
|
}
|
|
if(prevSelection!=selection){
|
|
if(selection!=vi2d{-1,-1}&&(buttons[selection.y][selection.x]->disabled||buttons[selection.y][selection.x]->grayedOut)){
|
|
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::MerchantInventorySlotsUpdated(ITCategory cat){
|
|
//Update the inventory with a new inventory slot, since there's one additional item to interact with now.
|
|
for(MenuComponent*component:merchantInventoryListeners.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::InitializeMenuListenerCategory(const std::string&category){
|
|
inventoryListeners[category];
|
|
merchantInventoryListeners[category];
|
|
}
|
|
|
|
void Menu::AddMerchantInventoryListener(MenuComponent*component,ITCategory category){
|
|
if(merchantInventoryListeners.count(category)){
|
|
std::vector<MenuComponent*>&listenerList=merchantInventoryListeners.at(category);
|
|
if(std::find(listenerList.begin(),listenerList.end(),component)!=listenerList.end()){
|
|
ERR("WARNING! Component "<<component->name<<" has already been added to the "<<category<<" merchant 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();
|
|
}
|
|
|
|
MenuType Menu::GetType(){
|
|
return type;
|
|
}
|
|
|
|
void Menu::LockInListeners(){
|
|
inventoryListeners.SetInitialized();
|
|
merchantInventoryListeners.SetInitialized();
|
|
} |