Ajout de Jolt Physics + 1ere version des factory entitecomposants - camera, transform, rigidbody, collider, renderer

This commit is contained in:
Tom Ray
2026-03-22 00:28:03 +01:00
parent 6695d46bcd
commit 48348936a8
1147 changed files with 214331 additions and 353 deletions

View File

@@ -0,0 +1,11 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UIAnimation.h>
JPH_IMPLEMENT_RTTI_VIRTUAL_BASE(UIAnimation)
{
}

View File

@@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/RTTI.h>
class UIElement;
/// Base class for UI element animations
class UIAnimation
{
public:
JPH_DECLARE_RTTI_VIRTUAL_BASE(JPH_NO_EXPORT, UIAnimation)
/// Destructor
virtual ~UIAnimation() = default;
///@name Interface
virtual void Init(UIElement *inElement) { }
virtual bool Update(UIElement *inElement, float inDeltaTime) { return true; } ///< Returns false when done
virtual void Exit(UIElement *inElement) { }
};
using UIAnimationVector = Array<UIAnimation *>;

View File

@@ -0,0 +1,82 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/Renderer.h>
#include <Window/ApplicationWindow.h>
#include <UI/UIAnimationSlide.h>
#include <UI/UIElement.h>
#include <UI/UIManager.h>
JPH_IMPLEMENT_RTTI_ABSTRACT(UIAnimationSlide)
{
JPH_ADD_BASE_CLASS(UIAnimationSlide, UIAnimation)
}
UIAnimationSlide::UIAnimationSlide(EMode inMode, int inSlideDistanceH, int inSlideDistanceV, float inTimeBeforeSlide, float inSlideTime) :
mSlideMode(inMode),
mSlideDistanceH(inSlideDistanceH),
mSlideDistanceV(inSlideDistanceV),
mTimeBeforeSlide(inTimeBeforeSlide),
mSlideTime(inSlideTime)
{
}
void UIAnimationSlide::Init(UIElement *inElement)
{
mTargetRelativeX = inElement->GetRelativeX();
mTargetRelativeY = inElement->GetRelativeY();
ApplicationWindow *window = inElement->GetManager()->GetRenderer()->GetWindow();
int dl = inElement->GetX();
int dr = window->GetWindowWidth() - (inElement->GetX() + inElement->GetWidth());
int dt = inElement->GetY();
int db = window->GetWindowHeight() - (inElement->GetY() + inElement->GetHeight());
if (min(dl, dr) < min(dt, db))
{
mInitialRelativeX = mTargetRelativeX + (dl < dr? -mSlideDistanceH : mSlideDistanceH);
mInitialRelativeY = mTargetRelativeY;
}
else
{
mInitialRelativeX = mTargetRelativeX;
mInitialRelativeY = mTargetRelativeY + (dt < db? -mSlideDistanceV : mSlideDistanceV);
}
if (mSlideMode == SLIDE_ON_SCREEN)
inElement->SetAnimatedVisible(true);
mTime = 0.0f;
}
bool UIAnimationSlide::Update(UIElement *inElement, float inDeltaTime)
{
mTime += inDeltaTime;
float factor = (mTime - mTimeBeforeSlide) / mSlideTime;
if (factor >= 1.0f)
return false;
if (factor < 0.0f)
factor = 0.0f;
if (mSlideMode == SLIDE_OFF_SCREEN)
factor = 1.0f - factor;
float x = mInitialRelativeX * (1.0f - factor) + mTargetRelativeX * factor;
float y = mInitialRelativeY * (1.0f - factor) + mTargetRelativeY * factor;
inElement->SetRelativeX((int)x);
inElement->SetRelativeY((int)y);
return true;
}
void UIAnimationSlide::Exit(UIElement *inElement)
{
inElement->SetRelativeX(mTargetRelativeX);
inElement->SetRelativeY(mTargetRelativeY);
inElement->SetAnimatedVisible(mSlideMode == SLIDE_ON_SCREEN);
}

View File

@@ -0,0 +1,41 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UIAnimation.h>
/// Animation that slides an element on or off screen
class UIAnimationSlide : public UIAnimation
{
public:
JPH_DECLARE_RTTI_ABSTRACT(JPH_NO_EXPORT, UIAnimationSlide)
/// Mode of sliding
enum EMode
{
SLIDE_ON_SCREEN,
SLIDE_OFF_SCREEN,
};
/// Constructor
UIAnimationSlide(EMode inMode, int inSlideDistanceH, int inSlideDistanceV, float inTimeBeforeSlide, float inSlideTime);
///@name Interface
virtual void Init(UIElement *inElement) override;
virtual bool Update(UIElement *inElement, float inDeltaTime) override;
virtual void Exit(UIElement *inElement) override;
private:
EMode mSlideMode;
int mSlideDistanceH;
int mSlideDistanceV;
float mTimeBeforeSlide;
float mSlideTime;
int mInitialRelativeX;
int mInitialRelativeY;
int mTargetRelativeX;
int mTargetRelativeY;
float mTime;
};

View File

@@ -0,0 +1,78 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/Renderer.h>
#include <UI/UIButton.h>
#include <UI/UIManager.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(UIButton)
{
JPH_ADD_BASE_CLASS(UIButton, UITextButton)
}
void UIButton::CopyTo(UIElement *ioElement) const
{
UITextButton::CopyTo(ioElement);
UIButton *element = StaticCast<UIButton>(ioElement);
element->mUpQuad = mUpQuad;
element->mUpColor = mUpColor;
element->mDownQuad = mDownQuad;
element->mDownColor = mDownColor;
element->mSelectedQuad = mSelectedQuad;
element->mSelectedColor = mSelectedColor;
element->mDisabledQuad = mDisabledQuad;
element->mDisabledColor = mDisabledColor;
}
void UIButton::Draw() const
{
if (mUpQuad.mTexture != nullptr)
{
int x = GetX(), y = GetY();
const UITexturedQuad &q = IsDisabled()? mDisabledQuad : (mPressed? mDownQuad : (mIsHighlighted? mHighlightQuad : mUpQuad));
Color c = IsDisabled()? mDisabledColor : (mPressed? mDownColor : (mIsHighlighted? mHighlightColor : mUpColor));
int ew = GetWidth();
int eh = GetHeight();
if (!q.HasInnerPart())
{
// Center image in button if it is smaller than the button
int w = min(ew, q.mWidth);
int h = min(eh, q.mHeight);
int x2 = x + (ew - w) / 2;
int y2 = y + (eh - h) / 2;
GetManager()->DrawQuad(x2, y2, w, h, q, c);
}
else
{
// This is a scale-9 quad, it will scale itself
GetManager()->DrawQuad(x, y, ew, eh, q, c);
}
// Draw selected quad
if (mIsSelected)
GetManager()->DrawQuad(x, y, ew, eh, mSelectedQuad, mSelectedColor);
}
DrawCustom();
// Skip direct base classes, we modify text color
UIElement::Draw();
}
void UIButton::SetButtonQuad(const UITexturedQuad &inQuad)
{
mUpQuad = inQuad;
mDownQuad = inQuad;
mHighlightQuad = inQuad;
mDisabledQuad = inQuad;
if (GetWidth() <= 0)
SetWidth(inQuad.mWidth);
if (GetHeight() <= 0)
SetHeight(inQuad.mHeight);
}

View File

@@ -0,0 +1,36 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UITextButton.h>
#include <UI/UITexturedQuad.h>
/// Button with a background image and text on it
class UIButton : public UITextButton
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, UIButton)
/// Cloning / copying
virtual void CopyTo(UIElement *ioElement) const override;
/// Draw element
virtual void Draw() const override;
/// Set quad
void SetButtonQuad(const UITexturedQuad &inQuad);
private:
UITexturedQuad mUpQuad;
Color mUpColor { Color(220, 220, 220) };
UITexturedQuad mDownQuad;
Color mDownColor { Color::sGrey };
UITexturedQuad mHighlightQuad;
Color mHighlightColor { Color::sWhite };
UITexturedQuad mSelectedQuad;
Color mSelectedColor { Color::sWhite };
UITexturedQuad mDisabledQuad;
Color mDisabledColor { Color::sGrey };
};

View File

