From 60e46a83f2811e8843458f3997cc6980dcfc4074 Mon Sep 17 00:00:00 2001 From: sigonasr2 Date: Sun, 1 Oct 2023 01:11:53 -0500 Subject: [PATCH] Improve keyboard/controller-based menu navigation by separating each row into its own structure. --- Crawler/Menu.cpp | 164 +++++++++++++++++++++++++++-------- Crawler/Menu.h | 7 +- Crawler/Version.h | 2 +- Crawler/olcPixelGameEngine.h | 16 +++- 4 files changed, 146 insertions(+), 43 deletions(-) diff --git a/Crawler/Menu.cpp b/Crawler/Menu.cpp index 4f5b7333..3af2371f 100644 --- a/Crawler/Menu.cpp +++ b/Crawler/Menu.cpp @@ -24,81 +24,171 @@ void Menu::InitializeMenus(){ } void Menu::AddButton(const MenuButton&button){ - buttons.push_back(button); + buttons[button.rect.pos.y].push_back(button); } void Menu::MenuSelect(Crawler*game){ - if(selection==-1)return; - buttons[selection].onClick(*this,game); - if(buttons[selection].menuDest!=MenuType::ENUM_END){ - if(stack.size()<32){ - stack.push_back(&menus[buttons[selection].menuDest]);//Navigate to the next menu. - }else{ + if(selection==vi2d{-1,-1})return; + buttons[selection.y][selection.x].onClick(*this,game); + 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!"<GetScreenSize()/2-size/2; - for(MenuButton&button:buttons){ - button.hovered=false; + for(auto&key:buttons){ + for(auto&button:key.second){ + button.hovered=false; + } } if(!MOUSE_NAVIGATION){ - if(selection!=-1)buttons[selection].hovered=true; + if(selection!=vi2d{-1,-1})buttons[selection.y][selection.x].hovered=true; }else{ - for(MenuButton&button:buttons){ - if(geom2d::overlaps(geom2d::rect{button.rect.pos+upperLeftPos,button.rect.size},game->GetMousePos())){ - button.hovered=true; + for(auto&key:buttons){ + for(auto&button:key.second){ + if(geom2d::overlaps(geom2d::rect{button.rect.pos+upperLeftPos,button.rect.size},game->GetMousePos())){ + button.hovered=true; + } } } } - if(game->GetKey(RIGHT).bPressed||game->GetKey(DOWN).bPressed){ - MOUSE_NAVIGATION=false; - selection=(selection+1)%buttons.size(); - } - if(game->GetKey(LEFT).bPressed||game->GetKey(UP).bPressed){ - MOUSE_NAVIGATION=false; - selection--; - if(selection<0)selection+=buttons.size(); - } + + KeyboardButtonNavigation(game); 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){ MenuSelect(game); //Key presses automatically highlight the first button if it's not highlighted. - if(selection==-1&&buttons.size()>0){ - selection=0; + 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; - int index=0; - for(MenuButton&button:buttons){ - if(geom2d::overlaps(geom2d::rect{button.rect.pos+upperLeftPos,button.rect.size},game->GetMousePos())){ - selection=index; - break; + selection={-1,-1}; + for(auto&key:buttons){ + int index=0; + for(auto&button:key.second){ + if(geom2d::overlaps(geom2d::rect{button.rect.pos+upperLeftPos,button.rect.size},game->GetMousePos())){ + selection={index,key.first}; + break; + } + index++; } - index++; } MenuSelect(game); } } - for(MenuButton&button:buttons){ - button.Update(game); + for(auto&key:buttons){ + for(auto&button:key.second){ + button.Update(game); + } } }; void Menu::Draw(Crawler*game){ vf2d upperLeftPos=game->GetScreenSize()/2-size/2; game->FillRectDecal(upperLeftPos,size,VERY_DARK_BLUE); - for(MenuButton&button:buttons){ - button.Draw(game,upperLeftPos); + for(auto&key:buttons){ + for(auto&button:key.second){ + button.Draw(game,upperLeftPos); + } } }; void Menu::OpenMenu(MenuType menu){ stack.clear(); stack.push_back(&(menus[menu])); +} + +void Menu::KeyboardButtonNavigation(Crawler*game){ + if(game->GetKey(RIGHT).bPressed){ + if(selection==vi2d{-1,-1})return; + MOUSE_NAVIGATION=false; + selection.x=(selection.x+1)%buttons[selection.y].size(); + } + if(game->GetKey(LEFT).bPressed){ + if(selection==vi2d{-1,-1})return; + selection.x--; + if(selection.x<0)selection.x+=buttons[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:buttons){ + selection.y=key.first; + break; + } + }else{ + for(auto&key:buttons){ + if(found){ //Once we discover the previous element, the next element becomes our next selection. + selection.y=key.first; + 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:buttons){ + selection.y=key.first; + break; + } + } + } + } + if(game->GetKey(UP).bPressed){ + MOUSE_NAVIGATION=false; + + if(selection==vi2d{-1,-1}){ + //Highlight last item. + for(auto&key:buttons){ + selection.y=key.first; + } + }else{ + int prevInd=-1; + for(auto&key:buttons){ + if(key.first==selection.y){ + break; + } + prevInd=key.first; + } + if(prevInd!=-1){ + selection.y=prevInd; + }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:buttons){ + 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(buttons[selection.y].size())-1); + }else{ + selection.x=-1; + } + } } \ No newline at end of file diff --git a/Crawler/Menu.h b/Crawler/Menu.h index e9027f3a..11b8f382 100644 --- a/Crawler/Menu.h +++ b/Crawler/Menu.h @@ -10,8 +10,9 @@ class Menu{ friend class Player; static bool MOUSE_NAVIGATION; static std::mapmenus; - std::vectorbuttons; - int selection=-1; + + std::map>buttons; //Buttons are stored in rows followed by their column order. + vi2d selection={-1,-1}; vf2d size; //Size in tiles (24x24), every menu will be tile-based public: Menu(); @@ -26,5 +27,7 @@ private: void MenuSelect(Crawler*game); static const Menu InitializeTestMenu(); static const Menu InitializeTestSubMenu(); + + void KeyboardButtonNavigation(Crawler*game); }; diff --git a/Crawler/Version.h b/Crawler/Version.h index 139c3748..44a33eda 100644 --- a/Crawler/Version.h +++ b/Crawler/Version.h @@ -2,7 +2,7 @@ #define VERSION_MAJOR 0 #define VERSION_MINOR 2 #define VERSION_PATCH 0 -#define VERSION_BUILD 1615 +#define VERSION_BUILD 1643 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Crawler/olcPixelGameEngine.h b/Crawler/olcPixelGameEngine.h index 8fb4b8d7..f59c55f4 100644 --- a/Crawler/olcPixelGameEngine.h +++ b/Crawler/olcPixelGameEngine.h @@ -2008,7 +2008,6 @@ namespace olc // Construct the window if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; - olc_UpdateWindowPos(30,30); olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); // Start the thread @@ -5547,6 +5546,8 @@ namespace olc olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle, vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + MoveWindow(olc_hWnd,vTopLeft.x,vTopLeft.y,width,height,false); //A hack to get the window's position updated in the correct spot (WM_MOVE reports the correct upper-left corner of the client area) + DragAcceptFiles(olc_hWnd, true); // Create Keyboard Mapping @@ -5642,7 +5643,13 @@ namespace olc ptrPGE->olc_UpdateMouse(ix, iy); return 0; } - case WM_MOVE: ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; + case WM_MOVE: + { + uint16_t x = lParam & 0xFFFF; uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; int16_t iy = *(int16_t*)&y; + ptrPGE->olc_UpdateWindowPos(ix, iy); + return 0; + } case WM_SIZE: ptrPGE->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; case WM_MOUSEWHEEL: ptrPGE->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); return 0; case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; @@ -6624,7 +6631,10 @@ namespace olc { emscripten_set_window_title(s.c_str()); return olc::OK; } virtual olc::rcode StartSystemEventLoop() override - { return olc::OK; } + { + ptrPGE->olc_UpdateWindowPos(EM_ASM_INT({return Module.canvas.getBoundingClientRect().left}),EM_ASM_INT({return Module.canvas.getBoundingClientRect().top})); + return olc::OK; + } virtual olc::rcode HandleSystemEvent() override {