Add in support for subcomponents within subcomponents. Prioritize highest depth menu item when hovering over overlapping items.

pull/35/head
sigonasr2 9 months ago
parent 59eb24b65b
commit 5fc5169ddd
  1. 62
      Adventures in Lestoria/AccessoryRowItemDisplay.h
  2. 1
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  3. 3
      Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
  4. 11
      Adventures in Lestoria/InventoryCreator.cpp
  5. 36
      Adventures in Lestoria/Menu.cpp
  6. 8
      Adventures in Lestoria/Menu.h
  7. 94
      Adventures in Lestoria/ScrollableWindowComponent.h
  8. 2
      Adventures in Lestoria/Version.h
  9. BIN
      x64/Release/Adventures in Lestoria.exe

@ -0,0 +1,62 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.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 © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "RowItemDisplay.h"
INCLUDE_game
INCLUDE_GFX
class AccessoryRowItemDisplay:public RowItemDisplay{
std::weak_ptr<MenuComponent>lockButton;
public:
inline AccessoryRowItemDisplay(geom2d::rect<float>rect,const std::weak_ptr<Item>itemRef,MenuFunc onClick,std::string itemNameLabelName,std::string itemDescriptionLabelName,ButtonAttr attributes=ButtonAttr::NONE)
:RowItemDisplay(rect,itemRef,onClick,itemNameLabelName,itemDescriptionLabelName,attributes){}
inline ~AccessoryRowItemDisplay(){
if(!lockButton.expired()){
size_t subcomponentCount=parentComponent.lock()->GetSubcomponentCount();
parentComponent.lock()->RemoveButton(lockButton);
if(parentComponent.lock()->GetSubcomponentCount()>=subcomponentCount)ERR(std::format("WARNING! The subcomponent count went from {} -> {} when trying to remove the lock button for item {}! THIS SHOULD NOT BE HAPPENING!",subcomponentCount,parentComponent.lock()->GetSubcomponentCount(),name));
lockButton.reset();
}
}
virtual inline void AfterCreate()override final{
lockButton=parentComponent.lock()->ADDSUB(std::format("{}_lock",name),MenuComponent)(geom2d::rect<float>{rect.pos,vf2d{16.f,8.f}},"Lock",[](MenuFuncData data){
return true;
})END;
}
};

@ -273,6 +273,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Ability.h" />
<ClInclude Include="AccessoryRowItemDisplay.h" />
<ClInclude Include="Animation.h" />
<ClInclude Include="Attributable.h" />
<ClInclude Include="AttributableStat.h">

@ -477,6 +477,9 @@
<ClInclude Include="FloatingMenuComponent.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="AccessoryRowItemDisplay.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Player.cpp">