@@ -0,0 +1,98 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UICheckBox.h>
#include <UI/UIManager.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(UICheckBox)
{
JPH_ADD_BASE_CLASS(UICheckBox, UIStaticText)
}
void UICheckBox::CopyTo(UIElement *ioElement) const
{
UIStaticText::CopyTo(ioElement);
UICheckBox *element = StaticCast<UICheckBox>(ioElement);
element->mDownTextColor = mDownTextColor;
element->mHighlightTextColor = mHighlightTextColor;
element->mPaddingBetweenCheckboxAndText = mPaddingBetweenCheckboxAndText;
element->mState = mState;
element->mUncheckedState = mUncheckedState;
element->mCheckedState = mCheckedState;
element->mClickAction = mClickAction;
}
void UICheckBox::OnAdded()
{
mTextPadLeft = max(mUncheckedState.mWidth, mCheckedState.mWidth) + mPaddingBetweenCheckboxAndText;
}
bool UICheckBox::MouseDown(int inX, int inY)
{
if (UIStaticText::MouseDown(inX, inY))
return true;
if (Contains(inX, inY))
{
mPressed = true;
return true;
}
return false;
}
bool UICheckBox::MouseUp(int inX, int inY)
{
if (UIStaticText::MouseUp(inX, inY))
return true;
if (mPressed)
{
mPressed = false;
if (Contains(inX, inY))
{
mState = mState == STATE_CHECKED? STATE_UNCHECKED : STATE_CHECKED;
if (mClickAction)
mClickAction(mState);
}
return true;
}
return false;
}
bool UICheckBox::MouseMove(int inX, int inY)
{
if (UIStaticText::MouseMove(inX, inY))
return true;
return mPressed;
}
void UICheckBox::MouseCancel()
{
UIStaticText::MouseCancel();
mPressed = false;
}
void UICheckBox::Draw() const
{
Color color = IsDisabled()? mDisabledTextColor : (mPressed? mDownTextColor : (mIsHighlighted? mHighlightTextColor : mTextColor));
UIStaticText::DrawCustom(color);
if (mState == STATE_UNCHECKED)
GetManager()->DrawQuad(GetX(), GetY() + (GetHeight() - mUncheckedState.mHeight) / 2, mUncheckedState.mWidth, mUncheckedState.mHeight, mUncheckedState, color);
else if (mState == STATE_CHECKED)
GetManager()->DrawQuad(GetX(), GetY() + (GetHeight() - mCheckedState.mHeight) / 2, mCheckedState.mWidth, mCheckedState.mHeight, mCheckedState, color);
// Skip direct base class, we modify the text color
UIElement::Draw();
}

View File

@@ -0,0 +1,58 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UIStaticText.h>
#include <UI/UITexturedQuad.h>
/// Check box control that allows the user to select between true or false
class UICheckBox : public UIStaticText
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, UICheckBox)
enum EState
{
STATE_UNCHECKED,
STATE_CHECKED
};
using ClickAction = function<void(EState)>;
/// Properties
void SetState(EState inState) { mState = inState; }
EState GetState() const { return mState; }
void SetClickAction(ClickAction inAction) { mClickAction = inAction; }
void SetUncheckedStateQuad(const UITexturedQuad &inQuad) { mUncheckedState = inQuad; }
void SetCheckedStateQuad(const UITexturedQuad &inQuad) { mCheckedState = inQuad; }
/// When added to a parent
virtual void OnAdded() override;
/// Cloning / copying
virtual void CopyTo(UIElement *ioElement) const override;
/// Actions
virtual bool MouseDown(int inX, int inY) override;
virtual bool MouseUp(int inX, int inY) override;
virtual bool MouseMove(int inX, int inY) override;
virtual void MouseCancel() override;
/// Draw element
virtual void Draw() const override;
protected:
/// Properties
Color mDownTextColor { Color::sGrey };
Color mHighlightTextColor { Color::sWhite };
int mPaddingBetweenCheckboxAndText = 8;
ClickAction mClickAction;
UITexturedQuad mUncheckedState;
UITexturedQuad mCheckedState;
/// State
EState mState = STATE_UNCHECKED;
bool mPressed = false;
};

View File

@@ -0,0 +1,88 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UIComboBox.h>
#include <UI/UIManager.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(UIComboBox)
{
JPH_ADD_BASE_CLASS(UIComboBox, UIElement)
}
void UIComboBox::CopyTo(UIElement *ioElement) const
{
UIElement::CopyTo(ioElement);
UIComboBox *element = StaticCast<UIComboBox>(ioElement);
element->mCurrentItem = mCurrentItem;
element->mItems = mItems;
element->mPreviousButton = mPreviousButton;
element->mNextButton = mNextButton;
element->mStaticText = mStaticText;
element->mItemChangedAction = mItemChangedAction;
}
bool UIComboBox::HandleUIEvent(EUIEvent inEvent, UIElement *inSender)
{
if (inEvent == EVENT_BUTTON_DOWN)
{
if (inSender == mPreviousButton)
{
SetItemInternal(mCurrentItem - 1);
return true;
}
else if (inSender == mNextButton)
{
SetItemInternal(mCurrentItem + 1);
return true;
}
}
return UIElement::HandleUIEvent(inEvent, inSender);
}
void UIComboBox::AutoLayout()
{
UIElement::AutoLayout();
// Position previous button
mPreviousButton->SetRelativeX(0);
mPreviousButton->SetRelativeY((GetHeight() - mPreviousButton->GetHeight()) / 2);
// Position static text
mStaticText->SetRelativeX((GetWidth() - mStaticText->GetWidth()) / 2);
mStaticText->SetRelativeY((GetHeight() - mStaticText->GetHeight()) / 2);
// Position next button
mNextButton->SetRelativeX(GetWidth() - mNextButton->GetWidth());
mNextButton->SetRelativeY((GetHeight() - mNextButton->GetHeight()) / 2);
}
void UIComboBox::SetItemInternal(int inItem)
{
int old_item = mCurrentItem;
if (inItem < 0)
mCurrentItem = 0;
else if (inItem > int(mItems.size()) - 1)
mCurrentItem = int(mItems.size()) - 1;
else
mCurrentItem = inItem;
if (mCurrentItem != old_item)
{
if (mItemChangedAction)
mItemChangedAction(mCurrentItem);
UpdateStaticText();
}
}
void UIComboBox::UpdateStaticText()
{
if (mStaticText != nullptr)
mStaticText->SetText(mItems[mCurrentItem]);
}

View File

@@ -0,0 +1,49 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UITexturedQuad.h>
#include <UI/UIButton.h>
/// Combo box with previous and next button
class UIComboBox : public UIElement
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, UIComboBox)
using ItemChangedAction = function<void(int)>;
/// Properties
void SetItems(const Array<String> &inItems) { mItems = inItems; }
void SetCurrentItem(int inItem) { mCurrentItem = inItem; }
void SetPreviousButton(UIButton *inPreviousButton) { mPreviousButton = inPreviousButton; }
void SetNextButton(UIButton *inNextButton) { mNextButton = inNextButton; }
void SetStaticText(UIStaticText *inStaticText) { mStaticText = inStaticText; UpdateStaticText(); }
void SetItemChangedAction(ItemChangedAction inAction) { mItemChangedAction = inAction; }
/// Cloning / copying
virtual void CopyTo(UIElement *ioElement) const override;
/// Event handling (returns true if the event has been handled)
virtual bool HandleUIEvent(EUIEvent inEvent, UIElement *inSender) override;
/// Calculate auto layout
virtual void AutoLayout() override;
protected:
/// Internal function to update the current item
void SetItemInternal(int inItem);
/// Update static text box
void UpdateStaticText();
/// Properties
Array<String> mItems;
int mCurrentItem = 0;
UIButton * mPreviousButton = nullptr;
UIButton * mNextButton = nullptr;
UIStaticText * mStaticText = nullptr;
ItemChangedAction mItemChangedAction;
};

View File

