#include "Crawler.h" #include "MenuComponent.h" #include "DEFINES.h" #include "safemap.h" bool Menu::MOUSE_NAVIGATION=true; std::vectorMenu::stack; std::mapMenu::menus; std::string Menu::themeSelection="BlueDefault"; safeunorderedmapMenu::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;typecomponents){ 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 "<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!"<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{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; }