From ff3f4ba1c08ff3fe79dfe3be8740e092c3b23786 Mon Sep 17 00:00:00 2001 From: sigonasr2 Date: Sun, 15 Oct 2023 12:58:39 -0500 Subject: [PATCH] Setup framework for scrollbar and buttons in ScrollableWindowComponent. Fixed a bug where disabled buttons would not increment selection check loop. CreateMenu function instead of creating pointer and returning, so windows can add stuff to menus easily. --- Crawler/Crawler.cpp | 14 ++++-- Crawler/InventoryWindow.cpp | 10 ++--- Crawler/Menu.cpp | 54 +++++++++++++++--------- Crawler/Menu.h | 14 +++--- Crawler/MenuComponent.cpp | 2 + Crawler/MenuComponent.h | 1 + Crawler/ScrollableWindowComponent.h | 24 +++++++++-- Crawler/TestMenu.cpp | 5 +-- Crawler/TestSubMenu.cpp | 6 +-- Crawler/Version.h | 2 +- Crawler/assets/config/configuration.txt | 2 +- Crawler/assets/knight_full3.png | Bin 6227 -> 6451 bytes Crawler/assets/knight_full_render1.png | Bin 0 -> 6594 bytes 13 files changed, 86 insertions(+), 48 deletions(-) create mode 100644 Crawler/assets/knight_full_render1.png diff --git a/Crawler/Crawler.cpp b/Crawler/Crawler.cpp index 7130058f..80cec8ae 100644 --- a/Crawler/Crawler.cpp +++ b/Crawler/Crawler.cpp @@ -1066,7 +1066,7 @@ void Crawler::RenderHud(){ if("debug_player_info"_I){ DrawShadowStringDecal({0,128},player->GetPos().str()); DrawShadowStringDecal({0,136},"Spd: "+std::to_string(player->GetMoveSpdMult())); - DrawShadowStringDecal({0,4},"Selection: "+Menu::menus[INVENTORY]->selection.str()); + DrawShadowStringDecal({0,1},"Selection: "+Menu::menus[INVENTORY]->selection.str()); DrawShadowStringDecal({0,12},"Button Hold Time: "+std::to_string(Menu::menus[INVENTORY]->buttonHoldTime)); } } @@ -1752,9 +1752,17 @@ void Crawler::InitializeGraphics(){ for(auto&val:DATA["Images"].GetKeys()){ std::string key=val.first; - std::string imgFile=DATA["Images"][key].GetString(); + std::string imgFile=DATA["Images"][key].GetString(0); std::cout<<"Loading image "+imgFile+"..."<1){ + filtering=bool(DATA["Images"][key].GetInt(1)); + } + if(DATA["Images"][key].GetValueCount()>2){ + clamping=bool(DATA["Images"][key].GetInt(2)); + } + if(!GFX.count(imgFile)&&GFX[imgFile].Load("GFX_Prefix"_S+imgFile,nullptr,filtering,clamping)!=rcode::OK){ std::cout<<" WARNING! Failed to load "+imgFile+"!"; throw; } diff --git a/Crawler/InventoryWindow.cpp b/Crawler/InventoryWindow.cpp index a335cd2a..2fcac464 100644 --- a/Crawler/InventoryWindow.cpp +++ b/Crawler/InventoryWindow.cpp @@ -11,7 +11,7 @@ INCLUDE_GFX typedef Attribute A; -Menu*Menu::InitializeInventoryWindow(){ +void Menu::InitializeInventoryWindow(){ constexpr int invWidth=5; constexpr int initialInvHeight=3; @@ -19,11 +19,11 @@ Menu*Menu::InitializeInventoryWindow(){ constexpr int buttonSize=24; constexpr int totalSpacing=buttonSize+itemSpacing; - vf2d windowSize={totalSpacing*invWidth-itemSpacing+1,totalSpacing*(3+1)-itemSpacing+24}; + vf2d windowSize={totalSpacing*invWidth-itemSpacing+2+24,totalSpacing*(3+1)-itemSpacing+24}; //Need space for the button. - Menu*inventoryWindow=new Menu(CENTERED,windowSize); + Menu*inventoryWindow=CreateMenu(INVENTORY,CENTERED,windowSize); - ScrollableWindowComponent*inventory=new ScrollableWindowComponent(INVENTORY,{{0,0},{windowSize.x,totalSpacing*3-itemSpacing}},nullptr,[](MenuFuncData data){}); + ScrollableWindowComponent*inventory=new ScrollableWindowComponent(INVENTORY,{{1,0},{windowSize.x,totalSpacing*3-itemSpacing}},nullptr,[](MenuFuncData data){}); inventoryWindow->AddComponent("inventory",inventory); MenuFunc useItemFunc=[](MenuFuncData data){ @@ -43,6 +43,4 @@ Menu*Menu::InitializeInventoryWindow(){ inventoryWindow->AddComponent("itemName",itemNameLabel); MenuLabel*itemDescriptionLabel=new MenuLabel{INVENTORY,geom2d::rect(vf2d{2,initialInvHeight*totalSpacing+itemSpacing},{windowSize.x-4,windowSize.y-108}),"",true,true}; inventoryWindow->AddComponent("itemDescription",itemDescriptionLabel); - - return inventoryWindow; } \ No newline at end of file diff --git a/Crawler/Menu.cpp b/Crawler/Menu.cpp index 0bdf3a41..3bb75289 100644 --- a/Crawler/Menu.cpp +++ b/Crawler/Menu.cpp @@ -15,8 +15,6 @@ extern vi2d WINDOW_SIZE; typedef Attribute A; -Menu::Menu(){} - Menu::Menu(vf2d pos,vf2d size) :pos(pos==CENTERED?WINDOW_SIZE/2-size/2:vi2d{pos}),size(size){ r.Create(size.x,size.y); @@ -25,19 +23,28 @@ Menu::Menu(vf2d pos,vf2d size) void Menu::InitializeMenus(){ stack.reserve(32); - menus[TEST]=InitializeTestMenu(); - menus[TEST_2]=InitializeTestSubMenu(); - menus[INVENTORY]=InitializeInventoryWindow(); + 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); @@ -59,13 +66,16 @@ void Menu::CheckClickAndPerformMenuSelect(Crawler*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{ + 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); + }else{ + CheckClickAndPerformMenuSelect(game); + } } void Menu::MenuSelect(Crawler*game){ @@ -110,8 +120,8 @@ void Menu::Update(Crawler*game){ selection.y=key.first; selection.x=index; } - index++; } + index++; } } } @@ -161,6 +171,11 @@ void Menu::Draw(Crawler*game){ 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){ @@ -168,15 +183,15 @@ void Menu::Draw(Crawler*game){ } } } - for(auto&component:displayComponents){ - if(component->renderInMain){ - component->_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){ @@ -184,11 +199,6 @@ void Menu::Draw(Crawler*game){ } } } - for(auto&component:displayComponents){ - if(component->renderInMain){ - component->_DrawDecal(game,pos,this==Menu::stack.back()); - } - } if(GetCurrentTheme().IsScaled()){ DrawScaledWindowBorder(game,pos); @@ -364,7 +374,9 @@ void Menu::KeyboardButtonNavigation(Crawler*game,vf2d menuPos){ } } if(prevSelection!=selection){ - if(selection!=vi2d{-1,-1}&&buttons[selection.y][selection.x]->disabled)selection=prevSelection; + 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. } } diff --git a/Crawler/Menu.h b/Crawler/Menu.h index 60480180..91431d9d 100644 --- a/Crawler/Menu.h +++ b/Crawler/Menu.h @@ -34,8 +34,9 @@ class Menu:IAttributable{ MenuComponent*draggingComponent=nullptr; Renderable r,overlay; public: - Menu(); - Menu(vf2d pos,vf2d size); + //The constructor is private. Use CreateMenu() instead! + Menu()=default; + virtual ~Menu()=default; void AddComponent(std::string key,MenuComponent*button); void Update(Crawler*game); void Draw(Crawler*game); @@ -51,12 +52,15 @@ public: vf2d size; //Size in tiles (24x24), every menu will be tile-based private: + Menu(vf2d pos,vf2d size); void HoverMenuSelect(Crawler*game); void MenuSelect(Crawler*game); void CheckClickAndPerformMenuSelect(Crawler*game); - static Menu*InitializeTestMenu(); - static Menu*InitializeTestSubMenu(); - static Menu*InitializeInventoryWindow(); + //Mandatory before any menu operations! This creates and sets up the menu in memory. + static Menu*CreateMenu(MenuType type,vf2d pos,vf2d size); + static void InitializeTestMenu(); + static void InitializeTestSubMenu(); + static void InitializeInventoryWindow(); //X (0-3), Y (0-2) for specific 9-patch tile (tiled version). static Renderable&GetPatchPart(int x,int y); diff --git a/Crawler/MenuComponent.cpp b/Crawler/MenuComponent.cpp index 08e3f8f8..6c6a3044 100644 --- a/Crawler/MenuComponent.cpp +++ b/Crawler/MenuComponent.cpp @@ -7,6 +7,8 @@ MenuComponent::MenuComponent(MenuType parent,geom2d::rectrect,std::string MenuComponent::MenuComponent(MenuType parent,geom2d::rectrect,std::string label,MenuType menuDest,MenuFunc onClick,bool selectable) :parentMenu(parent),rect(rect),label(label),menuDest(menuDest),onClick(onClick),hoverEffect(0),selectable(selectable){} +void MenuComponent::AfterCreate(){} + void MenuComponent::Update(Crawler*game){ if(hovered){ hoverEffect=std::min("ThemeGlobal.HighlightTime"_F,hoverEffect+game->GetElapsedTime()); diff --git a/Crawler/MenuComponent.h b/Crawler/MenuComponent.h index b262fe0a..72c8daed 100644 --- a/Crawler/MenuComponent.h +++ b/Crawler/MenuComponent.h @@ -27,6 +27,7 @@ protected: virtual void Draw(Crawler*game,vf2d parentPos,bool focused); virtual void DrawDecal(Crawler*game,vf2d parentPos,bool focused); virtual bool GetHoverState(Crawler*game,MenuComponent*child); + virtual void AfterCreate(); //Called after the creation of all menus finish. public: MenuComponent(MenuType parent,geom2d::rectrect,std::string label,MenuFunc onClick,bool selectable=true); MenuComponent(MenuType parent,geom2d::rectrect,std::string label,MenuType menuDest,MenuFunc onClick,bool selectable=true); diff --git a/Crawler/ScrollableWindowComponent.h b/Crawler/ScrollableWindowComponent.h index aa788b23..dfd0b2f9 100644 --- a/Crawler/ScrollableWindowComponent.h +++ b/Crawler/ScrollableWindowComponent.h @@ -9,6 +9,8 @@ class ScrollableWindowComponent:public MenuComponent{ protected: Renderable r; std::vectorcomponents; + MenuComponent*upButton=nullptr; + MenuComponent*downButton=nullptr; geom2d::rectbounds; //It's for the scrollbar. private: inline bool OnScreen(MenuComponent*component){ @@ -19,6 +21,17 @@ public: :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){std::cout<<"Clicked"<AddComponent("scrollableWindowButton"+upButton->rect.pos.str()+"_"+upButton->rect.size.str(),upButton); + Menu::menus[parentMenu]->AddComponent("scrollableWindowButton"+downButton->rect.pos.str()+"_"+downButton->rect.size.str(),downButton); + } protected: virtual inline void Update(Crawler*game)override{ MenuComponent::Update(game); @@ -42,20 +55,23 @@ protected: game->SetDrawTarget(r.Sprite()); game->Clear(BLANK); for(MenuComponent*component:components){ - component->_Draw(game,V(A::SCROLL_OFFSET),focused); + component->_Draw(game,rect.pos+V(A::SCROLL_OFFSET),focused); } game->SetDrawTarget(prevDrawTarget); game->DrawSprite(parentPos,r.Sprite()); } virtual inline void DrawDecal(Crawler*game,vf2d parentPos,bool focused)override{ MenuComponent::DrawDecal(game,parentPos,focused); - game->DrawRectDecal(parentPos,rect.size); + game->DrawRectDecal(rect.pos+parentPos,rect.size); for(MenuComponent*component:components){ - component->_DrawDecal(game,parentPos+V(A::SCROLL_OFFSET),focused); + component->_DrawDecal(game,rect.pos+parentPos+V(A::SCROLL_OFFSET),focused); } + game->DrawRectDecal(rect.pos+parentPos+vf2d{rect.size.x-12,0},{12,12}); + game->DrawRectDecal(rect.pos+parentPos+vf2d{rect.size.x-12,12},{12,rect.size.y-24}); + game->DrawRectDecal(rect.pos+parentPos+rect.size-vf2d{12,12},{12,12}); } virtual bool GetHoverState(Crawler*game,MenuComponent*child)override{ - return geom2d::overlaps(geom2d::rect{Menu::menus[parentMenu]->pos+child->rect.pos+V(A::SCROLL_OFFSET),child->rect.size},game->GetMousePos()); + return geom2d::overlaps(geom2d::rect{Menu::menus[parentMenu]->pos+rect.pos+child->rect.pos+V(A::SCROLL_OFFSET),child->rect.size},game->GetMousePos()); } public: void inline AddComponent(Menu*parentMenu,std::string key,MenuComponent*button){ diff --git a/Crawler/TestMenu.cpp b/Crawler/TestMenu.cpp index c49b31e4..81859fa9 100644 --- a/Crawler/TestMenu.cpp +++ b/Crawler/TestMenu.cpp @@ -1,8 +1,8 @@ #include "Crawler.h" #include "MenuComponent.h" -Menu*Menu::InitializeTestMenu(){ - Menu*testMenu=new Menu(CENTERED,{24*8,24*6}); +void Menu::InitializeTestMenu(){ + Menu*testMenu=CreateMenu(TEST,CENTERED,{24*8,24*6}); MenuFunc quitWindow=[](MenuFuncData data){ data.menu.stack.clear(); @@ -22,5 +22,4 @@ Menu*Menu::InitializeTestMenu(){ testMenu->AddComponent("Open SubMenu",new MenuComponent(TEST,{{24*2,24*4.5},{24*4,24*1}},"Open Another\n Menu",TEST_2,doNothing)); - return testMenu; } \ No newline at end of file diff --git a/Crawler/TestSubMenu.cpp b/Crawler/TestSubMenu.cpp index 51dc8d56..a1c926bf 100644 --- a/Crawler/TestSubMenu.cpp +++ b/Crawler/TestSubMenu.cpp @@ -8,8 +8,8 @@ INCLUDE_GFX typedef Attribute A; -Menu*Menu::InitializeTestSubMenu(){ - Menu*testSubMenu=new Menu({30,30},{24*4,24*5}); +void Menu::InitializeTestSubMenu(){ + Menu*testSubMenu=CreateMenu(TEST_2,{30,30},{24*4,24*5}); MenuFunc goBack=[](MenuFuncData data){ data.menu.stack.pop_back(); @@ -61,6 +61,4 @@ Menu*Menu::InitializeTestSubMenu(){ }; testSubMenu->AddComponent("NEXT_THEME",new MenuComponent(TEST_2,{{24*3.5,24*3},{24*1,24*1}},">",themeNext)); - - return testSubMenu; } \ No newline at end of file diff --git a/Crawler/Version.h b/Crawler/Version.h index d4125829..bfb39e87 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 2026 +#define VERSION_BUILD 2066 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Crawler/assets/config/configuration.txt b/Crawler/assets/config/configuration.txt index ed49527d..38be4c1c 100644 --- a/Crawler/assets/config/configuration.txt +++ b/Crawler/assets/config/configuration.txt @@ -55,7 +55,7 @@ debug_access_options = 0 debug_map_load_info = 0 # Shows extra info about the player on the HUD -debug_player_info = 0 +debug_player_info = 1 # Shows collision boxes of tiles. debug_collision_boxes = 0 diff --git a/Crawler/assets/knight_full3.png b/Crawler/assets/knight_full3.png index 6cb9af393c40b5a8ff24a4eccf81dda0f5579943..66af0a1d12cae8e278313fafbccd5e315123b0d6 100644 GIT binary patch delta 2717 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 X<1zJ4>J5cv00000NkvXXu0mjfrZVRa delta 2665 zcmV-v3YPV=GSe`SBmwP_B`JRw7>`39mAXO4uLGuODpl%9IQHR~<4F7SaZk4cR4GZ; zTC5ash>212q{6_`BZBnUA#)F}eA2l=PO*P^lt#ht&U=ngTlC}XpMz$C&9i)Nv=;nw z<(C*p^?Oh#U>qmI7C*!wrcflKUPDo_s+X&6D_l{}C9G6(K^2cepjuJP^GYD~pBr#J@W)`#X9AEeF@%1jkv%Js!IR=!1$pD{79Amm+e-UpG z&u&^e=Y8T3D@zLTIq{T17bJe| zFXUWSIB#)Qt991CCx2nEpsi%MPIDXyEFp;$M98S4f+{RTXw^tDk*58)hkwlRr^qFf zs{%%j1=OHIa{SV`644VKy`~EnzZcI5#t4Vq`crW|LY4tO__VH8?akIW;pdlMn`W z1u!r=H8+!s1}OzFFgi6iv$+Nh0kfqDUu205GMs-Wj{rg_+@3rF zgb>(2Jn%OfP_sbVy=EXxu)M+9Y&ypaPh3HBCD`M$=p-vh!& z;F>Q10HE9WF$6=MbZUQqQ~N_0j%*&9*ITUxptu~=ZnuN--a3B);(UZkw+rpvpHlq} z6+KZYZ#YH+10f_*>ihkEBtgf?Xr0WshY$j$X#xO%<2X_mv?~cQ%d()?>qVSoSr!P zGx$(qpQDiYkh6c9FNN>hIQ>1+OtO$<15)ZB9!^{pLkLCO)EnMLz)(1Os;tUAA~?Kz zcu>)hvm%p4-Z_K8Ao6;(Uh`$G&6IZ1^x)#4@W6N&6qsn$&gUM{!dM2Rl`b_Fg`{XM z&4ZyX5&@&lV-`^5ybn6fzP(;=p<@65?DLC6OPoSFNEClg%fJ)4gau-sUqGkVjqKO8 zI(`;V<)XSr6h=H05fEOTUKd=~1^fKMPXkFhptuT_w3;mpp+*xrZ8PycuImP4lGEc< zkaDSFS>(Dd3Ce&yKVbdRo-7*K9?RYcm!la<21DbZRiMnXnV!?IVW)NCaFhRE& z0B&I;v73LCEj5yyPF@6yh6i=fqMQ@wEUFeZX2LZIAZ49&n#vZZByji;>-GmTK-Om5 zkz^t1y6%j7RH$`&96Q8YSsz5CGZ|UuGO{KH%T`KhfcOOMjhyhqi>~?U_xrJKm=_`{*K*2p&~Pw> zrrCdluGN{z5A(u8^cu!kSqEK+KB!@u&@`J70HOpf0^~T(^F^E95NeuD7!HOo8VrDpNu&`>8Zt8GNV4>UjPgDHL}2`hz^Cy8e0}-~m7fu`+ikeH zxq+LTo0+v&k{q(Pw+DNBdr&Ty!Tra9U(A1B6MwFUhlgi3kud-O?4nAl3YOYIajRf@ zjiJ%N3zrz{qEuDvaWW~fn+TLQazc>HxJNXWph;hbmT`~NGCwBwFedkj-KIkG+Jlu1 z%yzc7eHM%eQd;fq?kaK7N*YC~3aUX|=oP^cxdP(dW4pJJT$L6K4@ZWJmd!r^8P9)e zuz+m$7LJ={><8czby|1Conq$SPc&{#1E?&^J!!*+4#%=AC{?N?E41dhhjkFfSO_KQ z3fysm7J_$CARYl^nloS;rkNig8B%}Q03rhc z@rV>CO9PUXoP{Nwn>nKj0+_n32m`m3C26+#BV!WIu-Q<{^IuR%3o)7Q5#=8WERB0; znC0rAtjT$vdqfAb44muBs}jEqK?a&}0VrV&z6O?)he}r5e#Lj(WuDI7a2VGxYmDojT9*rUmHGWYLw>CLQm3#Oy z&*r4GVU`OZk&9I2rN{s+i)m Xj|KaBh=BNx00000NkvXXu0mjfn4j4J diff --git a/Crawler/assets/knight_full_render1.png b/Crawler/assets/knight_full_render1.png new file mode 100644 index 0000000000000000000000000000000000000000..04f620e19ee7108615a98fd6d810d28ace5192e5 GIT binary patch literal 6594 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 a2g262{p!vOjz#dEjG%jXyPt9mPW%r65(c;c literal 0 HcmV?d00001