@@ -0,0 +1,311 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/Renderer.h>
#include <UI/UIElement.h>
#include <UI/UIAnimation.h>
JPH_IMPLEMENT_RTTI_VIRTUAL_BASE(UIElement)
{
}
UIElement::UIElement() :
mID(-1),
mParent(nullptr),
mIsVisible(true),
mAnimatedIsVisible(true),
mIsHighlighted(false),
mIsSelected(false),
mIsDisabled(false),
mHasActivateAnimation(true),
mHasDeactivateAnimation(true),
mManager(nullptr)
{
}
UIElement::~UIElement()
{
Clear();
}
void UIElement::Add(UIElement *inElement)
{
inElement->mParent = this;
inElement->mManager = mManager;
mChildren.push_back(inElement);
inElement->OnAdded();
}
void UIElement::Clear()
{
for (UIAnimation *a : mAnimations)
delete a;
for (UIElement *e : mChildren)
delete e;
}
void UIElement::StartAnimation(UIAnimation *inAnimation)
{
mAnimations.push_back(inAnimation);
inAnimation->Init(this);
inAnimation->Update(this, 0.0f);
}
void UIElement::StopAnimation(const RTTI *inAnimationType)
{
for (int i = (int)mAnimations.size() - 1; i >= 0; --i)
if (mAnimations[i]->GetRTTI()->IsKindOf(inAnimationType))
{
mAnimations[i]->Exit(this);
delete mAnimations[i];
mAnimations.erase(mAnimations.begin() + i);
}
}
UIElement *UIElement::Clone() const
{
UIElement *element = reinterpret_cast<UIElement *>(GetRTTI()->CreateObject());
CopyTo(element);
return element;
}
void UIElement::SetHighlighted(bool inHighlighted)
{
mIsHighlighted = inHighlighted;
for (UIElement *e : mChildren)
e->SetHighlighted(inHighlighted);
}
void UIElement::SetSelected(bool inSelected)
{
mIsSelected = inSelected;
for (UIElement *e : mChildren)
e->SetSelected(inSelected);
}
void UIElement::SetDisabled(bool inDisabled)
{
mIsDisabled = inDisabled;
for (UIElement *e : mChildren)
e->SetDisabled(inDisabled);
}
void UIElement::CopyTo(UIElement *ioElement) const
{
// Clone properties
ioElement->mID = mID;
ioElement->mRelativeX = mRelativeX;
ioElement->mRelativeY = mRelativeY;
ioElement->mWidth = mWidth;
ioElement->mHeight = mHeight;
ioElement->mIsVisible = mIsVisible;
ioElement->mAnimatedIsVisible = mAnimatedIsVisible;
ioElement->mHasActivateAnimation = mHasActivateAnimation;
ioElement->mHasDeactivateAnimation = mHasDeactivateAnimation;
ioElement->mManager = mManager;
// Clone children
for (const UIElement *e : mChildren)
ioElement->Add(e->Clone());
}
bool UIElement::Contains(int inX, int inY) const
{
int x = GetX(), y = GetY();
return inX >= x && inX < x + GetWidth() && inY >= y && inY < y + GetHeight();
}
bool UIElement::ContainsWidened(int inX, int inY, int inBorder) const
{
int x = GetX(), y = GetY();
return inX >= x - inBorder && inX < x + GetWidth() + inBorder && inY >= y - inBorder && inY < y + GetHeight() + inBorder;
}
void UIElement::Update(float inDeltaTime)
{
for (int i = 0; i < (int)mAnimations.size(); ++i)
{
UIAnimation *animation = mAnimations[i];
if (!animation->Update(this, inDeltaTime))
{
animation->Exit(this);
delete animation;
mAnimations.erase(mAnimations.begin() + i, mAnimations.begin() + i + 1);
--i;
}
}
for (UIElement *e : mChildren)
if (e->IsVisible())
e->Update(inDeltaTime);
}
void UIElement::Draw() const
{
for (const UIElement *e : mChildren)
if (e->IsVisible())
e->Draw();
}
bool UIElement::MouseDown(int inX, int inY)
{
for (UIElement *e : mChildren)
if (e->IsVisible() && !e->mIsDisabled)
if (e->MouseDown(inX, inY))
return true;
return false;
}
bool UIElement::MouseUp(int inX, int inY)
{
for (UIElement *e : mChildren)
if (e->IsVisible() && !e->mIsDisabled)
if (e->MouseUp(inX, inY))
return true;
return false;
}
bool UIElement::MouseMove(int inX, int inY)
{
if (Contains(inX, inY))
mIsHighlighted = true;
else
mIsHighlighted = false;
for (UIElement *e : mChildren)
if (e->IsVisible() && !e->mIsDisabled)
if (e->MouseMove(inX, inY))
return true;
return false;
}
void UIElement::MouseCancel()
{
for (UIElement *e : mChildren)
if (e->IsVisible() && !e->mIsDisabled)
e->MouseCancel();
}
UIElement *UIElement::FindByID(int inID)
{
if (inID == mID)
return this;
for (UIElement *e : mChildren)
{
UIElement *element = e->FindByID(inID);
if (element != nullptr)
return element;
}
return nullptr;
}
void UIElement::AutoLayout()
{
for (UIElement *e : mChildren)
{
// Recurse
e->AutoLayout();
// Encapsulate height and width of children
if (e->IsVisible())
{
mWidth.Set(max(GetWidth(), e->GetX() + e->GetWidth() - GetX() + e->GetPaddingRight()), PIXELS);
mHeight.Set(max(GetHeight(), e->GetY() + e->GetHeight() - GetY() + e->GetPaddingBottom()), PIXELS);
}
}
}
bool UIElement::HandleUIEvent(EUIEvent inEvent, UIElement *inSender)
{
if (mParent != nullptr)
return mParent->HandleUIEvent(inEvent, inSender);
return false;
}
void UIElement::Size::Set(int inValue, EUnits inUnits)
{
mUnit = inUnits;
mSize = inValue;
}
int UIElement::Size::GetSize(const UIElement *inElement, fGetSize inGetSize) const
{
switch (mUnit)
{
case PIXELS:
return mSize;
case PERCENTAGE:
{
const UIElement *parent = inElement->GetParent();
if (parent != nullptr)
return (mSize * (parent->*inGetSize)()) / 100;
else
return 0;
}
default:
JPH_ASSERT(false);
return 0;
}
}
void UIElement::Position::Set(int inValue, EUnits inUnits, EAlignment inAlignment)
{
mAlignment = inAlignment;
mSize.Set(inValue, inUnits);
}
int UIElement::Position::GetPosition(const UIElement *inElement, fGetSize inGetSize) const
{
int pos = mSize.GetSize(inElement, inGetSize);
switch (mAlignment)
{
case LEFT:
return pos;
case ONE_THIRD:
{
const UIElement *parent = inElement->GetParent();
if (parent != nullptr)
return ((parent->*inGetSize)() - (inElement->*inGetSize)()) / 3 + pos;
else
return 0;
}
case CENTER:
{
const UIElement *parent = inElement->GetParent();
if (parent != nullptr)
return ((parent->*inGetSize)() - (inElement->*inGetSize)()) / 2 + pos;
else
return 0;
}
case RIGHT:
{
const UIElement *parent = inElement->GetParent();
if (parent != nullptr)
return (parent->*inGetSize)() - (inElement->*inGetSize)() + pos;
else
return 0;
}
default:
JPH_ASSERT(false);
return 0;
}
}

View File

