Totally refactored UILabel

This commit is contained in:
2023-03-05 23:42:15 -08:00
parent aeb5014a07
commit ee2dc931f2
15 changed files with 463 additions and 194 deletions

Submodule lib/SDL updated: c9aec268fa...87a83787a3

View File

@ -132,7 +132,6 @@ void RenderPipeline::renderSceneCamera(Scene *scene, Camera *camera) {
auto itCanvas = canvases.begin();
while(itCanvas != canvases.end()) {
auto canvas = *itCanvas;
glm::mat4 model;
glm::mat4 projection;
glm::mat4 view;
@ -140,32 +139,23 @@ void RenderPipeline::renderSceneCamera(Scene *scene, Camera *camera) {
case UI_DRAW_TYPE_WORLD_ABSOLUTE:
projection = camera->getProjection();
view = camera->transform->getWorldTransform();
model = canvas->transform->getWorldTransform();
break;
case UI_DRAW_TYPE_WORLD_CAMERA_RELATIVE:
projection = glm::ortho(0.0f, renderTarget->getWidth(), renderTarget->getHeight(), 0.0f);
view = glm::mat4(1.0f);
model = canvas->transform->getWorldTransform();
break;
default:
assertUnreachable();
}
auto renderables = canvas->item->findChildrenDeep<UIComponentRendaerable>();
auto renderables = canvas->item->findChildrenDeep<UIComponentRenderable>();
auto itChild = renderables.begin();
while(itChild != renderables.end()) {
vectorAppend(&shaderPassItems,(*itChild)->getPassItems(projection, view));
++itChild;
}
// auto itChild = canvas->children.begin();
// while(itChild != canvas->children.end()) {
// vectorAppend(&shaderPassItems, (*itChild)->getPassItems(
// projection, view, model
// ));
// ++itChild;
// }
++itCanvas;
}

View File

@ -115,17 +115,24 @@ namespace Dawn {
return children;
}
/**
* Finds all children, and children of children, recursively that match
* the queried component.
*
* @tparam T Component Type to find.
* @return Array of pointers to matching children components.
*/
template<class T>
std::vector<T*> findChildrenDeep() {
std::vector<Transform*> itemsToCheck = this->transform.children;
std::vector<Transform*> transformsToCheck = this->transform.children;
std::vector<T*> itemsFound;
while(itemsToCheck.size() > 0) {
auto item = itemsToCheck.begin();
vectorAppend(&itemsToCheck, (*item)->children);
auto component = (*item)->item->getComponent<T>();
while(transformsToCheck.size() > 0) {
auto tras = transformsToCheck.begin();
vectorAppend(&transformsToCheck, (*tras)->children);
auto component = (*tras)->item->getComponent<T>();
if(component != nullptr) itemsFound.push_back(component);
itemsToCheck.erase(item);
transformsToCheck.erase(tras);
}
return itemsFound;

View File

@ -18,7 +18,7 @@ Camera::Camera(SceneItem *item) :
orthoBottom(0.0f),
orthoTop(1.0f),
clipNear(0.001f),
clipFar(50.0f)
clipFar(1000.0f)
{
}

View File

@ -5,6 +5,7 @@
#include "UICanvas.hpp"
#include "game/DawnGame.hpp"
#include "UIComponent.hpp"
using namespace Dawn;
@ -13,10 +14,47 @@ UICanvas * UICanvas::create(Scene *scene) {
return item->addComponent<UICanvas>();
}
UICanvas::UICanvas(SceneItem *item) : SceneItemComponent(item) {
UICanvas::UICanvas(SceneItem *item) :
SceneItemComponent(item),
camera(nullptr)
{
}
float_t UICanvas::getWidth() {
return w;
}
float_t UICanvas::getHeight() {
return h;
}
float_t UICanvas::getContentWidth() {
return this->getWidth();
}
float_t UICanvas::getContentHeight() {
return this->getHeight();
}
void UICanvas::onStart() {
if(camera == nullptr) camera = getScene()->findComponent<Camera>();
useEffectWithTeardown([&]{
if(camera == nullptr) return evtRenderResize = [&] {};
this->w = camera->getRenderTarget()->getWidth();
this->h = camera->getRenderTarget()->getHeight();
return evtRenderResize = useEvent([&](float_t w, float_t h){
this->w = w;
this->h = h;
auto comps = this->item->findChildren<UIComponent>();
auto itComps = comps.begin();
while(itComps != comps.end()) {
(*itComps)->alignmentNeedsUpdating = true;
++itComps;
}
}, camera->eventRenderTargetResized);
}, camera)();
}
void UICanvas::onDispose() {

View File

@ -9,13 +9,26 @@
#include "scene/components/display/Camera.hpp"
namespace Dawn {
class UIComponentDimensional {
public:
virtual float_t getWidth() = 0;
virtual float_t getHeight() = 0;
virtual float_t getContentWidth() = 0;
virtual float_t getContentHeight() = 0;
};
enum UIDrawType {
UI_DRAW_TYPE_WORLD_ABSOLUTE,
UI_DRAW_TYPE_WORLD_CAMERA_RELATIVE,
UI_DRAW_TYPE_CAMERA_OVERLAY
// UI_DRAW_TYPE_CAMERA_OVERLAY
};
class UICanvas : public SceneItemComponent {
class UICanvas : public SceneItemComponent, public UIComponentDimensional {
private:
std::function<void()> evtRenderResize;
float_t w = 1;
float_t h = 1;
public:
/**
* Creates a UI Canvas Scene Item Element, and attaches it to the provided
@ -27,7 +40,7 @@ namespace Dawn {
static UICanvas * create(Scene *scene);
//======================================================================//
StateProperty<Camera*> camera;
enum UIDrawType drawType = UI_DRAW_TYPE_WORLD_CAMERA_RELATIVE;
/**
@ -37,6 +50,11 @@ namespace Dawn {
*/
UICanvas(SceneItem *item);
float_t getWidth() override;
float_t getHeight() override;
float_t getContentWidth() override;
float_t getContentHeight() override;
void onStart() override;
void onDispose() override;
};

View File

@ -7,6 +7,113 @@
using namespace Dawn;
UIComponent::UIComponent(SceneItem *item) : SceneItemComponent(item) {
UIComponent::UIComponent(SceneItem *item) :
SceneItemComponent(item),
alignment(glm::vec4(0, 0, 128, 128)),
alignX(UI_COMPONENT_ALIGN_START),
alignY(UI_COMPONENT_ALIGN_START),
alignmentNeedsUpdating(true)
{
}
UIComponentDimensional * UIComponent::getParentDimensional() {
auto parent = this->transform->getParent();
if(!parent) return nullptr;
auto dimensional = parent->item->getComponent<UIComponentDimensional>();
assertNotNull(dimensional);
return dimensional;
}
void UIComponent::updateAlignment() {
if(!this->alignmentNeedsUpdating) return;
auto dimensional = this->getParentDimensional();
auto align = (glm::vec4)this->alignment;
auto translate = this->transform->getLocalPosition();
UIComponent::calculateDimensions(
this->alignX,
&translate.x,
&this->width,
dimensional->getWidth(),
this->getContentWidth(),
glm::vec2(align[0], align[2])
);
UIComponent::calculateDimensions(
this->alignY,
&translate.y,
&this->height,
dimensional->getHeight(),
this->getContentHeight(),
glm::vec2(align[1], align[3])
);
this->transform->setLocalPosition(translate);
this->alignmentNeedsUpdating = false;
this->eventAlignmentUpdated.invoke();
}
void UIComponent::calculateDimensions(
enum UIComponentAlign align,
float_t *position,
float_t *size,
float_t outerSize,
float_t innerSize,
glm::vec2 alignment
) {
assertNotNull(position);
assertNotNull(size);
switch(align) {
case UI_COMPONENT_ALIGN_STRETCH:
*position = alignment[0];
*size = outerSize + (alignment[0] + alignment[1]);
break;
case UI_COMPONENT_ALIGN_START:
*position = alignment[0];
*size = alignment[1];
break;
case UI_COMPONENT_ALIGN_MIDDLE:
*size = mathMax<float_t>(innerSize, alignment[1]);
*position = (outerSize / 2.0f) - (innerSize / 2.0f) + alignment[0];
break;
case UI_COMPONENT_ALIGN_END:
*size = alignment[0];
*position = outerSize - innerSize - alignment[1];
break;
default:
assertUnreachable();
break;
}
}
float_t UIComponent::getWidth() {
return this->width;
}
float_t UIComponent::getHeight() {
return this->height;
}
void UIComponent::onStart() {
useEffect([&]{
this->alignmentNeedsUpdating = true;
}, { &alignment, &alignX, &alignY });
useEffect([&]{
this->updateAlignment();
auto child = this->item->findChildren<UIComponent>();
auto itChild = child.begin();
while(itChild != child.end()) {
(*itChild)->alignmentNeedsUpdating = true;
++itChild;
}
}, alignmentNeedsUpdating)();
}

View File

@ -6,20 +6,71 @@
#pragma once
#include "scene/SceneItemComponent.hpp"
#include "UICanvas.hpp"
#include "display/shader/Shader.hpp"
#include "util/mathutils.hpp"
namespace Dawn {
class UIComponentRendaerable {
public:
int32_t cum;
enum UIComponentAlign {
UI_COMPONENT_ALIGN_START,
UI_COMPONENT_ALIGN_MIDDLE,
UI_COMPONENT_ALIGN_END,
UI_COMPONENT_ALIGN_STRETCH
};
class UIComponent : public SceneItemComponent {
private:
UICanvas *canvas = nullptr;
class UIComponentRenderable {
public:
virtual std::vector<struct ShaderPassItem> getPassItems(
glm::mat4 projection,
glm::mat4 view
) = 0;
};
class UIComponent : public SceneItemComponent, public UIComponentDimensional {
protected:
// Calculated (and cached) values
float_t width = 1;
float_t height = 1;
StateEvent<> eventAlignmentUpdated;
UIComponentDimensional * getParentDimensional();
void updateAlignment();
public:
StateProperty<bool_t> alignmentNeedsUpdating;
/**
* Method used to calculate alignment dimensions.
*
* @param align Alignment value enumator.
* @param position Output position floating point.
* @param size Output size floating point.
* @param outerSize Outer size (of the parent).
* @param innerSize Inner size (of this element's content).
* @param alignment Alignment settings.
*/
static void calculateDimensions(
enum UIComponentAlign align,
float_t *position,
float_t *size,
float_t outerSize,
float_t innerSize,
glm::vec2 alignment
);
//
StateProperty<UIComponentAlign> alignX;
StateProperty<UIComponentAlign> alignY;
StateProperty<glm::vec4> alignment;
UIComponent(SceneItem *item);
float_t getWidth() override;
float_t getHeight() override;
void onStart() override;
friend class UICanvas;
};
}

View File

@ -4,6 +4,7 @@
// https://opensource.org/licenses/MIT
#include "UILabel.hpp"
#include "game/DawnGame.hpp"
using namespace Dawn;
@ -11,39 +12,100 @@ UILabel::UILabel(SceneItem *item) :
UIComponent(item),
text(""),
fontSize(10.0f),
font(nullptr)
font(nullptr),
maxWidth(UI_LABEL_MAX_WIDTH_NONE)
{
}
bool_t UILabel::hasText() {
return (
this->font != nullptr &&
this->font->isReady() &&
((std::string)this->text).size() > 0
);
}
void UILabel::updateMesh() {
if(!this->needsRebuffering || !this->hasText) return;
if(((Font*)this->font) == nullptr || !this->font->isReady()) return;
if(!this->needsRebuffering) return;
if(!this->hasText()) return;
// float_t width = this->width;
// if(width == 0) width = -1;
// std::string text = this->getGame()->localeManager.getString(key);
float_t width = this->maxWidth;
assertTrue(width == 0 || width == -1 || width > 0);
if(width == 0) {
auto dimensional = this->getParentDimensional();
auto align = (glm::vec4)this->alignment;
float_t x;
UIComponent::calculateDimensions(
this->alignX,
&x,
&width,
dimensional->getWidth(),
0,
glm::vec2(align[0], align[2])
);
}
this->font->buffer(
text,
fontSize,
-1,
width,
&this->mesh,
&this->measure
);
this->needsRebuffering = false;
}
std::vector<struct ShaderPassItem> UILabel::getPassItems(
glm::mat4 proj, glm::mat4 view
) {
if(!this->hasText()) return {};
this->updateMesh();
struct ShaderPassItem item;
auto shader = &getGame()->renderManager.uiShaderProgram;
item.shaderProgram = shader;
item.colorValues[shader->paramColor] = textColor;
item.matrixValues[shader->paramProjection] = proj;
item.matrixValues[shader->paramView] = view;
item.matrixValues[shader->paramModel] = this->transform->getWorldTransform();
item.boolValues[shader->paramHasTexture] = true;
item.textureSlots[0] = this->font->getTexture();
item.textureValues[shader->paramTexture] = 0;
item.start = this->startQuad * QUAD_INDICE_COUNT;
item.count = this->quadCount == -1 ? -1 : this->quadCount * QUAD_INDICE_COUNT;
item.w = this->transform->getWorldPosition().z;
item.renderFlags = RENDER_MANAGER_RENDER_FLAG_BLEND;
item.mesh = &mesh;
return { item };
}
float_t UILabel::getContentWidth() {
if(this->maxWidth > 0) return this->maxWidth;
if(!this->hasText()) return 0;
this->updateMesh();
return this->measure.getWidth();
}
float_t UILabel::getContentHeight() {
if(!this->hasText()) return 0;
this->updateMesh();
return this->measure.getHeight();
}
void UILabel::onStart() {
UIComponent::onStart();
useEffect([&]{
hasText = ((std::string)text).size() > 0;
needsRebuffering = true;
}, text);
alignmentNeedsUpdating = true;
}, { &fontSize, &font, &text, &maxWidth });
useEffect([&]{
needsRebuffering = true;
}, fontSize);
}, alignmentNeedsUpdating);
useEffect([&]{
useEvent([&]{
needsRebuffering = true;
}, font);
}, eventAlignmentUpdated);
}

View File

@ -7,20 +7,23 @@
#include "UIComponent.hpp"
#include "display/font/Font.hpp"
#define UI_LABEL_MAX_WIDTH_NONE -1
#define UI_LABEL_MAX_WIDTH_ALIGN 0
namespace Dawn {
class UILabel : public UIComponent {
class UILabel : public UIComponent, public UIComponentRenderable {
private:
bool_t needsRebuffering = true;
bool_t hasText = false;
Mesh mesh;
bool_t hasText();
void updateMesh();
public:
StateProperty<std::string> text;
StateProperty<float_t> fontSize;
StateProperty<Font*> font;
StateProperty<float_t> maxWidth;
struct Color textColor = COLOR_MAGENTA;
struct FontMeasure measure;
int32_t startQuad = 0;
@ -28,6 +31,11 @@ namespace Dawn {
UILabel(SceneItem *item);
float_t getContentWidth() override;
float_t getContentHeight() override;
std::vector<struct ShaderPassItem> getPassItems(
glm::mat4 projection, glm::mat4 view
) override;
void onStart() override;
};
}

View File

@ -1,98 +1,98 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "StateInterfaces.hpp"
namespace Dawn {
template<class V>
class StateProperty : public IStateProperty {
private:
/**
* Method that is invoked every time that the value of this state property
* is updated.
*
* @param val Value that is to be used for this property.
*/
void setInternal(V val) {
if(val == this->_realValue) return;// TODO: can I omit this? kinda bad tbh.
// Run the teardowns
auto itTeardown = this->_effectTeardowns.begin();
while(itTeardown != this->_effectTeardowns.end()) {
(*itTeardown)();
++itTeardown;
}
this->_effectTeardowns.clear();
// Update the values
this->previous = this->_realValue;
this->_realValue = val;
// Notify the effect listeners
auto itEffect = this->_effectListners.begin();
while(itEffect != this->_effectListners.end()) {
(*itEffect)();
++itEffect;
}
// Notify the teardown effect listeners
auto itWithTeardown = this->_effectListnersWithTeardown.begin();
while(itWithTeardown != this->_effectListnersWithTeardown.end()) {
auto teardown = (*itWithTeardown)();
this->_effectTeardowns.push_back(teardown);
++itWithTeardown;
}
}
public:
V _realValue;
V previous;
/**
* Creates a new state property and listens for its change.
*/
StateProperty() {}
/**
* Creates a new state property and listens for its change.
* @param initial The initial value of this state.
*/
StateProperty(V initial) : _realValue(initial), previous(initial) {}
const StateProperty& operator += (const V &value) {
this->setInternal(this->_realValue + value);
return *this;
}
const bool_t operator != (const V &value) {
return value != this->_realValue;
}
const bool_t operator == (const V &value) {
return value == this->_realValue;
}
const StateProperty& operator = (const V &val) {
this->setInternal(val);
return *this;
}
const V operator->() const {
return this->_realValue;
}
operator V() const {
return this->_realValue;
}
/**
* Destructor for StateProperty.
*/
~StateProperty() {}
friend class StateOwner;
};
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "StateInterfaces.hpp"
namespace Dawn {
template<class V>
class StateProperty : public IStateProperty {
private:
/**
* Method that is invoked every time that the value of this state property
* is updated.
*
* @param val Value that is to be used for this property.
*/
void setInternal(V val) {
if(val == this->_realValue) return;// TODO: can I omit this? kinda bad tbh.
// Run the teardowns
auto itTeardown = this->_effectTeardowns.begin();
while(itTeardown != this->_effectTeardowns.end()) {
(*itTeardown)();
++itTeardown;
}
this->_effectTeardowns.clear();
// Update the values
this->previous = this->_realValue;
this->_realValue = val;
// Notify the effect listeners
auto itEffect = this->_effectListners.begin();
while(itEffect != this->_effectListners.end()) {
(*itEffect)();
++itEffect;
}
// Notify the teardown effect listeners
auto itWithTeardown = this->_effectListnersWithTeardown.begin();
while(itWithTeardown != this->_effectListnersWithTeardown.end()) {
auto teardown = (*itWithTeardown)();
this->_effectTeardowns.push_back(teardown);
++itWithTeardown;
}
}
public:
V _realValue;
V previous;
/**
* Creates a new state property and listens for its change.
*/
StateProperty() {}
/**
* Creates a new state property and listens for its change.
* @param initial The initial value of this state.
*/
StateProperty(V initial) : _realValue(initial), previous(initial) {}
const StateProperty& operator += (const V &value) {
this->setInternal(this->_realValue + value);
return *this;
}
const bool_t operator != (const V &value) {
return value != this->_realValue;
}
const bool_t operator == (const V &value) {
return value == this->_realValue;
}
const StateProperty& operator = (const V &val) {
this->setInternal(val);
return *this;
}
const V operator->() const {
return this->_realValue;
}
operator V() const {
return this->_realValue;
}
/**
* Destructor for StateProperty.
*/
~StateProperty() {}
friend class StateOwner;
};
}

View File

@ -1,43 +1,14 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "SimpleTexturedShaderProgram.hpp"
#define UI_SHADER_PROGRAM_PRIORITY 100
namespace Dawn {
class UIShaderProgram : public SimpleTexturedShaderProgram {
public:
struct ShaderPassItem getUIPassItem(
glm::mat4 projection,
glm::mat4 view,
glm::mat4 transform,
Texture *texture,
struct Color color,
Mesh *mesh,
float_t z
) {
struct ShaderPassItem item;
item.shaderProgram = this;
item.colorValues[this->paramColor] = color;
if(texture == nullptr) {
item.boolValues[this->paramHasTexture] = false;
} else {
item.textureSlots[0] = texture;
item.textureValues[this->paramTexture] = 0;
item.boolValues[this->paramHasTexture] = true;
}
item.matrixValues[this->paramProjection] = projection;
item.matrixValues[this->paramView] = view;
item.matrixValues[this->paramModel] = transform;
item.priority = UI_SHADER_PROGRAM_PRIORITY;
item.w = z;
item.renderFlags = RENDER_MANAGER_RENDER_FLAG_BLEND;
item.mesh = mesh;
return item;
}
};
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "SimpleTexturedShaderProgram.hpp"
#define UI_SHADER_PROGRAM_PRIORITY 100
namespace Dawn {
class UIShaderProgram : public SimpleTexturedShaderProgram {
};
}

View File

@ -86,7 +86,7 @@ void TicTacToeGame::onStart() {
}
if(isPlayerMove) {
if(getGame()->inputManager.isPressed(INPUT_BIND_MOUSE_CLICK)) {
if(getGame()->inputManager.isPressed(INPUT_BIND_MOUSE_CLICK) && hovered != nullptr) {
this->makeMove(hovered->tile, nextMove);
}
} else if(nextMove != TIC_TAC_TOE_NOUGHT) {

View File

@ -15,15 +15,18 @@
namespace Dawn {
class TicTacToeScene : public Scene {
class TicTacToeScene : public Scene, public StateOwner {
protected:
Camera *camera;
int32_t age = 0;
std::function<void()> evtUnsub;
UILabel *label;
StateProperty<int32_t> test;
void stage() override {
camera = Camera::create(this);
camera->transform->lookAt(glm::vec3(0, 0, 8), glm::vec3(0, 0, 0));
// camera->transform->lookAt(glm::vec3(0, 0, 8), glm::vec3(0, 0, 0));
camera->transform->lookAt(glm::vec3(32, 32, 32), glm::vec3(0, 0, 0));
float_t s = 2.0f;
camera->orthoTop = s;
@ -49,10 +52,24 @@ namespace Dawn {
auto canvas = canvasItem->addComponent<UICanvas>();
auto labelItem = this->createSceneItem();
auto label = labelItem->addComponent<UILabel>();
label = labelItem->addComponent<UILabel>();
label->font = &this->game->assetManager.get<TrueTypeAsset>("truetype_bizudp")->font;
label->text = "Hello World";
label->fontSize = 36.0f;
labelItem->transform.setParent(canvas->transform);
label->alignX = UI_COMPONENT_ALIGN_MIDDLE;
label->alignY = UI_COMPONENT_ALIGN_MIDDLE;
label->alignment = glm::vec4(0, 0, 350, 100);
label->maxWidth = 0;
useEffect([&]{
label->text = "Hello " + std::to_string(test) + " world";
}, test);
useEvent([&](float_t delta){
test = test + 1;
}, this->eventSceneUpdate);
}
std::vector<Asset*> getRequiredAssets() override {
@ -65,6 +82,6 @@ namespace Dawn {
}
public:
TicTacToeScene(DawnGame *game) : Scene(game) {}
TicTacToeScene(DawnGame *game) : Scene(game), test(0) {}
};
}