#pragma once #include "Menu.h" #include "MenuComponent.h" #include "MenuItemButton.h" #include "Crawler.h" typedef Attribute A; class ScrollableWindowComponent:public MenuComponent{ protected: Renderable r; std::vectorcomponents; MenuComponent*upButton=nullptr; MenuComponent*downButton=nullptr; geom2d::rectbounds; //It's for the scrollbar. float scrollBarHeight=0; float scrollBarTop=0; bool scrollBarSelected=false; float scrollBarHoverTime=0; protected: inline bool OnScreen(MenuComponent*component){ return geom2d::overlaps(geom2d::rect{{},rect.size},geom2d::rect{component->rect.pos+V(A::SCROLL_OFFSET)+vf2d{2,2},component->rect.size-vf2d{2,2}}); } public: inline ScrollableWindowComponent(MenuType parent,geom2d::rectrect,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE) :MenuComponent(parent,rect,"",[](MenuFuncData data){return true;},ButtonAttr::UNSELECTABLE|ButtonAttr::UNSELECTABLE_VIA_KEYBOARD){ background=attributes&ComponentAttr::BACKGROUND; border=attributes&ComponentAttr::OUTLINE; r.Create(rect.size.x,rect.size.y); } protected: virtual inline void AfterCreate()override{ upButton=NEW MenuComponent(parentMenu,{rect.pos+vf2d{rect.size.x-12,0},{12,12}},"^",[&](MenuFuncData dat){V(A::SCROLL_OFFSET).y+="ThemeGlobal.MenuButtonScrollSpeed"_I;return true;},ButtonAttr::UNSELECTABLE_VIA_KEYBOARD); downButton=NEW MenuComponent(parentMenu,{rect.pos+rect.size-vf2d{12,12},{12,12}},"v",[&](MenuFuncData dat){V(A::SCROLL_OFFSET).y-="ThemeGlobal.MenuButtonScrollSpeed"_I;return true;},ButtonAttr::UNSELECTABLE_VIA_KEYBOARD); //Let's use the internal name of this component to add unique names for sub-components. Menu::menus[parentMenu]->AddComponent(name+upButton->rect.pos.str()+"_"+upButton->rect.size.str(),upButton); Menu::menus[parentMenu]->AddComponent(name+downButton->rect.pos.str()+"_"+downButton->rect.size.str(),downButton); } protected: virtual inline void Update(Crawler*game)override{ MenuComponent::Update(game); vf2d windowAbsPos=Menu::menus[parentMenu]->pos+rect.pos; bool mouseOverScrollbar=geom2d::overlaps(geom2d::rect(windowAbsPos+vf2d{rect.size.x-12,scrollBarTop+12},{12,scrollBarHeight}),game->GetMousePos()); if(mouseOverScrollbar||scrollBarSelected){ scrollBarHoverTime=std::min(scrollBarHoverTime+game->GetElapsedTime(),"ThemeGlobal.HighlightTime"_F); if(game->GetMouse(0).bPressed){ scrollBarSelected=true; } if(game->GetMouse(0).bReleased){ scrollBarSelected=false; } if(scrollBarSelected){ float spaceBetweenTopAndBottomArrows=rect.size.y-24; float viewHeight=rect.size.y; float totalContentHeight=bounds.size.y; if(totalContentHeight==0)totalContentHeight=1; float scrollBarScale=(spaceBetweenTopAndBottomArrows/totalContentHeight); //The scroll amount moves centered on the position the mouse is at. float newScrollbarTop=(game->GetMousePos().y-windowAbsPos.y-12)-scrollBarHeight/2; V(A::SCROLL_OFFSET).y=(-newScrollbarTop+1)/scrollBarScale; //Derived formula from the draw code. } }else{ scrollBarHoverTime=std::max(scrollBarHoverTime-game->GetElapsedTime(),0.f); } if(game->GetMouseWheel()!=0){ if(game->GetMouseWheel()>0){ V(A::SCROLL_OFFSET).y+="ThemeGlobal.MenuScrollWheelSpeed"_I; }else{ V(A::SCROLL_OFFSET).y-="ThemeGlobal.MenuScrollWheelSpeed"_I; } } if(bounds.size.y-rect.size.y>0){ V(A::SCROLL_OFFSET).y=std::clamp(V(A::SCROLL_OFFSET).y,-(bounds.size.y-rect.size.y),0.f); }else{ V(A::SCROLL_OFFSET).y=0; } for(MenuComponent*component:components){ component->disabled=!OnScreen(component); component->_Update(game); } upButton->disabled=false; downButton->disabled=false; if(geom2d::contains(rect,bounds)){//This means we have no reason to show a scrollbar. upButton->disabled=true; downButton->disabled=true; } } virtual inline void Draw(Crawler*game,vf2d parentPos)override{ MenuComponent::Draw(game,parentPos); Sprite*prevDrawTarget=game->GetDrawTarget(); game->SetDrawTarget(r.Sprite()); game->Clear(BLANK); for(MenuComponent*component:components){ component->_Draw(game,V(A::SCROLL_OFFSET)); } game->SetDrawTarget(prevDrawTarget); game->DrawSprite(parentPos+rect.pos,r.Sprite()); } inline void DrawScrollbar(Crawler*game,vf2d parentPos,bool focused){ float spaceBetweenTopAndBottomArrows=rect.size.y-24; float viewHeight=rect.size.y; float totalContentHeight=bounds.size.y; if(totalContentHeight==0)totalContentHeight=1; float scrollBarScale=(spaceBetweenTopAndBottomArrows/totalContentHeight); scrollBarHeight=std::min(spaceBetweenTopAndBottomArrows,viewHeight*scrollBarScale-1); scrollBarTop=-V(A::SCROLL_OFFSET).y*scrollBarScale+1; float focusedWindowColorMult=(focused?1:"ThemeGlobal.MenuUnfocusedColorMult"_F); game->FillRectDecal(rect.pos+parentPos+vf2d{rect.size.x-11.75f,scrollBarTop+12},{12,scrollBarHeight},PixelLerp(Menu::GetCurrentTheme().GetButtonCol(),Menu::GetCurrentTheme().GetHighlightCol(),scrollBarHoverTime/"ThemeGlobal.HighlightTime"_F)*focusedWindowColorMult); game->DrawRectDecal(rect.pos+parentPos+vf2d{rect.size.x-11.75f,scrollBarTop+12},{12,scrollBarHeight},WHITE*focusedWindowColorMult); } virtual inline void DrawDecal(Crawler*game,vf2d parentPos,bool focused)override{ MenuComponent::DrawDecal(game,parentPos,focused); if(border){ game->DrawRectDecal(rect.pos+Menu::menus[parentMenu]->pos,rect.size); } for(MenuComponent*component:components){ component->_DrawDecal(game,rect.pos+Menu::menus[parentMenu]->pos+V(A::SCROLL_OFFSET),focused); } DrawScrollbar(game,Menu::menus[parentMenu]->pos,focused); } virtual bool GetHoverState(Crawler*game,MenuComponent*child)override{ return geom2d::overlaps(geom2d::rect{Menu::menus[parentMenu]->pos+rect.pos+child->rect.pos+V(A::SCROLL_OFFSET),child->rect.size},game->GetMousePos()); } //Calculates the bounds of all components. geom2d::rect inline CalculateBounds(){ geom2d::rectbounds; for(MenuComponent*component:components){ if(component->rect.pos.xrect.pos.x; bounds.size.x+=sizeIncrease; bounds.pos.x=component->rect.pos.x; } if(component->rect.right().start.x>bounds.right().start.x){ float sizeIncrease=component->rect.right().start.x-bounds.right().start.x; bounds.size.x+=sizeIncrease; } if(component->rect.pos.yrect.pos.y; bounds.size.y+=sizeIncrease; bounds.pos.y=component->rect.pos.y; } if(component->rect.bottom().start.y>bounds.bottom().start.y){ float sizeIncrease=component->rect.bottom().start.y-bounds.bottom().start.y; bounds.size.y+=sizeIncrease; } } return bounds; } public: void inline AddComponent(Menu*parentMenu,std::string key,MenuComponent*button){ components.push_back(button); button->renderInMain=false; //Now we are in control! button->parentComponent=this; if(button->rect.pos.xrect.pos.x; bounds.size.x+=sizeIncrease; bounds.pos.x=button->rect.pos.x; } if(button->rect.right().start.x>bounds.right().start.x){ float sizeIncrease=button->rect.right().start.x-bounds.right().start.x; bounds.size.x+=sizeIncrease; } if(button->rect.pos.yrect.pos.y; bounds.size.y+=sizeIncrease; bounds.pos.y=button->rect.pos.y; } if(button->rect.bottom().start.y>bounds.bottom().start.y){ float sizeIncrease=button->rect.bottom().start.y-bounds.bottom().start.y; bounds.size.y+=sizeIncrease; } parentMenu->AddComponent(key,button); } virtual inline bool PointWithinParent(MenuComponent*child,vi2d drawPos)override{ return geom2d::overlaps(geom2d::rect{Menu::menus[parentMenu]->pos+rect.pos,rect.size},drawPos); } virtual inline bool HandleOutsideDisabledButtonSelection(MenuComponent*disabledButton)override{ //Set the offset so the center is highlighted by this button. V(A::SCROLL_OFFSET).y=-(disabledButton->rect.pos.y-disabledButton->rect.size.y/2); V(A::SCROLL_OFFSET).y+=rect.size.y/2; return true; }; virtual void Cleanup()override{} };