@@ -0,0 +1,189 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/RTTI.h>
#include <Jolt/Core/Color.h>
#include <UI/UIEventListener.h>
class UIManager;
class UIElement;
class UIAnimation;
using UIElementVector = Array<UIElement *>;
using UIAnimationVector = Array<UIAnimation *>;
/// Base class UI element. Forms a tree of UI elements.
class UIElement : public UIEventListener
{
public:
JPH_DECLARE_RTTI_VIRTUAL_BASE(JPH_NO_EXPORT, UIElement)
/// Constructor
UIElement();
virtual ~UIElement() override;
/// Add / remove child elements
void Add(UIElement *inElement);
void Clear();
virtual void OnAdded() { }
/// Start / stop animations
void StartAnimation(UIAnimation *inAnimation);
void StopAnimation(const RTTI *inAnimationType);
/// Cloning / copying
UIElement * Clone() const;
virtual void CopyTo(UIElement *ioElement) const;
/// Units
enum EUnits
{
PIXELS,
PERCENTAGE,
};
/// Alignment
enum EAlignment
{
LEFT,
ONE_THIRD,
CENTER,
RIGHT
};
/// Properties
int GetID() const { return mID; }
void SetID(int inID) { mID = inID; }
int GetX() const { return GetRelativeX() + (mParent != nullptr? mParent->GetX() : 0); }
int GetY() const { return GetRelativeY() + (mParent != nullptr? mParent->GetY() : 0); }
int GetRelativeX() const { return mRelativeX.GetPosition(this, &UIElement::GetWidth); }
void SetRelativeX(int inX, EUnits inUnits = PIXELS, EAlignment inAlignment = LEFT) { mRelativeX.Set(inX, inUnits, inAlignment); }
int GetRelativeY() const { return mRelativeY.GetPosition(this, &UIElement::GetHeight); }
void SetRelativeY(int inY, EUnits inUnits = PIXELS, EAlignment inAlignment = LEFT) { mRelativeY.Set(inY, inUnits, inAlignment); }
int GetWidth() const { return mWidth.GetSize(this, &UIElement::GetWidth); }
void SetWidth(int inWidth, EUnits inUnits = PIXELS) { mWidth.Set(inWidth, inUnits); }
int GetHeight() const { return mHeight.GetSize(this, &UIElement::GetHeight); }
void SetHeight(int inHeight, EUnits inUnits = PIXELS) { mHeight.Set(inHeight, inUnits); }
int GetPaddingRight() const { return mPaddingRight.GetSize(this, &UIElement::GetWidth); }
void SetPaddingRight(int inY, EUnits inUnits = PIXELS) { mPaddingRight.Set(inY, inUnits); }
int GetPaddingBottom() const { return mPaddingBottom.GetSize(this, &UIElement::GetHeight); }
void SetPaddingBottom(int inY, EUnits inUnits = PIXELS) { mPaddingBottom.Set(inY, inUnits); }
void SetVisible(bool inShow) { mIsVisible = inShow; }
bool IsVisible() const { return mIsVisible && mAnimatedIsVisible; }
void SetDisabled(bool inDisabled);
bool IsDisabled() const { return mIsDisabled; }
void SetHighlighted(bool inHighlighted);
bool IsHighlighted() const { return mIsHighlighted; }
void SetSelected(bool inSelected);
bool IsSelected() const { return mIsSelected; }
/// Animation
void SetAnimatedVisible(bool inShow) { mAnimatedIsVisible = inShow; } ///< Visibility flag that can be set by UIAnimations
bool HasActivateAnimation() const { return mHasActivateAnimation; }
bool HasDeactivateAnimation() const { return mHasDeactivateAnimation; }
/// Manager
UIManager * GetManager() const { return mManager; }
/// Parent child linking
UIElement * GetParent() const { return mParent; }
int GetNumChildren() const { return (int)mChildren.size(); }
UIElement * GetChild(int inIdx) const { return mChildren[inIdx]; }
const UIElementVector &GetChildren() const { return mChildren; }
/// Hit testing
bool Contains(int inX, int inY) const;
bool ContainsWidened(int inX, int inY, int inBorder) const;
/// Calculate auto layout
virtual void AutoLayout();
/// Find element by ID
virtual UIElement * FindByID(int inID);
/// Update element
virtual void Update(float inDeltaTime);
/// Draw element
virtual void Draw() const;
/// Actions
virtual bool MouseDown(int inX, int inY);
virtual bool MouseUp(int inX, int inY);
virtual bool MouseMove(int inX, int inY);
virtual void MouseCancel();
/// Event handling (returns true if the event has been handled)
virtual bool HandleUIEvent(EUIEvent inEvent, UIElement *inSender) override;
protected:
/// ID
int mID;
/// Hierarchy
UIElement * mParent;
UIElementVector mChildren;
/// Abstract GetSize function
using fGetSize = int (UIElement::*)() const;
/// Size
class Size
{
public:
/// Constructor
Size() : mSize(0), mUnit(PIXELS) { }
/// Get size
int GetSize(const UIElement *inElement, fGetSize inGetSize) const;
/// Assignment
void Set(int inValue, EUnits inUnits);
private:
int mSize;
EUnits mUnit;
};
/// Position
class Position
{
public:
/// Constructor
Position() : mAlignment(LEFT) { }
/// Get position
int GetPosition(const UIElement *inElement, fGetSize inGetSize) const;
/// Assignment
void Set(int inValue, EUnits inUnits, EAlignment inAlignment);
private:
EAlignment mAlignment;
Size mSize;
};
/// Position
Position mRelativeX;
Position mRelativeY;
Size mWidth;
Size mHeight;
Size mPaddingRight;
Size mPaddingBottom;
bool mIsVisible;
bool mAnimatedIsVisible;
bool mIsHighlighted;
bool mIsSelected;
bool mIsDisabled;
/// Animations
bool mHasActivateAnimation;
bool mHasDeactivateAnimation;
UIAnimationVector mAnimations;
/// Manager
UIManager * mManager;
};

View File

@@ -0,0 +1,24 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
enum EUIEvent
{
EVENT_BUTTON_DOWN,
EVENT_MENU_DEACTIVATED,
};
class UIElement;
/// Callback class for handling events from UI elements
class UIEventListener
{
public:
/// Destructor
virtual ~UIEventListener() = default;
/// Handle an UI event, function should return true if event was handled
virtual bool HandleUIEvent(EUIEvent inEvent, UIElement *inSender) = 0;
};

View File

@@ -0,0 +1,62 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UIHorizontalStack.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(UIHorizontalStack)
{
JPH_ADD_BASE_CLASS(UIHorizontalStack, UIElement)
}
void UIHorizontalStack::sUniformChildWidth(UIElement *inParent)
{
Array<int> sizes;
sizes.resize(1, 0);
for (UIElement *e : inParent->GetChildren())
{
e->AutoLayout();
UIHorizontalStack *horiz = DynamicCast<UIHorizontalStack>(e);
if (horiz != nullptr)
{
if (horiz->GetNumChildren() > (int)sizes.size())
sizes.resize(horiz->GetNumChildren(), 0);
for (int i = 0; i < horiz->GetNumChildren(); ++i)
sizes[i] = max(sizes[i], horiz->GetChild(i)->GetWidth());
}
else
{
sizes[0] = max(sizes[0], e->GetWidth());
}
}
for (UIElement *e : inParent->GetChildren())
{
UIHorizontalStack *horiz = DynamicCast<UIHorizontalStack>(e);
if (horiz != nullptr)
{
for (int i = 0; i < horiz->GetNumChildren(); ++i)
horiz->GetChild(i)->SetWidth(sizes[i]);
}
else
{
e->SetWidth(sizes[0]);
}
}
}
void UIHorizontalStack::AutoLayout()
{
UIElement::AutoLayout();
mWidth.Set(0, PIXELS);
for (UIElement *e : mChildren)
if (e->IsVisible() || mPlaceInvisibleChildren)
{
e->SetRelativeX(GetWidth());
mWidth.Set(GetWidth() + e->GetWidth() + e->GetPaddingRight() + mDeltaX, PIXELS);
}
}

View File

@@ -0,0 +1,27 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UIElement.h>
/// Layout class that will horizontally place elements next to each other according to their widths
class UIHorizontalStack : public UIElement
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, UIHorizontalStack)
/// Helper function to resize a list of child elements consisting of UIHorizontalStack's to make them the same width.
/// Can be used to give them the appearance of a table.
/// Find the width of all UIHorizontalStack child elements in inParent and use the maximum width for all of them
/// Non UIHorizontalStack elements will be treated as a UIHorizontalStack with only 1 element inside
static void sUniformChildWidth(UIElement *inParent);
/// Calculate auto layout
virtual void AutoLayout() override;
private:
int mDeltaX = 0;
bool mPlaceInvisibleChildren = false;
};

View File

@@ -0,0 +1,29 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Renderer/Renderer.h>
#include <UI/UIImage.h>
#include <UI/UIManager.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(UIImage)
{
JPH_ADD_BASE_CLASS(UIImage, UIElement)
}
void UIImage::Draw() const
{
GetManager()->DrawQuad(GetX(), GetY(), GetWidth(), GetHeight(), mImage, Color::sWhite);
UIElement::Draw();
}
void UIImage::CopyTo(UIElement *ioElement) const
{
UIElement::CopyTo(ioElement);
UIImage *element = StaticCast<UIImage>(ioElement);
element->mImage = mImage;
}

View File

@@ -0,0 +1,27 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UIElement.h>
#include <UI/UITexturedQuad.h>
/// A static image UI element
class UIImage : public UIElement
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, UIImage)
/// Set properties
void SetImage(const UITexturedQuad &inImage) { mImage = inImage; }
/// Cloning / copying
virtual void CopyTo(UIElement *ioElement) const override;
/// Draw element
virtual void Draw() const override;
private:
UITexturedQuad mImage;
};

View File

