// SPDX-License-Identifier: MIT // // ES-DE Frontend // GuiComponent.cpp // // Basic GUI component handling such as placement, rotation, Z-order, rendering and animation. // #include "GuiComponent.h" #include "Log.h" #include "ThemeData.h" #include "Window.h" #include "animations/Animation.h" #include "renderers/Renderer.h" #include GuiComponent::GuiComponent() : mWindow {Window::getInstance()} , mParent {nullptr} , mThemeGameSelectorEntry {0} , mColor {0} , mColorShift {0} , mColorShiftEnd {0} , mColorOriginalValue {0} , mColorChangedValue {0} , mComponentThemeFlags {0} , mPosition {0.0f, 0.0f, 0.0f} , mOrigin {0.0f, 0.0f} , mRotationOrigin {0.5f, 0.5f} , mSize {0.0f, 0.0f} , mStationary {Stationary::NEVER} , mRenderDuringTransitions {true} , mBrightness {0.0f} , mOpacity {1.0f} , mSaturation {1.0f} , mDimming {1.0f} , mThemeOpacity {1.0f} , mThemeSaturation {1.0f} , mRotation {0.0f} , mScale {1.0f} , mDefaultZIndex {0.0f} , mZIndex {0.0f} , mIsProcessing {false} , mVisible {true} , mEnabled {true} , mTransform {Renderer::getIdentity()} { for (unsigned char i {0}; i < MAX_ANIMATIONS; ++i) mAnimationMap[i] = nullptr; } GuiComponent::~GuiComponent() { mWindow->removeGui(this); cancelAllAnimations(); if (mParent) mParent->removeChild(this); for (unsigned int i {0}; i < getChildCount(); ++i) getChild(i)->setParent(nullptr); } void GuiComponent::textInput(const std::string& text, const bool pasting) { for (auto it = mChildren.cbegin(); it != mChildren.cend(); ++it) (*it)->textInput(text, pasting); } bool GuiComponent::input(InputConfig* config, Input input) { for (unsigned int i {0}; i < getChildCount(); ++i) { if (getChild(i)->input(config, input)) return true; } return false; } void GuiComponent::update(int deltaTime) { updateSelf(deltaTime); updateChildren(deltaTime); } void GuiComponent::render(const glm::mat4& parentTrans) { if (!isVisible()) return; glm::mat4 trans {parentTrans * getTransform()}; renderChildren(trans); } void GuiComponent::setPosition(float x, float y, float z) { if (mPosition.x == x && mPosition.y == y && mPosition.z == z) return; mPosition = glm::vec3 {x, y, z}; onPositionChanged(); } void GuiComponent::setOrigin(float x, float y) { if (mOrigin.x == x && mOrigin.y == y) return; mOrigin = glm::vec2 {x, y}; onOriginChanged(); } void GuiComponent::setSize(const float w, const float h) { assert(w >= 0.0f && h >= 0.0f); if (mSize.x == w && mSize.y == h) return; mSize = glm::vec2 {w, h}; onSizeChanged(); } const glm::vec2 GuiComponent::getCenter() const { return glm::vec2 {mPosition.x - (getSize().x * mOrigin.x) + getSize().x / 2.0f, mPosition.y - (getSize().y * mOrigin.y) + getSize().y / 2.0f}; } void GuiComponent::addChild(GuiComponent* cmp) { mChildren.push_back(cmp); if (cmp->getParent()) cmp->getParent()->removeChild(cmp); cmp->setParent(this); } void GuiComponent::removeChild(GuiComponent* cmp) { if (!cmp->getParent()) return; if (cmp->getParent() != this) { LOG(LogError) << "Tried to remove child from incorrect parent!"; } cmp->setParent(nullptr); for (auto i {mChildren.cbegin()}; i != mChildren.cend(); ++i) { if (*i == cmp) { mChildren.erase(i); return; } } } void GuiComponent::sortChildren() { std::stable_sort(mChildren.begin(), mChildren.end(), [](GuiComponent* a, GuiComponent* b) { return b->getZIndex() > a->getZIndex(); }); } const int GuiComponent::getChildIndex() const { std::vector::iterator it { std::find(getParent()->mChildren.begin(), getParent()->mChildren.end(), this)}; if (it != getParent()->mChildren.end()) return static_cast(std::distance(getParent()->mChildren.begin(), it)); else return -1; } void GuiComponent::setAnimation(Animation* anim, int delay, std::function finishedCallback, bool reverse, unsigned char slot) { assert(slot < MAX_ANIMATIONS); AnimationController* oldAnim {mAnimationMap[slot]}; mAnimationMap[slot] = new AnimationController(anim, delay, finishedCallback, reverse); if (oldAnim) delete oldAnim; } const bool GuiComponent::stopAnimation(unsigned char slot) { assert(slot < MAX_ANIMATIONS); if (mAnimationMap[slot]) { delete mAnimationMap[slot]; mAnimationMap[slot] = nullptr; return true; } else { return false; } } const bool GuiComponent::cancelAnimation(unsigned char slot) { assert(slot < MAX_ANIMATIONS); if (mAnimationMap[slot]) { mAnimationMap[slot]->removeFinishedCallback(); delete mAnimationMap[slot]; mAnimationMap[slot] = nullptr; return true; } else { return false; } } const bool GuiComponent::finishAnimation(unsigned char slot) { assert(slot < MAX_ANIMATIONS); AnimationController* anim {mAnimationMap[slot]}; if (anim) { // Skip to animation's end. const bool done {anim->update(anim->getAnimation()->getDuration() - anim->getTime())}; if (done) { mAnimationMap[slot] = nullptr; delete anim; // Will also call finishedCallback. } return true; } else { return false; } } const bool GuiComponent::advanceAnimation(unsigned char slot, unsigned int time) { assert(slot < MAX_ANIMATIONS); AnimationController* anim {mAnimationMap[slot]}; if (anim) { bool done {anim->update(time)}; if (done) { mAnimationMap[slot] = nullptr; delete anim; // Will also call finishedCallback. } return true; } else { return false; } } void GuiComponent::stopAllAnimations() { for (unsigned char i {0}; i < MAX_ANIMATIONS; ++i) stopAnimation(i); } void GuiComponent::cancelAllAnimations() { for (unsigned char i {0}; i < MAX_ANIMATIONS; ++i) cancelAnimation(i); } void GuiComponent::setBrightness(float brightness) { if (mBrightness == brightness) return; mBrightness = brightness; for (auto it = mChildren.cbegin(); it != mChildren.cend(); ++it) (*it)->setBrightness(brightness); } void GuiComponent::setOpacity(float opacity) { if (mOpacity == opacity) return; mOpacity = opacity; for (auto it = mChildren.cbegin(); it != mChildren.cend(); ++it) (*it)->setOpacity(opacity); } void GuiComponent::setDimming(float dimming) { if (mDimming == dimming) return; mDimming = dimming; for (auto it = mChildren.cbegin(); it != mChildren.cend(); ++it) (*it)->setDimming(dimming); } const glm::mat4& GuiComponent::getTransform() { mTransform = Renderer::getIdentity(); mTransform = glm::translate(mTransform, glm::round(mPosition)); if (mScale != 1.0f) mTransform = glm::scale(mTransform, glm::vec3 {mScale}); if (mRotation != 0.0f) { // Calculate offset as difference between origin and rotation origin. glm::vec2 rotationSize {getRotationSize()}; float xOff {(mOrigin.x - mRotationOrigin.x) * rotationSize.x}; float yOff {(mOrigin.y - mRotationOrigin.y) * rotationSize.y}; // Transform to offset point. if (xOff != 0.0f || yOff != 0.0f) mTransform = glm::translate(mTransform, glm::vec3 {xOff * -1.0f, yOff * -1.0f, 0.0f}); // Apply rotation transform. mTransform = glm::rotate(mTransform, mRotation, glm::vec3 {0.0f, 0.0f, 1.0f}); // Transform back to original point. if (xOff != 0.0f || yOff != 0.0f) mTransform = glm::translate(mTransform, glm::vec3 {xOff, yOff, 0.0f}); } mTransform = glm::translate( mTransform, glm::round(glm::vec3 {mOrigin.x * mSize.x * -1.0f, mOrigin.y * mSize.y * -1.0f, 0.0f})); return mTransform; } void GuiComponent::onShow() { for (unsigned int i {0}; i < getChildCount(); ++i) getChild(i)->onShow(); } void GuiComponent::onHide() { for (unsigned int i {0}; i < getChildCount(); ++i) getChild(i)->onHide(); } void GuiComponent::applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) { const glm::vec2 scale {getParent() ? getParent()->getSize() : glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}}; const ThemeData::ThemeElement* elem {theme->getElement(view, element, "")}; if (!elem) return; using namespace ThemeFlags; if (properties & POSITION && elem->has("pos")) { glm::vec2 denormalized {elem->get("pos") * scale}; setPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f}); } if (properties & ThemeFlags::SIZE && elem->has("size")) { glm::vec2 size {(elem->get("size") * scale)}; if (size.x == 0.0f && size.y == 0.0f) setAutoCalcExtent(glm::ivec2 {1, 0}); else if (size.x != 0.0f && size.y == 0.0f) setAutoCalcExtent(glm::ivec2 {0, 1}); else if (size.x != 0.0f && size.y != 0.0f) setAutoCalcExtent(glm::ivec2 {0, 0}); setSize(size); } // Position + size also implies origin. if ((properties & ORIGIN || (properties & POSITION && properties & ThemeFlags::SIZE)) && elem->has("origin")) { setOrigin(glm::clamp(elem->get("origin"), 0.0f, 1.0f)); } if (properties & ThemeFlags::ROTATION) { if (elem->has("rotation")) setRotationDegrees(elem->get("rotation")); if (elem->has("rotationOrigin")) setRotationOrigin(glm::clamp(elem->get("rotationOrigin"), 0.0f, 1.0f)); } if (properties & ThemeFlags::Z_INDEX && elem->has("zIndex")) setZIndex(elem->get("zIndex")); else setZIndex(getDefaultZIndex()); if (properties & ThemeFlags::BRIGHTNESS && elem->has("brightness")) mBrightness = glm::clamp(elem->get("brightness"), -2.0f, 2.0f); if (properties & ThemeFlags::OPACITY && elem->has("opacity")) mThemeOpacity = glm::clamp(elem->get("opacity"), 0.0f, 1.0f); if (properties & ThemeFlags::VISIBLE && elem->has("visible") && !elem->get("visible")) mThemeOpacity = 0.0f; else setVisible(true); if (properties & ThemeFlags::SATURATION && elem->has("saturation")) mThemeSaturation = glm::clamp(elem->get("saturation"), 0.0f, 1.0f); if (properties && elem->has("gameselector")) mThemeGameSelector = elem->get("gameselector"); if (properties && elem->has("gameselectorEntry")) mThemeGameSelectorEntry = elem->get("gameselectorEntry"); } void GuiComponent::updateHelpPrompts() { if (getParent()) { getParent()->updateHelpPrompts(); return; } std::vector prompts {getHelpPrompts()}; if (mWindow->peekGui() == this) mWindow->setHelpPrompts(prompts, getHelpStyle()); } void GuiComponent::updateSelf(int deltaTime) { for (unsigned char i {0}; i < MAX_ANIMATIONS; ++i) advanceAnimation(i, deltaTime); } void GuiComponent::updateChildren(int deltaTime) { for (unsigned int i {0}; i < getChildCount(); ++i) getChild(i)->update(deltaTime); } void GuiComponent::renderChildren(const glm::mat4& transform) const { for (unsigned int i {0}; i < getChildCount(); ++i) getChild(i)->render(transform); }