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.
AdventuresInLestoria/Crawler/Menu.cpp

492 lines
15 KiB

#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){
selection=prevSelection;
}
// If the new selection of a button on this frame is disabled for some reason, we need to go back to what we had selected before.
}
}
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;
}