@@ -0,0 +1,342 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UIManager.h>
#include <UI/UIAnimationSlide.h>
#include <Jolt/Core/Profiler.h>
#include <Renderer/Renderer.h>
#include <Renderer/Font.h>
struct QuadVertex
{
Float3 mPosition;
Float2 mTexCoord;
Color mColor;
};
UIManager::UIManager(Renderer *inRenderer) :
mRenderer(inRenderer),
mListener(nullptr),
mState(STATE_INVALID)
{
mManager = this;
// Set dimensions of the screen
ApplicationWindow *window = mRenderer->GetWindow();
SetWidth(window->GetWindowWidth());
SetHeight(window->GetWindowHeight());
// Create input layout
const PipelineState::EInputDescription vertex_desc[] =
{
PipelineState::EInputDescription::Position,
PipelineState::EInputDescription::TexCoord,
PipelineState::EInputDescription::Color
};
// Load vertex shader
Ref<VertexShader> vtx = mRenderer->CreateVertexShader("UIVertexShader");
// Load pixel shader
Ref<PixelShader> pix_textured = mRenderer->CreatePixelShader("UIPixelShader");
Ref<PixelShader> pix_untextured = mRenderer->CreatePixelShader("UIPixelShaderUntextured");
mTextured = mRenderer->CreatePipelineState(vtx, vertex_desc, std::size(vertex_desc), pix_textured, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
mUntextured = mRenderer->CreatePipelineState(vtx, vertex_desc, std::size(vertex_desc), pix_untextured, PipelineState::EDrawPass::Normal, PipelineState::EFillMode::Solid, PipelineState::ETopology::Triangle, PipelineState::EDepthTest::Off, PipelineState::EBlendMode::AlphaBlend, PipelineState::ECullMode::Backface);
}
UIManager::~UIManager()
{
while (!mInactiveElements.empty())
PopLayer();
}
// Update elements
void UIManager::Update(float inDeltaTime)
{
JPH_PROFILE_FUNCTION();
// Update inactive elements (array can resize at any time, so no iterators and extra checking here)
for (int i = (int)mInactiveElements.size() - 1; i >= 0; --i)
for (int j = 0; i < (int)mInactiveElements.size() && j < (int)mInactiveElements[i].size(); ++j)
mInactiveElements[i][j]->Update(inDeltaTime);
// Update elements
UIElement::Update(inDeltaTime);
// Update state
mStateTime += inDeltaTime;
switch (mState)
{
case STATE_ACTIVATING:
if (mStateTime > cActivateScreenTime)
SwitchToState(STATE_ACTIVE);
break;
case STATE_DEACTIVATING:
if (mStateTime > cActivateScreenTime)
SwitchToState(STATE_DEACTIVE);
break;
case STATE_ACTIVE:
case STATE_DEACTIVE:
case STATE_INVALID:
default:
break;
}
}
void UIManager::Draw() const
{
JPH_PROFILE_FUNCTION();
// Switch tho ortho mode
mRenderer->SetOrthoMode();
// Draw inactive elements first
if (mDrawInactiveElements)
for (int i = (int)mInactiveElements.size() - 1; i >= 0; --i)
for (const UIElement *j : mInactiveElements[i])
if (j->IsVisible())
j->Draw();
// Then draw active elements
UIElement::Draw();
// Restore state
mRenderer->SetProjectionMode();
}
void UIManager::PushLayer()
{
mInactiveElements.push_back(mChildren);
mChildren.clear();
}
void UIManager::PopLayer()
{
Clear();
mChildren = mInactiveElements.back();
mInactiveElements.pop_back();
}
UIElement *UIManager::FindByID(int inID)
{
UIElement *element = UIElement::FindByID(inID);
if (element != nullptr)
return element;
for (int i = (int)mInactiveElements.size() - 1; i >= 0; --i)
for (int j = 0; j < (int)mInactiveElements[i].size(); ++j)
{
element = mInactiveElements[i][j]->FindByID(inID);
if (element != nullptr)
return element;
}
return nullptr;
}
bool UIManager::HandleUIEvent(EUIEvent inEvent, UIElement *inSender)
{
if (UIElement::HandleUIEvent(inEvent, inSender))
return true;
return mListener != nullptr && mListener->HandleUIEvent(inEvent, inSender);
}
void UIManager::GetMaxElementDistanceToScreenEdge(int &outMaxH, int &outMaxV)
{
outMaxH = 0;
outMaxV = 0;
ApplicationWindow *window = mRenderer->GetWindow();
for (const UIElement *e : mChildren)
if (e->HasDeactivateAnimation())
{
int dl = e->GetX() + e->GetWidth();
int dr = window->GetWindowWidth() - e->GetX();
outMaxH = max(outMaxH, min(dl, dr));
int dt = e->GetY() + e->GetHeight();
int db = window->GetWindowHeight() - e->GetY();
outMaxV = max(outMaxV, min(dt, db));
}
}
void UIManager::SwitchToState(EState inState)
{
// Clean up old state
switch (mState)
{
case STATE_ACTIVATING:
case STATE_DEACTIVATING:
for (UIElement *e : mChildren)
e->StopAnimation(JPH_RTTI(UIAnimationSlide));
break;
case STATE_ACTIVE:
case STATE_DEACTIVE:
case STATE_INVALID:
default:
break;
}
// Store new state
mState = inState;
mStateTime = 0.0f;
// Calculate max horizontal and vertical distance of elements to edge of screen
int max_h, max_v;
GetMaxElementDistanceToScreenEdge(max_h, max_v);
switch (inState)
{
case STATE_ACTIVATING:
for (UIElement *e : mChildren)
if (e->HasActivateAnimation())
e->StartAnimation(new UIAnimationSlide(UIAnimationSlide::SLIDE_ON_SCREEN, max_h, max_v, 0.0f, cActivateScreenTime));
break;
case STATE_DEACTIVATING:
for (UIElement *e : mChildren)
if (e->HasDeactivateAnimation())
e->StartAnimation(new UIAnimationSlide(UIAnimationSlide::SLIDE_OFF_SCREEN, max_h, max_v, 0.0f, cActivateScreenTime));
break;
case STATE_DEACTIVE:
HandleUIEvent(EVENT_MENU_DEACTIVATED, this);
if (mDeactivatedAction)
mDeactivatedAction();
break;
case STATE_ACTIVE:
case STATE_INVALID:
default:
break;
}
}
inline static void sDrawQuad(QuadVertex *&v ,float x1, float y1, float x2, float y2, float tx1, float ty1, float tx2, float ty2, ColorArg inColor)
{
v->mPosition = Float3(x1, y1, 0);
v->mTexCoord = Float2(tx1, ty1);
v->mColor = inColor;
++v;
v->mPosition = Float3(x1, y2, 0);
v->mTexCoord = Float2(tx1, ty2);
v->mColor = inColor;
++v;
v->mPosition = Float3(x2, y2, 0);
v->mTexCoord = Float2(tx2, ty2);
v->mColor = inColor;
++v;
v->mPosition = Float3(x1, y1, 0);
v->mTexCoord = Float2(tx1, ty1);
v->mColor = inColor;
++v;
v->mPosition = Float3(x2, y2, 0);
v->mTexCoord = Float2(tx2, ty2);
v->mColor = inColor;
++v;
v->mPosition = Float3(x2, y1, 0);
v->mTexCoord = Float2(tx2, ty1);
v->mColor = inColor;
++v;
}
void UIManager::DrawQuad(int inX, int inY, int inWidth, int inHeight, const UITexturedQuad &inQuad, ColorArg inColor)
{
// Outer area - screen coordinates
float x1 = float(inX);
float y1 = float(inY);
float x2 = float(inX + inWidth);
float y2 = float(inY + inHeight);
if (inQuad.mTexture != nullptr)
{
bool has_inner = inQuad.HasInnerPart();
Ref<RenderPrimitive> primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
primitive->CreateVertexBuffer(has_inner? 9 * 6 : 6, sizeof(QuadVertex));
float w = float(inQuad.mTexture->GetWidth()), h = float(inQuad.mTexture->GetHeight());
// Outer area - texture coordinates
float tx1 = float(inQuad.mX);
float ty1 = float(inQuad.mY);
float tx2 = float(inQuad.mX + inQuad.mWidth);
float ty2 = float(inQuad.mY + inQuad.mHeight);
tx1 /= w; ty1 /= h;
tx2 /= w; ty2 /= h;
QuadVertex *v = (QuadVertex *)primitive->LockVertexBuffer();
if (has_inner)
{
// Inner area - screen coordinates
float ix1 = float(inX + inQuad.mInnerX - inQuad.mX);
float iy1 = float(inY + inQuad.mInnerY - inQuad.mY);
float ix2 = float(inX + inWidth - (inQuad.mWidth - inQuad.mInnerWidth - (inQuad.mInnerX - inQuad.mX)));
float iy2 = float(inY + inHeight - (inQuad.mHeight - inQuad.mInnerHeight - (inQuad.mInnerY - inQuad.mY)));
// Inner area - texture coordinates
float itx1 = float(inQuad.mInnerX);
float ity1 = float(inQuad.mInnerY);
float itx2 = float(inQuad.mInnerX + inQuad.mInnerWidth);
float ity2 = float(inQuad.mInnerY + inQuad.mInnerHeight);
itx1 /= w; ity1 /= h;
itx2 /= w; ity2 /= h;
sDrawQuad(v, x1, y1, ix1, iy1, tx1, ty1, itx1, ity1, inColor);
sDrawQuad(v, ix1, y1, ix2, iy1, itx1, ty1, itx2, ity1, inColor);
sDrawQuad(v, ix2, y1, x2, iy1, itx2, ty1, tx2, ity1, inColor);
sDrawQuad(v, x1, iy1, ix1, iy2, tx1, ity1, itx1, ity2, inColor);
sDrawQuad(v, ix1, iy1, ix2, iy2, itx1, ity1, itx2, ity2, inColor);
sDrawQuad(v, ix2, iy1, x2, iy2, itx2, ity1, tx2, ity2, inColor);
sDrawQuad(v, x1, iy2, ix1, y2, tx1, ity2, itx1, ty2, inColor);
sDrawQuad(v, ix1, iy2, ix2, y2, itx1, ity2, itx2, ty2, inColor);
sDrawQuad(v, ix2, iy2, x2, y2, itx2, ity2, tx2, ty2, inColor);
}
else
{
sDrawQuad(v, x1, y1, x2, y2, tx1, ty1, tx2, ty2, inColor);
}
primitive->UnlockVertexBuffer();
inQuad.mTexture->Bind();
mTextured->Activate();
primitive->Draw();
}
else
{
Ref<RenderPrimitive> primitive = mRenderer->CreateRenderPrimitive(PipelineState::ETopology::Triangle);
primitive->CreateVertexBuffer(6, sizeof(QuadVertex));
QuadVertex *v = (QuadVertex *)primitive->LockVertexBuffer();
sDrawQuad(v, x1, y1, x2, y2, 0, 0, 0, 0, inColor);
primitive->UnlockVertexBuffer();
mUntextured->Activate();
primitive->Draw();
}
}
void UIManager::DrawText(int inX, int inY, const string_view &inText, const Font *inFont, ColorArg inColor)
{
Vec4 pos(float(inX), float(inY), 0.0f, 1.0f);
Vec4 right(float(inFont->GetCharHeight()), 0.0f, 0.0f, 0.0f);
Vec4 up(0.0f, float(-inFont->GetCharHeight()), 0.0f, 0.0f);
Vec4 forward(0.0f, 0.0f, 1.0f, 0.0f);
Mat44 transform(right, up, forward, pos);
inFont->DrawText3D(transform, inText, inColor);
}

View File

@@ -0,0 +1,86 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UIElement.h>
#include <UI/UITexturedQuad.h>
#include <Renderer/Renderer.h>
#include <Renderer/PipelineState.h>
#include <memory>
class Font;
const float cActivateScreenTime = 0.2f;
/// Manager class that manages UIElements
class UIManager : public UIElement
{
public:
/// Constructor
UIManager(Renderer *inRenderer);
virtual ~UIManager() override;
/// Update elements
virtual void Update(float inDeltaTime) override;
/// Draw elements
virtual void Draw() const override;
/// Only one layer can be active, if you push a layer it will exit in the background and no longer be updated
void PushLayer();
void PopLayer();
int GetNumLayers() const { return (int)mInactiveElements.size() + 1; }
void SetDrawInactiveLayers(bool inDraw) { mDrawInactiveElements = inDraw; }
/// Find element by ID
virtual UIElement * FindByID(int inID) override;
/// Listeners
void SetListener(UIEventListener *inListener) { mListener = inListener; }
UIEventListener * GetListener() const { return mListener; }
/// Actions
void SetDeactivatedAction(function<void()> inAction) { mDeactivatedAction = inAction; }
/// Event handling (returns true if the event has been handled)
virtual bool HandleUIEvent(EUIEvent inEvent, UIElement *inSender) override;
/// Current state
enum EState
{
STATE_INVALID,
STATE_ACTIVATING,
STATE_ACTIVE,
STATE_DEACTIVATING,
STATE_DEACTIVE
};
void SwitchToState(EState inState);
EState GetState() const { return mState; }
/// Calculate max horizontal and vertical distance of elements to edge of screen
void GetMaxElementDistanceToScreenEdge(int &outMaxH, int &outMaxV);
/// Access to the renderer
Renderer * GetRenderer() { return mRenderer; }
/// Drawing
void DrawQuad(int inX, int inY, int inWidth, int inHeight, const UITexturedQuad &inQuad, ColorArg inColor);
/// Draw a string in screen coordinates (assumes that the projection matrix has been set up correctly)
void DrawText(int inX, int inY, const string_view &inText, const Font *inFont, ColorArg inColor = Color::sWhite);
private:
Renderer * mRenderer;
UIEventListener * mListener;
Array<UIElementVector> mInactiveElements;
bool mDrawInactiveElements = true;
unique_ptr<PipelineState> mTextured;
unique_ptr<PipelineState> mUntextured;
function<void()> mDeactivatedAction;
EState mState;
float mStateTime;
};

View File

@@ -0,0 +1,186 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UISlider.h>
#include <UI/UIManager.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(UISlider)
{
JPH_ADD_BASE_CLASS(UISlider, UIElement)
}
void UISlider::CopyTo(UIElement *ioElement) const
{
UIElement::CopyTo(ioElement);
UISlider *element = StaticCast<UISlider>(ioElement);
element->mCurrentValue = mCurrentValue;
element->mMinValue = mMinValue;
element->mMaxValue = mMaxValue;
element->mStepValue = mStepValue;
element->mDecreaseButton = mDecreaseButton;
element->mIncreaseButton = mIncreaseButton;
element->mSpaceBetweenButtonAndSlider = mSpaceBetweenButtonAndSlider;
element->mSlider = mSlider;
element->mThumb = mThumb;
element->mValueChangedAction = mValueChangedAction;
}
void UISlider::GetSliderRange(int &outSliderStart, int &outSliderEnd) const
{
outSliderStart = GetX() + mDecreaseButton->GetWidth() + mSpaceBetweenButtonAndSlider;
outSliderEnd = GetX() + GetWidth() - mIncreaseButton->GetWidth() - mSpaceBetweenButtonAndSlider;
}
int UISlider::GetThumbStart(int inSliderStart, int inSliderEnd) const
{
return inSliderStart + int(float(inSliderEnd - inSliderStart - mThumb.mWidth) * (mCurrentValue - mMinValue) / (mMaxValue - mMinValue));
}
bool UISlider::HandleUIEvent(EUIEvent inEvent, UIElement *inSender)
{
if (inEvent == EVENT_BUTTON_DOWN)
{
if (inSender == mDecreaseButton)
{
SetValueInternal(mCurrentValue - mStepValue);
return true;
}
else if (inSender == mIncreaseButton)
{
SetValueInternal(mCurrentValue + mStepValue);
return true;
}
}
return UIElement::HandleUIEvent(inEvent, inSender);
}
bool UISlider::MouseDown(int inX, int inY)
{
if (Contains(inX, inY))
{
int slider_start, slider_end;
GetSliderRange(slider_start, slider_end);
int tx = GetThumbStart(slider_start, slider_end);
if (inX >= tx && inX < tx + mThumb.mWidth)
{
mThumbDragPoint = inX - tx;
return true;
}
}
return UIElement::MouseDown(inX, inY);
}
bool UISlider::MouseUp(int inX, int inY)
{
if (mThumbDragPoint != -1)
{
mThumbDragPoint = -1;
return true;
}
return UIElement::MouseUp(inX, inY);
}
bool UISlider::MouseMove(int inX, int inY)
{
if (mThumbDragPoint != -1)
{
// Calculate new value
int slider_start, slider_end;
GetSliderRange(slider_start, slider_end);
SetValueInternal(mMinValue + float(inX - mThumbDragPoint - slider_start) * (mMaxValue - mMinValue) / float(slider_end - slider_start - mThumb.mWidth));
return true;
}
return UIElement::MouseMove(inX, inY);
}
void UISlider::MouseCancel()
{
UIElement::MouseCancel();
mThumbDragPoint = -1;
}
void UISlider::Draw() const
{
UIElement::Draw();
int slider_start, slider_end;
GetSliderRange(slider_start, slider_end);
// Draw slider
int sy = (GetHeight() - mSlider.mHeight) / 2;
GetManager()->DrawQuad(slider_start, GetY() + sy, slider_end - slider_start, mSlider.mHeight, mSlider, Color::sWhite);
// Draw thumb
int tx = GetThumbStart(slider_start, slider_end);
int ty = (GetHeight() - mThumb.mHeight) / 2;
GetManager()->DrawQuad(tx, GetY() + ty, mThumb.mWidth, mThumb.mHeight, mThumb, Color::sWhite);
}
void UISlider::AutoLayout()
{
UIElement::AutoLayout();
// Position decrease button
mDecreaseButton->SetRelativeX(0);
mDecreaseButton->SetRelativeY((GetHeight() - mDecreaseButton->GetHeight()) / 2);
// Position increase button
mIncreaseButton->SetRelativeX(GetWidth() - mIncreaseButton->GetWidth());
mIncreaseButton->SetRelativeY((GetHeight() - mIncreaseButton->GetHeight()) / 2);
}
void UISlider::SetValueInternal(float inValue)
{
float old_value = mCurrentValue;
float step = round((inValue - mMinValue) / mStepValue);
mCurrentValue = Clamp(mMinValue + step * mStepValue, mMinValue, mMaxValue);
if (mCurrentValue != old_value)
{
if (mValueChangedAction)
mValueChangedAction(mCurrentValue);
UpdateStaticText();
}
}
void UISlider::UpdateStaticText()
{
if (mStaticText != nullptr)
{
float step_frac = mStepValue - trunc(mStepValue);
float min_frac = mMinValue - trunc(mMinValue);
float max_frac = mMaxValue - trunc(mMaxValue);
float smallest = step_frac;
if (min_frac < smallest && abs(min_frac) > 1.0e-6f)
smallest = min_frac;
if (max_frac < smallest && abs(max_frac) > 1.0e-6f)
smallest = max_frac;
if (smallest == 0.0f)
{
// Integer values
mStaticText->SetText(ConvertToString(int(round(mCurrentValue))));
}
else
{
int num_digits = -int(floor(log10(smallest)));
stringstream ss;
ss.precision(num_digits);
ss << fixed << mCurrentValue;
mStaticText->SetText(ss.str());
}
}
}

View File

@@ -0,0 +1,74 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UITexturedQuad.h>
#include <UI/UIButton.h>
/// Slider control with up/down button and thumb to select a value
class UISlider : public UIElement
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, UISlider)
using ValueChangedAction = function<void(float)>;
/// Properties
void SetValue(float inValue) { mCurrentValue = inValue; }
void SetRange(float inMin, float inMax, float inStep) { mMinValue = inMin; mMaxValue = inMax; mStepValue = inStep; }
void SetDecreaseButton(UIButton *inDecreaseButton) { mDecreaseButton = inDecreaseButton; }
void SetIncreaseButton(UIButton *inIncreaseButton) { mIncreaseButton = inIncreaseButton; }
void SetStaticText(UIStaticText *inStaticText) { mStaticText = inStaticText; UpdateStaticText(); }
void SetSlider(const UITexturedQuad &inSlider) { mSlider = inSlider; }
void SetThumb(const UITexturedQuad &inThumb) { mThumb = inThumb; }
void SetValueChangedAction(ValueChangedAction inAction) { mValueChangedAction = inAction; }
/// Cloning / copying
virtual void CopyTo(UIElement *ioElement) const override;
/// Actions
virtual bool MouseDown(int inX, int inY) override;
virtual bool MouseUp(int inX, int inY) override;
virtual bool MouseMove(int inX, int inY) override;
virtual void MouseCancel() override;
/// Draw element
virtual void Draw() const override;
/// Event handling (returns true if the event has been handled)
virtual bool HandleUIEvent(EUIEvent inEvent, UIElement *inSender) override;
/// Calculate auto layout
virtual void AutoLayout() override;
protected:
/// Pixel range of slider relative to parent
void GetSliderRange(int &outSliderStart, int &outSliderEnd) const;
/// X coordinate of thumb start
int GetThumbStart(int inSliderStart, int inSliderEnd) const;
/// Internal function to update the value
void SetValueInternal(float inValue);
/// Update static text box
void UpdateStaticText();
/// Properties
float mCurrentValue = 0.0f;
float mMinValue = 0.0f;
float mMaxValue = 1.0f;
float mStepValue = 0.1f;
UIButton * mDecreaseButton = nullptr;
UIButton * mIncreaseButton = nullptr;
UIStaticText * mStaticText = nullptr;
int mSpaceBetweenButtonAndSlider = 5;
UITexturedQuad mSlider;
UITexturedQuad mThumb;
ValueChangedAction mValueChangedAction;
/// State
int mThumbDragPoint = -1;
};