@ -37,6 +37,7 @@ All rights reserved.
#pragma endregion
#include "InventoryCreator.h"
#include "RowInventoryScrollableWindowComponent.h"
#include "AccessoryRowItemDisplay.h"
#define DEFINE(SubName) InventoryCreator InventoryCreator::SubName##_InventoryUpdate(&InventoryCreator::SubName##_InventorySlotsUpdate,&InventoryCreator::SubName##_AddButtonOnSlotUpdate);
@ -76,7 +77,7 @@ std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)>
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::RowPlayer_AddButtonOnSlotUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
RowInventoryScrollableWindowComponent*c=DYNAMIC_CAST<RowInventoryScrollableWindowComponent*>(&component);
size_t invSize=c->components.size()+1;
size_t invSize=c->GetMainComponentCount()+1; //Remove any subcomponents as they get in the way of the actual amount of items in the list.
int invWidth=int(c->rect.size.x/(float(c->options.size.x)+c->options.padding));
int x=int((invSize-1)%invWidth);
int y=int((invSize-1)/invWidth);
@ -84,8 +85,12 @@ std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)>
vf2d buttonSize=c->options.size;
vf2d totalSpacing={c->options.padding+buttonSize.x,c->options.padding+buttonSize.y};
auto newItem=c->ADD("item_"+cat+"_"+std::to_string(itemIndex),RowItemDisplay)(geom2d::rect<float>{totalSpacing*vf2d{float(x),float(y)},buttonSize},Inventory::GetInventorySlot(cat,itemIndex),c->inventoryButtonClickAction,c->itemNameLabelName,c->itemDescriptionLabelName,c->inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
std::shared_ptr<RowItemDisplay>newItem;
if(cat=="Accessories"){
newItem=c->ADD("item_"+cat+"_"+std::to_string(itemIndex),AccessoryRowItemDisplay)(geom2d::rect<float>{totalSpacing*vf2d{float(x),float(y)},buttonSize},Inventory::GetInventorySlot(cat,itemIndex),c->inventoryButtonClickAction,c->itemNameLabelName,c->itemDescriptionLabelName,c->inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
}else{
newItem=c->ADD("item_"+cat+"_"+std::to_string(itemIndex),RowItemDisplay)(geom2d::rect<float>{totalSpacing*vf2d{float(x),float(y)},buttonSize},Inventory::GetInventorySlot(cat,itemIndex),c->inventoryButtonClickAction,c->itemNameLabelName,c->itemDescriptionLabelName,c->inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
}
newItem->SetCompactDescriptions(c->compact);
newItem->SetPriceLabelType(c->priceLabel);
newItem->SetHoverFunc(c->inventoryButtonHoverAction);

@ -167,6 +167,7 @@ void Menu::MenuSelect(AiL*game){
}
void Menu::Update(AiL*game){
if(!draggingComponent){
HoverMenuSelect(game);
}
@ -201,15 +202,19 @@ void Menu::Update(AiL*game){
}
}else{
selection={};
for(auto&[key,component]:components){
if(component->selectable){
if(!component->disable&&!component->disableOutsideWindow&&!component->grayedOut){
if(component->GetHoverState(game)){
if(!Menu::lastHover.expired()&&&*component!=&*Menu::lastHover.lock())SoundEffect::PlaySFX("Menu Navigate",SoundEffect::CENTERED);
Menu::lastHover=component;
component->hovered=true;
std::vector<std::weak_ptr<MenuComponent>>allComponents;
std::for_each(components.begin(),components.end(),[&](auto&pair){allComponents.push_back(pair.second);});
std::sort(allComponents.begin(),allComponents.end(),[](std::weak_ptr<MenuComponent>c1,std::weak_ptr<MenuComponent>c2){return c1.lock()->depth<c2.lock()->depth;});
for(const auto&component:allComponents){
if(component.lock()->selectable){
if(!component.lock()->disable&&!component.lock()->disableOutsideWindow&&!component.lock()->grayedOut){
if(component.lock()->GetHoverState(game)){
if(!Menu::lastHover.expired()&&&*component.lock()!=&*Menu::lastHover.lock())SoundEffect::PlaySFX("Menu Navigate",SoundEffect::CENTERED);
Menu::lastHover=component.lock();
component.lock()->hovered=true;
itemHovered=true;
SetSelection(std::weak_ptr<MenuComponent>(component));
SetSelection(std::weak_ptr<MenuComponent>(component.lock()));
break;
}
}
}
@ -269,9 +274,6 @@ void Menu::Draw(AiL*game){
DrawTiledWindowBackground(game,pos,size,GetRenderColor());
}
std::vector<std::weak_ptr<MenuComponent>>allComponents;
std::for_each(components.begin(),components.end(),[&](auto&pair){allComponents.push_back(pair.second);});
std::sort(allComponents.begin(),allComponents.end(),[](std::weak_ptr<MenuComponent>c1,std::weak_ptr<MenuComponent>c2){return c1.lock()->depth>c2.lock()->depth;});
if(GetCurrentTheme().IsScaled()){
DrawScaledWindowBorder(game,pos,size,GetRenderColor());
@ -279,6 +281,9 @@ void Menu::Draw(AiL*game){
DrawTiledWindowBorder(game,pos,size,GetRenderColor());
}
std::vector<std::weak_ptr<MenuComponent>>allComponents;
std::for_each(components.begin(),components.end(),[&](auto&pair){allComponents.push_back(pair.second);});
std::sort(allComponents.begin(),allComponents.end(),[](std::weak_ptr<MenuComponent>c1,std::weak_ptr<MenuComponent>c2){return c1.lock()->depth>c2.lock()->depth;});
for(const auto&component:allComponents){
if(!component.expired()&&component.lock()->renderInMain){
component.lock()->_DrawDecal(window,this==Menu::stack.back());
@ -688,11 +693,6 @@ void Menu::DrawThemedWindow(vf2d menuPos,vf2d size,Pixel renderColor){
}
}
void Menu::RecalculateComponentCount(){
componentCount=components.size();
}
const MenuType Menu::GetType()const{
return type;
}
@ -793,3 +793,7 @@ bool Menu::IsCurrentlyActive(MenuType type){
void Menu::ReInitializeInputGroup(){
helpDisplay.Initialize(inputGroups);
}
const bool Menu::GameInitialized()const{
return game->GameInitialized();
}

@ -120,7 +120,6 @@ class Menu:public IAttributable{
float buttonHoldTime=0;
static vi2d lastActiveMousePos;
int componentCount=0;
float componentSelectionIndex=0.f;
//This variable is an override available to all menus that allows us to say we've clicked something, so do not handle any automatic clicking anymore of menus for this frame!
static bool alreadyClicked;
@ -140,7 +139,7 @@ public:
std::shared_ptr<T>_AddComponent(std::string componentKey,std::shared_ptr<T>component,int depth=DEFAULT_DEPTH){
component->parentMenu=type;
if(depth==DEFAULT_DEPTH){
component->depth=STARTING_DEPTH-componentCount;
component->depth=STARTING_DEPTH-components.size();
}else{
component->depth=depth;
}
@ -151,7 +150,7 @@ public:
components.SetInitialized();
lastRegisteredComponent=componentKey;
RecalculateComponentCount();
if(GameInitialized())component->AfterCreate();
return component;
}
@ -195,7 +194,6 @@ public:
//X (0-2), Y (0-2) for specific 9-patch tile (tiled version).
static Renderable&GetPatchPart(int x,int y);
void RecalculateComponentCount();
void SetSelection(std::string_view button,const bool scroll=true,const bool reset=false); // Use the reset parameter when a window is opening up, as this will cause the window now to scroll to its previous target.
void SetSelection(std::weak_ptr<MenuComponent>button,const bool scroll=true,const bool reset=false); // Use the reset parameter when a window is opening up, as this will cause the window now to scroll to its previous target.
void SetSelection(Data button,const bool reset=false); // Use the reset parameter when a window is opening up, as this will cause the window now to scroll to its previous target.
@ -224,6 +222,8 @@ private:
//This triggers if we use a keyboard/controller input to try and select some off-screen menu item. We should ideally follow the menu cursor.
bool HandleOutsideDisabledButtonSelection(std::weak_ptr<MenuComponent>disabledButton);
const bool GameInitialized()const;
Pixel GetRenderColor();
MenuType type;
MenuInputGroups inputGroups;

@ -42,6 +42,8 @@ All rights reserved.
using A=Attribute;
#define ADDSUB(key,componentType) _AddSubcomponent<componentType>(key,std::make_shared<componentType>
INCLUDE_game
class ScrollableWindowComponent:public MenuComponent{
@ -52,6 +54,7 @@ protected:
std::weak_ptr<MenuComponent>upButton;
std::weak_ptr<MenuComponent>downButton;
geom2d::rect<float>bounds; //It's for the scrollbar.
std::vector<std::weak_ptr<MenuComponent>>subcomponents; //Subcomponents are anything that is created internally by another component.
float scrollBarHeight=0;
float scrollBarTop=0;
bool scrollBarSelected=false;
@ -74,11 +77,26 @@ public:
while(components.size()>0){
RemoveButton(components.back());
}
while(subcomponents.size()>0){
std::weak_ptr<MenuComponent>button=subcomponents.back();
std::string componentName=button.lock()->GetName();
subcomponents.erase(subcomponents.end()-1);
size_t removedCount=0;
MenuType parentMenu=button.lock()->parentMenu;
removedCount+=Menu::menus[parentMenu]->components.erase(componentName);
if(removedCount!=1){
std::cout<<"WARNING! Attempted to remove subbuttons from button listing, but not found!";
}
}
}
virtual inline void RemoveButton(std::weak_ptr<MenuComponent>button){
auto componentSearchResults=std::find_if(components.begin(),components.end(),[&](std::weak_ptr<MenuComponent>ptr){return &*ptr.lock()==&*button.lock();});
if(componentSearchResults==components.end())ERR("Could not find Component"<<std::quoted(button.lock()->GetName())<<" inside the component list!");
components.erase(componentSearchResults);
auto subcomponentSearchResults=std::find_if(subcomponents.begin(),subcomponents.end(),[&](std::weak_ptr<MenuComponent>ptr){return &*ptr.lock()==&*button.lock();});
if(componentSearchResults==components.end()&&subcomponentSearchResults==subcomponents.end())ERR("Could not find Component "<<std::quoted(button.lock()->GetName())<<" inside the component or subcomponent lists!");
if(componentSearchResults!=components.end())components.erase(componentSearchResults);
if(subcomponentSearchResults!=subcomponents.end())subcomponents.erase(subcomponentSearchResults);
size_t removedCount=0;
MenuType parentMenu=button.lock()->parentMenu;
@ -87,9 +105,14 @@ public:
if(removedCount!=1){
std::cout<<"WARNING! Attempted to remove buttons from button listing, but not found!";
}
Menu::menus[parentMenu]->RecalculateComponentCount();
CalculateBounds();
}
virtual inline size_t GetMainComponentCount()const{
return components.size();
}
virtual inline size_t GetSubcomponentCount()const{
return subcomponents.size();
}
virtual inline void SetScrollAmount(vf2d scrollOffset){
this->targetScrollOffset=scrollOffset;
}
@ -148,6 +171,11 @@ protected:
if(componentPtr->renderInMain)ERR(std::format("WARNING! Component {} is inside a ScrollableWindowComponent but renders in main instead! Parent Component: {}",componentPtr->GetName(),GetName()));
componentPtr->_BeforeUpdate(game);
}
for(const auto&component:subcomponents){
std::shared_ptr<MenuComponent>componentPtr=component.lock();
if(componentPtr->renderInMain)ERR(std::format("WARNING! Subcomponent {} is inside a ScrollableWindowComponent but renders in main instead! Parent Component: {}",componentPtr->GetName(),GetName()));
componentPtr->_BeforeUpdate(game);
}
}
virtual inline void Update(AiL*game)override{
MenuComponent::Update(game);
@ -210,6 +238,15 @@ protected:
}
component.lock()->_Update(game);
}
std::sort(subcomponents.begin(),subcomponents.end(),[](std::weak_ptr<MenuComponent>c1,std::weak_ptr<MenuComponent>c2){return c1.lock()->depth>c2.lock()->depth;});
for(std::weak_ptr<MenuComponent>component:subcomponents){
if(OnScreen(component.lock())){
component.lock()->EnableOutsideWindow();
}else{
component.lock()->DisableOutsideWindow();
}
component.lock()->_Update(game);
}
upButton.lock()->disable=false;
downButton.lock()->disable=false;
@ -238,6 +275,9 @@ protected:
for(std::weak_ptr<MenuComponent>component:components){
component.lock()->rect.pos=component.lock()->originalPos+scrollOffset;
}
for(std::weak_ptr<MenuComponent>component:subcomponents){
component.lock()->rect.pos=component.lock()->originalPos+scrollOffset;
}
lastScrollUpdate=1/60.f;
}
}
@ -270,6 +310,9 @@ protected:
for(std::weak_ptr<MenuComponent>component:components){
component.lock()->_DrawDecal(subWindow,focused);
}
for(std::weak_ptr<MenuComponent>component:subcomponents){
component.lock()->_DrawDecal(subWindow,focused);
}
if(!geom2d::contains(rect,bounds)){
DrawScrollbar(window,{},focused);
}
@ -298,8 +341,44 @@ public:
bounds.size.y+=sizeIncrease;
}
}
for(std::weak_ptr<MenuComponent>component:subcomponents){
if(component.lock()->rect.pos.x<bounds.pos.x){
float sizeIncrease=bounds.pos.x-component.lock()->rect.pos.x;
bounds.size.x+=sizeIncrease;
bounds.pos.x=component.lock()->rect.pos.x;
}
if(component.lock()->rect.right().start.x>bounds.right().start.x){
float sizeIncrease=component.lock()->rect.right().start.x-bounds.right().start.x;
bounds.size.x+=sizeIncrease;
}
if(component.lock()->rect.pos.y<bounds.pos.y){
float sizeIncrease=bounds.pos.y-component.lock()->rect.pos.y;
bounds.size.y+=sizeIncrease;
bounds.pos.y=component.lock()->rect.pos.y;
}
if(component.lock()->rect.bottom().start.y>bounds.bottom().start.y){
float sizeIncrease=component.lock()->rect.bottom().start.y-bounds.bottom().start.y;
bounds.size.y+=sizeIncrease;
}
}
}
public:
template<class T>
std::shared_ptr<T> _AddSubcomponent(std::string key,std::shared_ptr<T>button){
if(Menu::menus[parentMenu]->components.count(key)){
ERR("WARNING! Key "<<key<<" for menu"<<parentMenu<<" already exists! Key names must be unique!")
}
subcomponents.push_back(button);
button->renderInMain=false; //Now we are in control!
button->parentComponent=DYNAMIC_POINTER_CAST<ScrollableWindowComponent>(Menu::menus[parentMenu]->components[this->GetName()]);
button->disable=disable;
CalculateBounds();
Menu::menus[parentMenu]->_AddComponent(key,button);
return button;
}
template<class T>
std::shared_ptr<T> _AddComponent(std::string key,std::shared_ptr<T>button){
if(Menu::menus[parentMenu]->components.count(key)){
@ -331,11 +410,17 @@ public:
inline std::vector<std::weak_ptr<MenuComponent>>&GetComponents(){
return components;
}
inline std::vector<std::weak_ptr<MenuComponent>>&GetSubcomponents(){
return subcomponents;
}
virtual inline void Enable()override final{
MenuComponent::Enable();
for(std::weak_ptr<MenuComponent>component:components){
component.lock()->Enable();
}
for(std::weak_ptr<MenuComponent>component:subcomponents){
component.lock()->Enable();
}
if(upButton.lock()){upButton.lock()->Enable();}
if(downButton.lock()){downButton.lock()->Enable();}
};
@ -344,6 +429,9 @@ public:
for(std::weak_ptr<MenuComponent>component:components){
component.lock()->Disable();
}
for(std::weak_ptr<MenuComponent>component:subcomponents){
component.lock()->Disable();
}
if(upButton.lock()){upButton.lock()->Disable();}
if(downButton.lock()){downButton.lock()->Disable();}
};

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 0
#define VERSION_MINOR 4
#define VERSION_PATCH 4
#define VERSION_BUILD 7910
#define VERSION_BUILD 7947
#define stringify(a) stringify_(a)
#define stringify_(a) #a

Loading…
Cancel
Save