View File

@@ -0,0 +1,168 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UIStaticText.h>
#include <Renderer/Renderer.h>
#include <Renderer/Font.h>
#include <UI/UIManager.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(UIStaticText)
{
JPH_ADD_BASE_CLASS(UIStaticText, UIElement)
}
void UIStaticText::CopyTo(UIElement *ioElement) const
{
UIElement::CopyTo(ioElement);
UIStaticText *element = StaticCast<UIStaticText>(ioElement);
element->mFont = mFont;
element->mText = mText;
element->mTextColor = mTextColor;
element->mDisabledTextColor = mDisabledTextColor;
element->mTextPadLeft = mTextPadLeft;
element->mTextPadRight = mTextPadRight;
element->mTextPadTop = mTextPadTop;
element->mTextPadBottom = mTextPadBottom;
element->mTextAlignment = mTextAlignment;
element->mWrap = mWrap;
}
void UIStaticText::Draw() const
{
DrawCustom(IsDisabled()? mDisabledTextColor : mTextColor);
UIElement::Draw();
}
void UIStaticText::AutoLayout()
{
UIElement::AutoLayout();
// Update size
if (mFont != nullptr)
{
Float2 size = mFont->MeasureText(GetWrappedText());
int w = int(size.x * mFont->GetCharHeight()) + mTextPadLeft + mTextPadRight;
int h = int(size.y * mFont->GetCharHeight()) + mTextPadTop + mTextPadBottom;
if (GetWidth() <= 0)
mWidth.Set(w, PIXELS);
if (GetHeight() <= 0)
mHeight.Set(h, PIXELS);
}
}
String UIStaticText::GetWrappedText() const
{
String text;
if (mWrap)
{
int width = GetWidth() - mTextPadLeft - mTextPadRight;
size_t start_pos = 0, prev_end_pos = size_t(-1);
for (;;)
{
// Find next space or end of text
size_t end_pos = mText.find(' ', prev_end_pos + 1);
if (end_pos == String::npos)
end_pos = mText.length();
// Get line to test for width
String sub = mText.substr(start_pos, end_pos - start_pos);
// Measure width
Float2 size = mFont->MeasureText(sub);
int w = int(size.x * mFont->GetCharHeight());
// Check if still fits
if (width < w)
{
// If nothing was found yet, add the last word anyhow so that we don't get into an infinite loop
if (start_pos >= prev_end_pos
|| prev_end_pos == size_t(-1))
prev_end_pos = end_pos;
// Add current line
text += mText.substr(start_pos, prev_end_pos - start_pos);
text += "\n";
// Start here for new line
start_pos = prev_end_pos + 1;
}
else
{
// Store last fitting line
prev_end_pos = end_pos;
}
// Check end
if (end_pos >= mText.length())
break;
}
// Add last line
if (start_pos < mText.length())
text += mText.substr(start_pos, mText.length() - start_pos);
}
else
{
// Don't autowrap
text = mText;
}
return text;
}
void UIStaticText::DrawCustom(ColorArg inColor) const
{
if (mFont != nullptr && !mText.empty())
{
String text = GetWrappedText();
int y = GetY() + mTextPadTop;
if (mTextAlignment == LEFT)
{
GetManager()->DrawText(GetX() + mTextPadLeft, y, text, mFont, inColor);
}
else if (mTextAlignment == CENTER)
{
// Split lines
Array<String> lines;
StringToVector(text, lines, "\n");
// Amount of space we have horizontally
int width = GetWidth() - mTextPadLeft - mTextPadRight;
// Center each line individually
for (const String &l : lines)
{
Float2 size = mFont->MeasureText(l);
int w = int(size.x * mFont->GetCharHeight());
GetManager()->DrawText(GetX() + (width - w) / 2 + mTextPadLeft, y, l, mFont, inColor);
y += mFont->GetCharHeight();
}
}
else
{
JPH_ASSERT(mTextAlignment == RIGHT);
// Split lines
Array<String> lines;
StringToVector(text, lines, "\n");
// Center each line individually
for (const String &l : lines)
{
Float2 size = mFont->MeasureText(l);
int w = int(size.x * mFont->GetCharHeight());
GetManager()->DrawText(GetX() + GetWidth() - mTextPadRight - w, y, l, mFont, inColor);
y += mFont->GetCharHeight();
}
}
}
}

View File

@@ -0,0 +1,50 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UIElement.h>
#include <Renderer/Font.h>
/// Static text string
class UIStaticText : public UIElement
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, UIStaticText)
/// Cloning / copying
virtual void CopyTo(UIElement *ioElement) const override;
/// Set properties
void SetTextColor(ColorArg inColor) { mTextColor = inColor; }
void SetDisabledTextColor(ColorArg inColor) { mDisabledTextColor = inColor; }
void SetFont(const Font *inFont) { mFont = inFont; }
void SetText(const string_view &inText) { mText = inText; }
void SetTextPadding(int inTop, int inLeft, int inBottom, int inRight) { mTextPadTop = inTop; mTextPadLeft = inLeft; mTextPadBottom = inBottom; mTextPadRight = inRight; }
void SetTextAlignment(EAlignment inAlignment) { JPH_ASSERT(inAlignment == LEFT || inAlignment == RIGHT || inAlignment == CENTER); mTextAlignment = inAlignment; }
void SetWrap(bool inWrap) { mWrap = inWrap; }
/// Draw element
virtual void Draw() const override;
/// Calculate auto layout
virtual void AutoLayout() override;
protected:
/// Draw element custom
void DrawCustom(ColorArg inColor) const;
String GetWrappedText() const;
RefConst<Font> mFont;
String mText;
Color mTextColor { Color(220, 220, 200) };
Color mDisabledTextColor { Color::sGrey };
int mTextPadLeft = 0;
int mTextPadRight = 0;
int mTextPadTop = 0;
int mTextPadBottom = 0;
EAlignment mTextAlignment = LEFT;
bool mWrap = false;
};

View File

@@ -0,0 +1,114 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UITextButton.h>
#include <UI/UIManager.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(UITextButton)
{
JPH_ADD_BASE_CLASS(UITextButton, UIStaticText)
}
void UITextButton::CopyTo(UIElement *ioElement) const
{
UIStaticText::CopyTo(ioElement);
UITextButton *element = StaticCast<UITextButton>(ioElement);
element->mDownTextColor = mDownTextColor;
element->mHighlightTextColor = mHighlightTextColor;
element->mSelectedTextColor = mSelectedTextColor;
element->mRepeatStartTime = mRepeatStartTime;
element->mRepeatTime = mRepeatTime;
element->mClickAction = mClickAction;
}
bool UITextButton::MouseDown(int inX, int inY)
{
if (UIStaticText::MouseDown(inX, inY))
return true;
if (Contains(inX, inY))
{
mPressed = true;
mIsRepeating = false;
mRepeatTimeLeft = mRepeatStartTime;
return true;
}
return false;
}
bool UITextButton::MouseUp(int inX, int inY)
{
if (UIStaticText::MouseUp(inX, inY))
return true;
if (mPressed)
{
mPressed = false;
if (!mIsRepeating && Contains(inX, inY))
{
HandleUIEvent(EVENT_BUTTON_DOWN, this);
if (mClickAction)
mClickAction();
}
return true;
}
return false;
}
bool UITextButton::MouseMove(int inX, int inY)
{
if (UIStaticText::MouseMove(inX, inY))
return true;
return mPressed;
}
void UITextButton::MouseCancel()
{
UIStaticText::MouseCancel();
mPressed = false;
}
void UITextButton::Update(float inDeltaTime)
{
UIStaticText::Update(inDeltaTime);
if (mPressed && mRepeatStartTime > 0)
{
// Check repeat
mRepeatTimeLeft -= inDeltaTime;
if (mRepeatTimeLeft <= 0.0f)
{
// We're repeating
mIsRepeating = true;
mRepeatTimeLeft = mRepeatTime;
HandleUIEvent(EVENT_BUTTON_DOWN, this);
if (mClickAction)
mClickAction();
}
}
}
void UITextButton::DrawCustom() const
{
UIStaticText::DrawCustom(IsDisabled()? mDisabledTextColor : (mPressed? mDownTextColor : (mIsHighlighted? mHighlightTextColor : (mIsSelected? mSelectedTextColor : mTextColor))));
}
void UITextButton::Draw() const
{
DrawCustom();
// Skip direct base class, we modify the text color
UIElement::Draw();
}

View File

@@ -0,0 +1,55 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UIStaticText.h>
/// Clickable text button
class UITextButton : public UIStaticText
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, UITextButton)
using ClickAction = function<void()>;
/// Properties
void SetDownColor(ColorArg inColor) { mDownTextColor = inColor; }
void SetHighlightColor(ColorArg inColor) { mHighlightTextColor = inColor; }
void SetSelectedColor(ColorArg inColor) { mSelectedTextColor = inColor; }
void SetRepeat(float inRepeatStartTime, float inRepeatTime) { mRepeatStartTime = inRepeatStartTime; mRepeatTime = inRepeatTime; }
void SetClickAction(ClickAction inAction) { mClickAction = inAction; }
/// Cloning / copying
virtual void CopyTo(UIElement *ioElement) const override;
/// Actions
virtual bool MouseDown(int inX, int inY) override;
virtual bool MouseUp(int inX, int inY) override;
virtual bool MouseMove(int inX, int inY) override;
virtual void MouseCancel() override;
/// Update element
virtual void Update(float inDeltaTime) override;
/// Draw element
virtual void Draw() const override;
protected:
/// Draw element custom
void DrawCustom() const;
/// Properties
Color mDownTextColor { Color::sGrey };
Color mHighlightTextColor { Color::sWhite };
Color mSelectedTextColor { Color::sWhite };
float mRepeatStartTime = -1.0f;
float mRepeatTime = 0.5f;
ClickAction mClickAction;
/// State
bool mPressed = false;
bool mIsRepeating = false;
float mRepeatTimeLeft = 0;
};

View File

@@ -0,0 +1,36 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/Texture.h>
/// Helper class that points to a subsection of a texture for rendering it as a quad. Can specify borders which won't scale (only inner part of the quad will scale).
class UITexturedQuad
{
public:
/// Constructor
UITexturedQuad() = default;
UITexturedQuad(const Texture *inTexture) : mTexture(inTexture), mWidth(inTexture->GetWidth()), mHeight(inTexture->GetHeight()) { }
UITexturedQuad(const Texture *inTexture, int inX, int inY, int inWidth, int inHeight) : mTexture(inTexture), mX(inX), mY(inY), mWidth(inWidth), mHeight(inHeight) { }
UITexturedQuad(const Texture *inTexture, int inX, int inY, int inWidth, int inHeight, int inInnerX, int inInnerY, int inInnerWidth, int inInnerHeight) : mTexture(inTexture), mX(inX), mY(inY), mWidth(inWidth), mHeight(inHeight), mInnerX(inInnerX), mInnerY(inInnerY), mInnerWidth(inInnerWidth), mInnerHeight(inInnerHeight) { }
/// Check if this quad consists of 9 parts
bool HasInnerPart() const { return mInnerX >= 0 && mInnerY >= 0 && mInnerWidth >= 0 && mInnerHeight >= 0; }
/// The texture to use
RefConst<Texture> mTexture;
/// These are the normal texel coordinates for the quad
int mX = 0;
int mY = 0;
int mWidth = 0;
int mHeight = 0;
/// This quad can also scale its inner part leaving borders in tact, in this case the inner scaling part is defined here
int mInnerX = -1;
int mInnerY = -1;
int mInnerWidth = -1;
int mInnerHeight = -1;
};

View File

@@ -0,0 +1,25 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <UI/UIVerticalStack.h>
JPH_IMPLEMENT_RTTI_VIRTUAL(UIVerticalStack)
{
JPH_ADD_BASE_CLASS(UIVerticalStack, UIElement)
}
void UIVerticalStack::AutoLayout()
{
UIElement::AutoLayout();
mHeight.Set(0, PIXELS);
for (UIElement *e : mChildren)
if (e->IsVisible() || mPlaceInvisibleChildren)
{
e->SetRelativeY(GetHeight());
mHeight.Set(GetHeight() + e->GetHeight() + e->GetPaddingBottom() + mDeltaY, PIXELS);
}
}

View File

@@ -0,0 +1,21 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <UI/UIElement.h>
/// Layout class that will automatically layout child elements vertically, stacking them
class UIVerticalStack : public UIElement
{
public:
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, UIVerticalStack)
/// Calculate auto layout
virtual void AutoLayout() override;
private:
int mDeltaY = 0;
bool mPlaceInvisibleChildren = false;
};