Stripped back old shader code for now.

This commit is contained in:
2024-12-24 12:27:55 -06:00
parent 2b36a12335
commit e10aea20a1
81 changed files with 325 additions and 900 deletions

8
archive/Shader.cpp Normal file
View File

@ -0,0 +1,8 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "Shader.hpp"
using namespace Dawn;

259
archive/Shader.hpp Normal file
View File

@ -0,0 +1,259 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "display/shader/ShaderStage.hpp"
#include "display/shader/IShader.hpp"
#include "assert/assert.hpp"
#include "assert/assertgl.hpp"
#include "display/Color.hpp"
#include "display/Texture.hpp"
#include "ShaderParameter.hpp"
#include "ShaderStructure.hpp"
namespace Dawn {
typedef GLuint shadertexturebinding_t;
enum class ShaderOpenGLVariant {
GLSL_330_CORE
};
template<typename T>
class Shader : public IShader<T> {
private:
std::vector<std::shared_ptr<ShaderStage>> stages;
std::vector<struct ShaderParameter> parameters;
std::vector<struct IShaderStructure> structures;
enum ShaderOpenGLVariant variant;
GLuint shaderProgram = -1;
protected:
/**
* Overridable function to get the stages for the shader.
*
* @param variant The variant of the shader to use.
* @param rel The relative data to use.
* @param stages The stages to add to.
* @param parameters The parameters to add to.
* @param structures The structures to add to.
*/
virtual void getStages(
const enum ShaderOpenGLVariant variant,
const T *rel,
std::vector<std::shared_ptr<ShaderStage>> &stages,
std::vector<struct ShaderParameter> &parameters,
std::vector<struct IShaderStructure> &structures
) = 0;
public:
/**
* Initializes the shader, this needs to be called before the shader can
* be used.
*/
void init() override {
// Determine which kind of OpenGL shader to use.
variant = ShaderOpenGLVariant::GLSL_330_CORE;
// Now get the stages
T dummy;
this->getStages(
variant,
&dummy,
stages,
parameters,
structures
);
// Create the shader program
shaderProgram = glCreateProgram();
assertNoGLError();
// Attach all the stages
for(auto stage : stages) {
glAttachShader(shaderProgram, stage->id);
assertNoGLError();
}
// Link and verify the program
glLinkProgram(shaderProgram);
assertNoGLError();
GLint status;
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status);
assertNoGLError();
assertTrue(status == GL_TRUE, "Failed to link shader program.");
std::vector<std::string> uniformNames;
GLint numUniforms = 0;
// Get the number of active uniforms
glGetProgramiv(shaderProgram, GL_ACTIVE_UNIFORMS, &numUniforms);
assertNoGLError();
// Iterate through each uniform
// for (GLint i = 0; i < numUniforms; ++i) {
// char name[256];
// GLsizei length;
// GLint size;
// GLenum type;
// // Get the uniform name
// glGetActiveUniform(shaderProgram, i, sizeof(name), &length, &size, &type, name);
// assertNoGLError();
// std::cout << "Uniform: " << i << ":" << name << std::endl;
// // uniformNames.push_back(std::string(name));
// }
// Map parameters correctly.
std::for_each(
parameters.begin(),
parameters.end(),
[&](struct ShaderParameter &param) {
// Correct offset
param.offset = param.offset - (size_t)(&dummy);
param.location = glGetUniformLocation(
shaderProgram,
param.name.c_str()
);
assertNoGLError();
assertTrue(
param.location != -1,
"Failed to get location for parameter %s.",
param.name.c_str()
);
}
);
// Map structures
std::for_each(
structures.begin(),
structures.end(),
[&](struct IShaderStructure &structure) {
structure.offset = structure.offset - (size_t)(&dummy);
structure.location = glGetUniformBlockIndex(
shaderProgram,
structure.structureName.c_str()
);
assertNoGLError();
assertTrue(
structure.location != -1,
"Failed to get location for structure %s.",
structure.structureName.c_str()
);
// Create the buffer
glGenBuffers(1, &structure.buffer);
}
);
this->bind();
}
/**
* Binds the shader as the current one, does not upload any data, somewhat
* relies on something else uploading the data.
*/
void bind() override {
glUseProgram(shaderProgram);
assertNoGLError();
}
/**
* Uploads the data to the GPU.
*/
void upload() override {
switch(this->variant) {
case ShaderOpenGLVariant::GLSL_330_CORE:
for(auto param : parameters) {
void *value = (void*)(
((size_t)&this->data) + param.offset
);
switch(param.type) {
case ShaderParameterType::MAT4: {
glm::mat4 *matrix = (glm::mat4 *)value;
if(param.count != 1) {
assertUnreachable("I haven't implemented multiple mat4s");
}
glUniformMatrix4fv(
param.location, 1, GL_FALSE, glm::value_ptr(*matrix)
);
break;
}
case ShaderParameterType::COLOR: {
auto color = (Color *)value;
glUniform4fv(
param.location,
param.count,
(GLfloat*)value
);
break;
}
case ShaderParameterType::BOOLEAN: {
glUniform1iv(param.location, param.count, (GLint*)value);
break;
}
case ShaderParameterType::TEXTURE: {
glUniform1iv(param.location, param.count, (GLint*)value);
break;
}
default: {
assertUnreachable("Unsupported ShaderParameterType");
}
}
assertNoGLError();
}
break;
default:
assertUnreachable("Unsupported ShaderOpenGLVariant");
}
// Upload structures
for(auto structure : structures) {
switch(structure.structureType) {
case ShaderOpenGLStructureType::STD140: {
// Upload the data
glBindBuffer(GL_UNIFORM_BUFFER, structure.buffer);
assertNoGLError();
glBindBufferBase(GL_UNIFORM_BUFFER, structure.location, structure.buffer);
assertNoGLError();
glBufferData(
GL_UNIFORM_BUFFER,
structure.size * structure.count,
(void*)((size_t)&this->data + (size_t)structure.offset),
GL_STATIC_DRAW
);
assertNoGLError();
break;
}
default:
assertUnreachable("Unsupported ShaderOpenGLStructureType");
}
}
}
~Shader() {
// Delete the structures
for(auto structure : structures) {
assertTrue(structure.buffer != -1, "Invalid buffer.");
glDeleteBuffers(1, &structure.buffer);
assertNoGLError();
}
// Delete the shader program
glDeleteProgram(shaderProgram);
assertNoGLError();
}
};
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "ShaderParameter.hpp"
using namespace Dawn;
ShaderParameter::ShaderParameter(
const std::string &name,
const void *offset,
const enum ShaderParameterType type,
const size_t count
) {
this->name = name;
this->offset = (size_t)offset;
this->type = type;
this->count = count;
}

View File

@ -0,0 +1,33 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "display/shader/IShader.hpp"
#include "dawnopengl.hpp"
namespace Dawn {
struct ShaderParameter {
std::string name;
size_t offset;
enum ShaderParameterType type;
size_t count;
GLint location = -1;
/**
* Construct a new shader parameter.
*
* @param name Name of the parameter within the shader.
* @param offset Offset, relative to the structure of the data.
* @param type Type of the parameter.
* @param count How many elements in the array (if multiple).
*/
ShaderParameter(
const std::string &name,
const void *offset,
const enum ShaderParameterType type,
const size_t count = 1
);
};
}

74
archive/ShaderStage.cpp Normal file
View File

@ -0,0 +1,74 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "assert/assertgl.hpp"
#include "assert/assert.hpp"
#include "ShaderStage.hpp"
using namespace Dawn;
ShaderStage::ShaderStage(
const enum ShaderStageType type,
const std::string source
) : IShaderStage(type) {
// Get OpenGL Shader Type
GLenum shaderType;
switch(this->type) {
case ShaderStageType::VERTEX:
shaderType = GL_VERTEX_SHADER;
break;
case ShaderStageType::FRAGMENT:
shaderType = GL_FRAGMENT_SHADER;
break;
// case ShaderStageType::COMPUTE:
// shaderType = GL_COMPUTE;
// break;
default:
assertUnreachable("Unknown ShaderStageType");
}
// Initialize the shader
this->id = glCreateShader(shaderType);
assertNoGLError();
// Compile the shader
auto cSource = source.c_str();
glShaderSource(this->id, 1, &cSource, NULL);
assertNoGLError();
glCompileShader(this->id);
assertNoGLError();
// glShaderBinary(1, &this->id, GL_SHADER_BINARY_FORMAT_SPIR_V, source.data(), source.size());
// assertNoGLError();
// glSpecializeShader(this->id, "main", 0, NULL, NULL);
// assertNoGLError();
// Validate
GLint status;
glGetShaderiv(this->id, GL_COMPILE_STATUS, &status);
assertNoGLError();
if(!status) {
// Failed to compile
GLint logLength;
glGetShaderiv(this->id, GL_INFO_LOG_LENGTH, &logLength);
assertNoGLError();
GLchar *log = new GLchar[logLength];
glGetShaderInfoLog(this->id, logLength, NULL, log);
assertNoGLError();
assertUnreachable("Failed to compile shader stage %i:\n%s", type, log);
}
}
ShaderStage::~ShaderStage() {
if(this->id != -1) {
glDeleteShader(this->id);
assertNoGLError();
}
}

28
archive/ShaderStage.hpp Normal file
View File

@ -0,0 +1,28 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dawnopengl.hpp"
#include "display/shader/IShaderStage.hpp"
namespace Dawn {
class ShaderStage : public IShaderStage {
public:
GLuint id = -1;
/**
* Constructs a new ShaderStage.
*
* @param type The type of shader this is.
* @param source The source code to compile.
*/
ShaderStage(const enum ShaderStageType type, const std::string source);
/**
* Disposes of the shader stage.
*/
~ShaderStage();
};
}

View File

@ -0,0 +1,96 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma ocne
#include "display/shader/ShaderParameter.hpp"
namespace Dawn {
enum class ShaderOpenGLStructureType {
STD140
};
struct IShaderStructure {
std::string structureName;
size_t offset;
enum ShaderOpenGLStructureType structureType;
size_t size;
size_t count;
std::vector<struct ShaderParameter> parameters;
GLint location = -1;
GLuint buffer = -1;
};
template<typename T>
struct ShaderStructure final : public IShaderStructure {
public:
/**
* Constructs a new shader structure. Shader structures allow for larger
* amounts/volumes of data to be passed to the shader in a single call.
* Ideally I wouldn't really need this as I wanted the entire shader to
* basically be a single large structure, but OpenGL doesn't support that
* so I have to do this instead.
*
* @param structureName Structure name within the shader.
* @param offset Offset, within the data structure, that this structure
* starts at.
* @param structureType The type of structure data format to use.
* @param getParameters A callback that, when invoked, will populate the
* parameters vector with the parameters for this
* structure.
* @param count The number of structures to create.
*/
ShaderStructure(
const std::string &structureName,
const void *offset,
const enum ShaderOpenGLStructureType structureType,
std::function<
void(const T&, std::vector<struct ShaderParameter>&)
> getParameters,
size_t count = 1
) {
this->structureName = structureName;
this->offset = (size_t)offset;
this->structureType = structureType;
this->size = sizeof(T);
this->count = count;
this->parameters = std::vector<struct ShaderParameter>();
T dummy;
getParameters(dummy, this->parameters);
// Update offsets.
auto itParams = this->parameters.begin();
while(itParams != this->parameters.end()) {
struct ShaderParameter &param = *itParams;
param.offset -= (size_t)(&dummy);
// Check for non-aligned OpenGL structures.
if(param.offset % sizeof(glm::vec4) != 0) {
assertUnreachable(
"%s%s%s",
"Non-aligned OpenGL structure detected on param ",
param.name.c_str(),
"!\nEnsure you have padded correctly."
);
}
if(
itParams == (this->parameters.end() - 1) &&
count > 1 &&
(sizeof(T) % sizeof(glm::vec4)) != 0
) {
assertUnreachable(
"%s%s%s",
"Non-aligned OpenGL structure detected on last element in array structure on param ",
param.name.c_str(),
"!\nEnsure you have padded correctly."
);
}
++itParams;
}
}
};
}

View File

@ -0,0 +1,230 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "display/shader/SimpleTexturedShader.hpp"
#include <fstream>
#include "slang.h"
#include "slang-gfx.h"
using namespace slang;
using namespace Dawn;
void SimpleTexturedShader::getStages(
const enum ShaderOpenGLVariant variant,
const struct SimpleTexturedShaderData *rel,
std::vector<std::shared_ptr<ShaderStage>> &stages,
std::vector<struct ShaderParameter> &parameters,
std::vector<struct IShaderStructure> &structures
) {
// Stages
std::shared_ptr<ShaderStage> vertex;
std::shared_ptr<ShaderStage> fragment;
std::string shader = R"(
cbuffer Uniforms {
float4x4 u_Projection;
float4x4 u_View;
float4x4 u_Model;
float4 u_Color;
bool u_HasTexture;
uniform Sampler2D u_Texture;
};
struct AssembledVertex {
float3 position : POSITION;
float2 texcoord : TEXCOORD;
};
struct Fragment {
float4 color;
};
struct VertexStageOutput {
float2 uv : UV;
float4 sv_position : SV_Position;
};
[shader("vertex")]
VertexStageOutput vertexMain(
AssembledVertex assembledVertex
) {
VertexStageOutput output;
float3 position = assembledVertex.position;
output.uv = assembledVertex.texcoord;
output.sv_position = mul(
float4(position, 1.0),
mul(u_Model, mul(u_View, u_Projection))
);
return output;
}
[shader("fragment")]
Fragment fragmentMain(
float2 uv: UV
) : SV_Target {
Fragment output;
if(u_HasTexture) {
output.color = u_Texture.Sample(uv) * u_Color;
} else {
output.color = u_Color;
}
return output;
}
)";
Slang::ComPtr<IGlobalSession> globalSession;
createGlobalSession(globalSession.writeRef());
SessionDesc sessionDesc;
TargetDesc targetDesc;
targetDesc.format = SLANG_GLSL;
targetDesc.profile = globalSession->findProfile("glsl_330");
sessionDesc.targets = &targetDesc;
sessionDesc.targetCount = 1;
Slang::ComPtr<IBlob> diagnostics;
const char* searchPaths[] = { "/home/yourwishes/htdocs/Dawn/assets/shaders/" };
sessionDesc.searchPaths = searchPaths;
sessionDesc.searchPathCount = 1;
Slang::ComPtr<ISession> session;
globalSession->createSession(sessionDesc, session.writeRef());
auto module = session->loadModuleFromSourceString(
"hello-world.slang",
"hello-world.slang",
shader.c_str(),
diagnostics.writeRef()
);
if(diagnostics) {
assertUnreachable("Failed to load module %s", (const char*) diagnostics->getBufferPointer());
return;
}
Slang::ComPtr<IEntryPoint> vertexEntryPoint;
Slang::ComPtr<IEntryPoint> fragEntryPoint;
module->findEntryPointByName("vertexMain", vertexEntryPoint.writeRef());
module->findEntryPointByName("fragmentMain", fragEntryPoint.writeRef());
IComponentType* components[] = { module, vertexEntryPoint, fragEntryPoint };
Slang::ComPtr<IComponentType> program;
session->createCompositeComponentType(
components,
sizeof(components) / sizeof(components[0]),
program.writeRef()
);
Slang::ComPtr<IComponentType> linkedProgram;
auto result = program->link(linkedProgram.writeRef(), diagnostics.writeRef());
std::cout << "Result: " << result << std::endl;
if(diagnostics) {
assertUnreachable("%s\n", (const char*) diagnostics->getBufferPointer());
return;
}
int entryPointIndex = 0;
int targetIndex = 0; // only one target
Slang::ComPtr<IBlob> vertexBlob;
result = linkedProgram->getEntryPointCode(
entryPointIndex,
targetIndex,
vertexBlob.writeRef(),
diagnostics.writeRef()
);
if(diagnostics) {
assertUnreachable("%s\n", (const char*) diagnostics->getBufferPointer());
return;
}
slang::ProgramLayout* layout = program->getLayout();
unsigned parameterCount = layout->getParameterCount();
for(unsigned pp = 0; pp < parameterCount; pp++) {
slang::VariableLayoutReflection* parameter = layout->getParameterByIndex(pp);
std::cout << "Parameter: " << parameter->getName() << std::endl;
auto layout = parameter->getTypeLayout();
auto fields = layout->getFieldCount();
for(unsigned ff = 0; ff < fields; ff++) {
slang::VariableLayoutReflection* field = layout->getFieldByIndex(ff);
std::string fieldName = field->getName();
std::cout << "Field: " << fieldName << std::endl;
}
}
std::string vertexString = (const char*)vertexBlob->getBufferPointer();
entryPointIndex = 1;
Slang::ComPtr<IBlob> fragmentBlob;
result = linkedProgram->getEntryPointCode(
entryPointIndex,
targetIndex,
fragmentBlob.writeRef(),
diagnostics.writeRef()
);
if(diagnostics) {
assertUnreachable("%s\n", (const char*) diagnostics->getBufferPointer());
return;
}
std::string fragmentString = (const char*)fragmentBlob->getBufferPointer();
vertex = std::make_shared<ShaderStage>(
ShaderStageType::VERTEX, vertexString
);
stages.push_back(vertex);
fragment = std::make_shared<ShaderStage>(
ShaderStageType::FRAGMENT, fragmentString
);
stages.push_back(fragment);
structures.push_back(ShaderStructure<struct SimpleTexturedShaderDataSub>(
"block_SLANG_ParameterGroup_Uniforms_std140_0",
&rel->data,
ShaderOpenGLStructureType::STD140,
[](const SimpleTexturedShaderDataSub &data, std::vector<struct ShaderParameter> &parameters) {
parameters.push_back(ShaderParameter(
"u_Projection_0",
&data.projection,
ShaderParameterType::MAT4
));
parameters.push_back(ShaderParameter(
"u_View_0",
&data.view,
ShaderParameterType::MAT4
));
parameters.push_back(ShaderParameter(
"u_Model_0",
&data.model,
ShaderParameterType::MAT4
));
parameters.push_back(ShaderParameter(
"u_Color_0",
&data.color,
ShaderParameterType::COLOR
));
parameters.push_back(ShaderParameter(
"u_HasTexture_0",
&data.hasTexture,
ShaderParameterType::BOOLEAN
));
}
));
// Parameters
parameters.push_back(ShaderParameter(
"Uniforms_u_Texture_0",
&rel->texture,
ShaderParameterType::TEXTURE
));
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "display/shader/Shader.hpp"
namespace Dawn {
struct SimpleTexturedShaderDataSub {
glm::mat4 projection;
glm::mat4 view;
glm::mat4 model;
struct Color color = COLOR_WHITE;
bool hasTexture = false;
};
struct SimpleTexturedShaderData {
struct SimpleTexturedShaderDataSub data;
shadertexturebinding_t texture = 0;
};
class SimpleTexturedShader : public Shader<SimpleTexturedShaderData> {
protected:
void getStages(
const enum ShaderOpenGLVariant variant,
const struct SimpleTexturedShaderData *rel,
std::vector<std::shared_ptr<ShaderStage>> &stages,
std::vector<struct ShaderParameter> &parameters,
std::vector<struct IShaderStructure> &structures
) override;
};
}

194
archive/UIShader.cpp Normal file
View File

@ -0,0 +1,194 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "display/shader/UIShader.hpp"
#include "util/Macro.hpp"
using namespace Dawn;
void UIShader::getStages(
const enum ShaderOpenGLVariant variant,
const struct UIShaderData *rel,
std::vector<std::shared_ptr<ShaderStage>> &stages,
std::vector<struct ShaderParameter> &parameters,
std::vector<struct IShaderStructure> &structures
) {
// Stages
std::shared_ptr<ShaderStage> vertex;
std::shared_ptr<ShaderStage> fragment;
switch(variant) {
case ShaderOpenGLVariant::GLSL_330_CORE:
vertex = std::make_shared<ShaderStage>(
ShaderStageType::VERTEX,
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 1) in vec2 aTexCoord;\n"
"uniform mat4 u_Projection;\n"
"uniform mat4 u_View;\n"
"uniform mat4 u_Model;\n"
"struct UIShaderQuad {\n"
"vec4 quad;\n"
"vec4 uv;\n"
"vec4 color;\n"
"vec4 style;\n"
"};\n"
"layout (std140) uniform ub_Quad {\n"
"UIShaderQuad quads[" MACRO_STRINGIFY(UI_SHADER_QUAD_COUNT) "];\n"
"};\n"
"out vec2 v_TextCoord;\n"
"out vec4 v_Color;\n"
"out vec4 v_Style;\n"
"void main() {\n"
"vec4 pos;\n"
"vec2 coord;\n"
"int index = int(aPos.z);\n"
"int quadIndex = index / 4;\n"
"int vertexIndex = index % 4;\n"
"UIShaderQuad quad = quads[quadIndex];\n"
"if(vertexIndex == 0) {\n"
"pos.x = quad.quad.x;\n"
"pos.y = quad.quad.y;\n"
"coord.x = quad.uv.x;\n"
"coord.y = quad.uv.y;\n"
"} else if(vertexIndex == 1) {\n"
"pos.x = quad.quad.z;\n"
"pos.y = quad.quad.y;\n"
"coord.x = quad.uv.z;\n"
"coord.y = quad.uv.y;\n"
"} else if(vertexIndex == 2) {\n"
"pos.y = quad.quad.w;\n"
"pos.x = quad.quad.x;\n"
"coord.x = quad.uv.x;\n"
"coord.y = quad.uv.w;\n"
"} else if(vertexIndex == 3) {\n"
"pos.x = quad.quad.z;\n"
"pos.y = quad.quad.w;\n"
"coord.x = quad.uv.z;\n"
"coord.y = quad.uv.w;\n"
"}\n"
"pos.z = 0;\n"
"pos.w = 1;\n"
"gl_Position = u_Projection * u_View * u_Model * pos;\n"
"v_TextCoord = coord;\n"
"v_Color = quad.color;\n"
"v_Style = quad.style;\n"
"}"
);
fragment = std::make_shared<ShaderStage>(
ShaderStageType::FRAGMENT,
"#version 330 core\n"
"in vec2 v_TextCoord;\n"
"in vec4 v_Color;\n"
"in vec4 v_Style;\n"
"uniform sampler2D u_Texture[" MACRO_STRINGIFY(UI_SHADER_TEXTURE_COUNT) "];\n"
"out vec4 o_Color;\n"
"void main() {\n"
"vec4 texColor = vec4(1, 1, 1, 1);\n"
"int vStyle = int(round(v_Style[0]));\n"
"int vTextInd = int(round(v_Style[1]));\n"
"switch(vTextInd) {\n"
"case -1:\n"
"texColor = vec4(1, 1, 1, 1);\n"
"break;\n"
"case 0:\n"
"texColor = texture(u_Texture[0], v_TextCoord);\n"
"break;\n"
"case 1:\n"
"texColor = texture(u_Texture[1], v_TextCoord);\n"
"break;\n"
"case 2:\n"
"texColor = texture(u_Texture[2], v_TextCoord);\n"
"break;\n"
"case 3:\n"
"texColor = texture(u_Texture[3], v_TextCoord);\n"
"break;\n"
"case 4:\n"
"texColor = texture(u_Texture[4], v_TextCoord);\n"
"break;\n"
"case 5:\n"
"texColor = texture(u_Texture[5], v_TextCoord);\n"
"break;\n"
"}\n"
"switch(vStyle) {\n"
"case 0:\n"
"o_Color = texColor * v_Color;\n"
"break;\n"
"case 1:\n"
"o_Color.rgb = v_Color.rgb;\n"
"o_Color.a = texColor.r * v_Color.a;\n"
"break;\n"
"}\n"
"}\n"
);
break;
default:
assertUnreachable("Unsupported ShaderOpenGLVariant");
}
// Add stages
stages.push_back(vertex);
stages.push_back(fragment);
// Parameters
parameters.push_back(ShaderParameter(
"u_Projection",
&rel->projection,
ShaderParameterType::MAT4
));
parameters.push_back(ShaderParameter(
"u_View",
&rel->view,
ShaderParameterType::MAT4
));
parameters.push_back(ShaderParameter(
"u_Model",
&rel->model,
ShaderParameterType::MAT4
));
parameters.push_back(ShaderParameter(
"u_Texture",
&rel->textures,
ShaderParameterType::TEXTURE,
UI_SHADER_TEXTURE_COUNT
));
structures.push_back(ShaderStructure<struct UIShaderQuad>(
"ub_Quad",
&rel->quads,
ShaderOpenGLStructureType::STD140,
[&](const struct UIShaderQuad &rel, std::vector<struct ShaderParameter> &parameters) {
parameters.push_back(ShaderParameter(
"quad",
&rel.quad,
ShaderParameterType::VEC4
));
parameters.push_back(ShaderParameter(
"uv",
&rel.uv,
ShaderParameterType::VEC4
));
parameters.push_back(ShaderParameter(
"color",
&rel.color,
ShaderParameterType::COLOR
));
parameters.push_back(ShaderParameter(
"style",
&rel.style,
ShaderParameterType::VEC4
));
},
UI_SHADER_QUAD_COUNT
));
}

43
archive/UIShader.hpp Normal file
View File

@ -0,0 +1,43 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "display/shader/Shader.hpp"
namespace Dawn {
#define UI_SHADER_QUAD_COUNT 1024
#define UI_SHADER_TEXTURE_COUNT 16
enum class UIShaderQuadStyle {
TEXTURED = 0,
FONT = 1
};
struct UIShaderQuad {
glm::vec4 quad;
glm::vec4 uv;
struct Color color;
glm::vec4 style;
};
struct UIShaderData {
glm::mat4 projection;
glm::mat4 view;
glm::mat4 model;
shadertexturebinding_t textures[UI_SHADER_TEXTURE_COUNT];
struct UIShaderQuad quads[UI_SHADER_QUAD_COUNT];
};
class UIShader : public Shader<UIShaderData> {
protected:
void getStages(
const enum ShaderOpenGLVariant variant,
const struct UIShaderData *rel,
std::vector<std::shared_ptr<ShaderStage>> &stages,
std::vector<struct ShaderParameter> &parameters,
std::vector<struct IShaderStructure> &structures
) override;
};
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2023 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DAWN_TARGET_NAME}
PRIVATE
UICanvas.cpp
)

View File

@ -0,0 +1,144 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "UICanvas.hpp"
#include "display/pass/RenderPass.hpp"
#include "display/mesh/QuadMesh.hpp"
#include "ui/UIElement.hpp"
using namespace Dawn;
void UICanvas::onInit() {
mesh = std::make_shared<Mesh>();
mesh->createBuffers(
QUAD_VERTICE_COUNT * UI_SHADER_QUAD_COUNT,
QUAD_INDICE_COUNT * UI_SHADER_QUAD_COUNT
);
for(int32_t i = 0; i < UI_SHADER_QUAD_COUNT; i++) {
QuadMesh::bufferWithIndex(
mesh,
glm::vec4(0, 0, 1, 1),
glm::vec4(0, 0, 1, 1),
i * QUAD_VERTICE_COUNT,
i * QUAD_INDICE_COUNT,
i * QUAD_VERTICE_COUNT
);
}
}
void UICanvas::onDispose() {
mesh = nullptr;
}
std::vector<std::shared_ptr<IRenderPass>> UICanvas::getPasses(
struct RenderPassContext &ctx
) {
if(this->elements.empty()) return {};
glm::mat4 projection;
glm::mat4 view;
// Setup the projection and views
data.projection = glm::ortho(
0.0f, ctx.renderTarget->getWidth(),
ctx.renderTarget->getHeight(), 0.0f,
0.0f, 1.0f
);
data.view = glm::mat4(1.0f);
data.model = glm::mat4(1.0f);
// Reset the passes
this->passes.clear();
this->textureBindings.clear();
this->textures.clear();
quadCount = 0;
nextBinding = 0;
// Alignment root
const glm::vec2 rootPosition = { 0, 0 };
const glm::vec2 rootSize = {
ctx.renderTarget->getWidth(),
ctx.renderTarget->getHeight()
};
const float_t rootScale = 1.0f;
// Get the quads for each component
auto itComponents = elements.begin();
auto self = std::ref(*this);
while(itComponents != elements.end()) {
auto component = *itComponents;
component->updateAlignment(rootPosition, rootSize, rootScale);
component->getQuads(self);
++itComponents;
}
// Flush the remaining quads
flushPass();
return passes;
}
void UICanvas::addQuad(
const glm::vec4 quad,
const glm::vec4 uvs,
const struct Color color,
const enum UIShaderQuadStyle style,
const std::shared_ptr<Texture> text
) {
glm::vec4 styleData;
styleData[0] = (float_t)style;
if(text == nullptr) {
styleData[1] = -1;
} else {
shadertexturebinding_t texture;
auto bindingIt = textureBindings.find(text);
if(bindingIt == textureBindings.end()) {
if(nextBinding >= UI_SHADER_TEXTURE_COUNT) {
flushPass();
}
textureBindings[text] = nextBinding;
textures[nextBinding] = text;
data.textures[nextBinding] = nextBinding;
texture = nextBinding++;
} else {
texture = bindingIt->second;
}
styleData[1] = (float_t)texture;
}
data.quads[quadCount] = {
quad,
uvs,
color,
styleData
};
quadCount++;
if(quadCount == UI_SHADER_QUAD_COUNT) flushPass();
}
void UICanvas::flushPass() {
if(quadCount == 0) return;
auto pass = createRenderPass<UIShader, UIShaderData>(
std::ref(*this),
data,
textures,
mesh,
MeshDrawMode::TRIANGLES,
0,
quadCount * QUAD_INDICE_COUNT
);
passes.push_back(pass);
quadCount = 0;
nextBinding = 0;
textures.clear();
textureBindings.clear();
}
void UICanvas::addElement(const std::shared_ptr<UIElement> element) {
elements.push_back(element);
}

View File

@ -0,0 +1,73 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "scene/SceneItem.hpp"
#include "component/display/IRenderableComponent.hpp"
#include "display/shader/UIShader.hpp"
namespace Dawn {
class UIElement;
class UICanvas :
public SceneComponent,
public IRenderableComponent
{
private:
std::shared_ptr<Mesh> mesh;
UIShaderData data;
std::vector<std::shared_ptr<UIElement>> elements;
size_t quadCount = 0;
shadertexturebinding_t nextBinding = 0;
std::unordered_map<
shadertexturebinding_t, std::shared_ptr<Texture>
> textures;
std::map<
std::shared_ptr<Texture>, shadertexturebinding_t
> textureBindings;
std::vector<std::shared_ptr<IRenderPass>> passes;
protected:
virtual void onInit() override;
virtual void onDispose() override;
/**
* Flushes all pending quads to the render pass. This doesn't actually
* render anything, it just flushes the data buffer to a new pass.
*/
void flushPass();
public:
std::vector<std::shared_ptr<IRenderPass>> getPasses(
struct RenderPassContext &ctx
) override;
/**
* Adds a quad to the canvas and performs a flush if necessary.
*
* @param quad The quad to add.
* @param uvs The UVs to use for the quad.
* @param color The color to use for the quad.
* @param style Style that the quad should be rendered in.
* @param texture The texture to use for the quad, can be null.
*/
void addQuad(
const glm::vec4 quad,
const glm::vec4 uvs,
const struct Color color,
const enum UIShaderQuadStyle style,
const std::shared_ptr<Texture> texture = nullptr
);
/**
* Adds a component to the canvas.
*
* @param component The component to add.
*/
void addElement(const std::shared_ptr<UIElement> component);
};
}

16
archive/ui/CMakeLists.txt Normal file
View File

@ -0,0 +1,16 @@
# Copyright (c) 2023 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DAWN_TARGET_NAME}
PRIVATE
UIAlignableElement.cpp
UISubAlignableElement.cpp
UIElement.cpp
UIMenu.cpp
)
# Subdirs
add_subdirectory(container)
add_subdirectory(elements)

View File

@ -0,0 +1,247 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "UIAlignableElement.hpp"
using namespace Dawn;
UIAlignableElement::UIAlignableElement() {
alignUnit[0] = UIAlignmentUnit::SCALE;
alignUnit[1] = UIAlignmentUnit::SCALE;
alignUnit[2] = UIAlignmentUnit::SCALE;
alignUnit[3] = UIAlignmentUnit::SCALE;
eventAlignmentUpdated = [&](const glm::vec2 p, const glm::vec2 s) {
};
}
void UIAlignableElement::updateSelfAlignment(
const glm::vec2 pPos,
const glm::vec2 pSize,
const float_t canvasScale
) {
auto valueAxis = [&](
const enum UIAlignmentUnit unit,
const float_t alignment,
const float_t parentSize,
const float_t ratioSize,
const float_t contentSize
) {
if(alignment == UI_ALIGN_SIZE_AUTO) return contentSize;
switch(unit) {
case UIAlignmentUnit::PIXEL:
return alignment;
case UIAlignmentUnit::SCALE:
return canvasScale * alignment;
case UIAlignmentUnit::PERCENT:
return parentSize * (alignment / 100.0f);
case UIAlignmentUnit::RATIO:
return (alignment / 100.0f) * ratioSize;
default:
assertUnreachable("Invalid UIAlignmentType");
return 0.0f;
}
};
auto alignAxis = [&](
const enum UIAlignmentType type,
const enum UIAlignmentUnit unit0,
const enum UIAlignmentUnit unit1,
const float_t alignment0,
const float_t alignment1,
const float_t parentSize,
const float_t ratioSize,
const float_t contentSize,
float_t &outPosition,
float_t &outSize
) {
switch(type) {
case UIAlignmentType::START:
outPosition = valueAxis(
unit0,
alignment0,
parentSize,
ratioSize,
contentSize
);
outSize = valueAxis(
unit1,
alignment1,
parentSize,
ratioSize,
contentSize
);
break;
case UIAlignmentType::MIDDLE:
outSize = valueAxis(
unit1,
alignment1,
parentSize,
ratioSize,
contentSize
);
outPosition = (parentSize / 2.0f) - (contentSize / 2.0f) + valueAxis(
unit0,
alignment0,
parentSize,
ratioSize,
contentSize
);
break;
case UIAlignmentType::END:
outSize = valueAxis(
unit0,
alignment0,
parentSize,
ratioSize,
contentSize
);
outPosition = parentSize - outSize - valueAxis(
unit1,
alignment1,
parentSize,
ratioSize,
contentSize
);
break;
case UIAlignmentType::STRETCH:
outPosition = valueAxis(
unit0,
alignment0,
parentSize,
ratioSize,
contentSize
);
outSize = parentSize - (outPosition + valueAxis(
unit1,
alignment1,
parentSize,
ratioSize,
contentSize
));
break;
default:
assertUnreachable("Invalid UIAlignmentType");
}
};
bool_t heightFirst = (
alignUnit[0] == UIAlignmentUnit::RATIO ||
alignUnit[2] == UIAlignmentUnit::RATIO
);
if(heightFirst) {
// Align height first, this will define size.y which we can use as the ratio
// for the width/X axis alignment.
alignAxis(
alignY,
alignUnit[1],
alignUnit[3],
align[1],
align[3],
pSize.y,
0,
this->getContentHeight(),
position.y,
size.y
);
alignAxis(
alignX,
alignUnit[0],
alignUnit[2],
align[0],
align[2],
pSize.x,
size.y,
this->getContentWidth(),
position.x,
size.x
);
} else {
alignAxis(
alignX,
alignUnit[0],
alignUnit[2],
align[0],
align[2],
pSize.x,
0,
this->getContentWidth(),
position.x,
size.x
);
alignAxis(
alignY,
alignUnit[1],
alignUnit[3],
align[1],
align[3],
pSize.y,
size.x,
this->getContentHeight(),
position.y,
size.y
);
}
this->position += pPos;
this->eventAlignmentUpdated(position, size);
}
bool_t UIAlignableElement::hasExplicitWidth() {
if(size.x == 0.0f) return false;
if(
(alignX == UIAlignmentType::STRETCH) ||
(alignX == UIAlignmentType::END)
) {
return align[0] != UI_ALIGN_SIZE_AUTO;
}
return align[2] != UI_ALIGN_SIZE_AUTO;
}
bool_t UIAlignableElement::hasExplicitHeight() {
if(size.y == 0.0f) return false;
if(
(alignY == UIAlignmentType::STRETCH) ||
(alignY == UIAlignmentType::END)
) {
return align[1] != UI_ALIGN_SIZE_AUTO;
}
return align[3] != UI_ALIGN_SIZE_AUTO;
}
float_t UIAlignableElement::getWidth() {
if(hasExplicitWidth()) return size.x;
return getContentWidth();
}
float_t UIAlignableElement::getHeight() {
if(hasExplicitHeight()) return size.y;
return getContentHeight();
}
void UIAlignableElement::updateAlignment(
const glm::vec2 pPos,
const glm::vec2 pSize,
const float_t canvasScale
) {
this->updateSelfAlignment(pPos, pSize, canvasScale);
// Now update children alignment
auto children = getChildren();
for(auto &c : children) {
c->updateAlignment(this->position, this->size, canvasScale);
}
}

View File

@ -0,0 +1,83 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "ui/UIElement.hpp"
namespace Dawn {
#define UI_ALIGN_SIZE_AUTO -1.0f
enum class UIAlignmentType {
START,
MIDDLE,
END,
STRETCH
};
enum class UIAlignmentUnit {
PIXEL,
SCALE,
PERCENT,
RATIO
};
class UIAlignableElement : public UIElement {
protected:
glm::vec2 position;
glm::vec2 size;
/**
* Updates the alignment of this element ONLY.
*
* @param parentPosition The position of the parent.
* @param parentSize The size of the parent.
* @param canvasScale The scale of the canvas.
*/
virtual void updateSelfAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
);
/**
* Returns true only if the width of this component is explicitly set.
*
* @return True if the width of this component is explicitly set.
*/
bool_t hasExplicitWidth();
/**
* Returns true only if the height of this component is explicitly set.
*
* @return True if the height of this component is explicitly set.
*/
bool_t hasExplicitHeight();
public:
// Primary alignment controls
glm::vec4 align = glm::vec4(0, 0, 0, 0);
enum UIAlignmentType alignX = UIAlignmentType::STRETCH;
enum UIAlignmentType alignY = UIAlignmentType::STRETCH;
enum UIAlignmentUnit alignUnit[4];
std::function<
void(const glm::vec2, const glm::vec2)
> eventAlignmentUpdated;
/**
* Constructor for the UIAlignableElement.
*/
UIAlignableElement();
float_t getWidth() override;
float_t getHeight() override;
void updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) override;
};
}

53
archive/ui/UIElement.cpp Normal file
View File

@ -0,0 +1,53 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "UIElement.hpp"
#include "assert/assert.hpp"
using namespace Dawn;
std::vector<std::shared_ptr<UIElement>> UIElement::getChildren() {
return {};
}
void UIElement::getSelfQuads(UICanvas &ctx) {
//Do nothing
}
float_t UIElement::getContentWidth() {
return 0.0f;
}
float_t UIElement::getContentHeight() {
return 0.0f;
}
float_t UIElement::getWidth() {
return this->getContentWidth();
}
float_t UIElement::getHeight() {
return this->getContentHeight();
}
void UIElement::getQuads(UICanvas &ctx) {
this->getSelfQuads(ctx);
auto children = getChildren();
for(auto &c : children) {
c->getQuads(ctx);
}
}
void UIElement::updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) {
auto children = getChildren();
for(auto &c : children) {
c->updateAlignment(parentPosition, parentSize, canvasScale);
}
}

80
archive/ui/UIElement.hpp Normal file
View File

@ -0,0 +1,80 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "display/shader/UIShader.hpp"
#include "component/ui/UICanvas.hpp"
namespace Dawn {
class UIElement {
protected:
/**
* Virtual method overridden by the UIElement to get the quads for the
* component.
*
* @param alignment The alignment of this component.
* @param ctx The canvas to add the quads to.
*/
virtual void getSelfQuads(UICanvas &ctx);
public:
/**
* Overrided method by the UI Element that requests the minimum
* width of the content.
*
* @return The minimum width of the content.
*/
virtual float_t getContentWidth();
/**
* Overrided method by the UI Element that requests the minimum
* height of the content.
*
* @return The minimum height of the content.
*/
virtual float_t getContentHeight();
/**
* Returns the width of this component.
*
* @return The width of this component.
*/
virtual float_t getWidth();
/**
* Returns the height of this component.
*
* @return The height of this component.
*/
virtual float_t getHeight();
/**
* Virtual method overridden by the UIElement to get the children of
* this component.
*/
virtual std::vector<std::shared_ptr<UIElement>> getChildren();
/**
* Method called by the UICanvas to get the quads for this component.
*
* @param ctx The canvas to add the quads to.
*/
void getQuads(UICanvas &ctx);
/**
* Updates the alignment of this component based on the parent. Typically
* left to the UIAlignableElement to implement, default implementation
* does nothing but invoke children.
*
* @param parentPosition The position of the parent.
* @param parentSize The size of the parent.
*/
virtual void updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
);
};
}

50
archive/ui/UIMenu.cpp Normal file
View File

@ -0,0 +1,50 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "UIMenu.hpp"
using namespace Dawn;
void UIMenu::setPosition(int32_t x, int32_t y) {
assertTrue(x >= 0, "X position must be greater than or equal to 0.");
assertTrue(y >= 0, "Y position must be greater than or equal to 0.");
assertTrue(x < columns, "X must be less than the number of columns.");
assertTrue(y < rows, "Y must be less than the number of rows.");
if(this->x == x && this->y == y) return;
this->x = x;
this->y = y;
eventPositionChanged.emit(x, y);
}
void UIMenu::setSize(int32_t columns, int32_t rows) {
assertTrue(columns > 0, "Columns must be greater than 0.");
assertTrue(rows > 0, "Rows must be greater than 0.");
assertTrue(columns > x, "Columns must be greater than current x position.");
assertTrue(rows > y, "Rows must be greater than current y position.");
if(this->columns == columns && this->rows == rows) return;
this->columns = columns;
this->rows = rows;
}
int32_t UIMenu::getX() {
return x;
}
int32_t UIMenu::getY() {
return y;
}
int32_t UIMenu::getColumns() {
return columns;
}
int32_t UIMenu::getRows() {
return rows;
}

64
archive/ui/UIMenu.hpp Normal file
View File

@ -0,0 +1,64 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "ui/UIElement.hpp"
namespace Dawn {
class UIMenu final : public UIElement {
private:
int32_t x = 0;
int32_t y = 0;
int32_t columns = 1;
int32_t rows = 1;
public:
Event<int32_t, int32_t> eventPositionChanged;
/**
* Sets the position of this menu.
*
* @param x The x position of this menu.
* @param y The y position of this menu.
*/
void setPosition(int32_t x, int32_t y);
/**
* Sets the size of this menu.
*
* @param columns The number of columns in this menu.
* @param rows The number of rows in this menu.
*/
void setSize(int32_t columns, int32_t rows);
/**
* Gets the x position of this menu.
*
* @return The x position of this menu.
*/
int32_t getX();
/**
* Gets the y position of this menu.
*
* @return The y position of this menu.
*/
int32_t getY();
/**
* Gets the number of columns in this menu.
*
* @return The number of columns in this menu.
*/
int32_t getColumns();
/**
* Gets the number of rows in this menu.
*
* @return The number of rows in this menu.
*/
int32_t getRows();
};
}

View File

@ -0,0 +1,68 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "UISubAlignableElement.hpp"
using namespace Dawn;
void UISubAlignableElement::updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) {
UIAlignableElement::updateAlignment(parentPosition, parentSize, canvasScale);
switch(this->subAlignX) {
case UISubAlignment::START:
this->subAlignedPosition.x = this->position.x;
break;
case UISubAlignment::MIDDLE:
this->subAlignedPosition.x = (
this->position.x +
(this->size.x / 2.0f) -
(this->getContentWidth() / 2.0f)
);
break;
case UISubAlignment::END:
this->subAlignedPosition.x = (
this->position.x +
this->size.x -
this->getContentWidth()
);
break;
default:
assertUnreachable("Unknown UISubAlignment!");
}
switch(this->subAlignY) {
case UISubAlignment::START:
this->subAlignedPosition.y = this->position.y;
break;
case UISubAlignment::MIDDLE:
this->subAlignedPosition.y = (
this->position.y +
(this->size.y / 2.0f) -
(this->getContentHeight() / 2.0f)
);
break;
case UISubAlignment::END:
this->subAlignedPosition.y = (
this->position.y +
this->size.y -
this->getContentHeight()
);
break;
default:
assertUnreachable("Unknown UISubAlignment!");
}
this->eventSubAlignmentUpdated.emit(this->subAlignedPosition);
}

View File

@ -0,0 +1,32 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "ui/UIAlignableElement.hpp"
namespace Dawn {
enum class UISubAlignment {
START,
MIDDLE,
END
};
class UISubAlignableElement : public UIAlignableElement {
protected:
glm::vec2 subAlignedPosition;
public:
Event<glm::vec2> eventSubAlignmentUpdated;
enum UISubAlignment subAlignX = UISubAlignment::START;
enum UISubAlignment subAlignY = UISubAlignment::START;
void updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) override;
};
}

View File

@ -0,0 +1,12 @@
# Copyright (c) 2023 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DAWN_TARGET_NAME}
PRIVATE
UIColumnContainer.cpp
UIContainer.cpp
UIPaddingContainer.cpp
UIRowContainer.cpp
)

View File

@ -0,0 +1,36 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "assert/assert.hpp"
#include "UIColumnContainer.hpp"
using namespace Dawn;
void UIColumnContainer::updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) {
this->updateSelfAlignment(parentPosition, parentSize, canvasScale);
// Now we have our dimensions, divide evenly
auto children = this->getChildren();
float_t x = 0.0f;
float_t xPiece = this->size.x / (float_t)children.size();
// Update all children
for(auto &child : children) {
child->updateAlignment(
this->position + glm::vec2(x, 0),
glm::vec2(
xPiece,
this->size.y
),
canvasScale
);
x += xPiece;
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "ui/container/UIContainer.hpp"
namespace Dawn {
class UIColumnContainer final : public UIContainer {
public:
void updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) override;
};
}

View File

@ -0,0 +1,47 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "assert/assert.hpp"
#include "UIContainer.hpp"
using namespace Dawn;
std::vector<std::shared_ptr<UIElement>> UIContainer::getChildren() {
return this->children;
}
float_t UIContainer::getContentWidth() {
float_t width = 0;
auto children = this->getChildren();
for(auto child : children) {
width = Math::max(width, child->getWidth());
}
return width;
}
float_t UIContainer::getContentHeight() {
float_t height = 0;
auto children = this->getChildren();
for(auto child : children) {
height = Math::max(height, child->getHeight());
}
return height;
}
void UIContainer::appendChild(std::shared_ptr<UIElement> child) {
assertNotNull(child, "Cannot append a null child!");
this->children.push_back(child);
}
void UIContainer::removeChild(std::shared_ptr<UIElement> child) {
assertNotNull(child, "Cannot remove a null child!");
auto it = std::find(this->children.begin(), this->children.end(), child);
if(it == this->children.end()) return;
this->children.erase(it);
}
void UIContainer::clearChildren() {
this->children.clear();
}

View File

@ -0,0 +1,39 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "ui/UIAlignableElement.hpp"
namespace Dawn {
class UIContainer : public UIAlignableElement {
private:
std::vector<std::shared_ptr<UIElement>> children;
public:
std::vector<std::shared_ptr<UIElement>> getChildren() override;
float_t getContentWidth() override;
float_t getContentHeight() override;
/**
* Appends a child to this container.
*
* @param child Child to append.
*/
void appendChild(std::shared_ptr<UIElement> child);
/**
* Removes a child from this container.
*
* @param child Child to remove.
*/
void removeChild(std::shared_ptr<UIElement> child);
/**
* Removes all children from this container.
*/
void clearChildren();
};
}

View File

@ -0,0 +1,38 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "UIPaddingContainer.hpp"
using namespace Dawn;
float_t UIPaddingContainer::getContentWidth() {
float_t width = 0.0f;
for(auto &child : getChildren()) {
width = Math::max(width, child->getWidth());
}
return width + padding.x + padding.z;
}
float_t UIPaddingContainer::getContentHeight() {
float_t height = 0.0f;
for(auto &child : getChildren()) {
height = Math::max(height, child->getHeight());
}
return height + padding.y + padding.w;
}
void UIPaddingContainer::updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) {
glm::vec2 childPosition = parentPosition + glm::vec2(padding.x, padding.y);
glm::vec2 childSize = parentSize - glm::vec2(padding.x + padding.z, padding.y + padding.w);
auto children = getChildren();
for(auto &child : children) {
child->updateAlignment(childPosition, childSize, canvasScale);
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "ui/container/UIContainer.hpp"
namespace Dawn {
class UIPaddingContainer final : public UIContainer {
public:
glm::vec4 padding = { 0, 0, 0, 0 };
float_t getContentWidth() override;
float_t getContentHeight() override;
void updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) override;
};
}

View File

@ -0,0 +1,51 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "UIRowContainer.hpp"
using namespace Dawn;
float_t UIRowContainer::getContentWidth() {
float_t width = 0.0f;
for(auto &child : this->getChildren()) {
width = Math::max(width, child->getWidth());
}
return width;
}
float_t UIRowContainer::getContentHeight() {
float_t height = 0.0f;
for(auto &child : this->getChildren()) {
height += child->getHeight();
}
return height;
}
void UIRowContainer::updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) {
this->updateSelfAlignment(parentPosition, parentSize, canvasScale);
// Now we have our dimensions, divide evenly
auto children = this->getChildren();
float_t y = 0.0f;
float_t yPiece = this->size.y / (float_t)children.size();
// Update all children
for(auto &child : children) {
child->updateAlignment(
this->position + glm::vec2(0, y),
glm::vec2(
this->size.x,
yPiece
),
canvasScale
);
y += yPiece;
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "ui/container/UIContainer.hpp"
namespace Dawn {
class UIRowContainer final : public UIContainer {
public:
float_t getContentWidth() override;
float_t getContentHeight() override;
void updateAlignment(
const glm::vec2 parentPosition,
const glm::vec2 parentSize,
const float_t canvasScale
) override;
};
}

View File

@ -0,0 +1,10 @@
# Copyright (c) 2023 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DAWN_TARGET_NAME}
PRIVATE
UILabel.cpp
UIRectangle.cpp
)

View File

@ -0,0 +1,175 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "UILabel.hpp"
using namespace Dawn;
void UILabel::getSelfQuads(UICanvas &ctx) {
std::vector<struct UIShaderQuad> quads;
if(this->texture == nullptr || this->text.empty()) return;
glm::vec4 quad;
glm::vec2 pos = glm::vec2(0, this->texture->fontSize);
bool_t lastCharWasSpace = false;
for(size_t i = 0; i < text.size(); i++) {
wchar_t c = text[i];
auto info = texture->getCharacterData(c);
// Newline(s)
if(c == L'\n') {
pos.x = 0;
pos.y += this->texture->fontSize;
continue;
}
// Spaces
if(c == L' ') {
pos.x += info.advance.x;
lastCharWasSpace = true;
continue;
}
// Word Wrap
if(wordWrap) {
if(lastCharWasSpace) {
// Scan ahead to next space
float_t wordWidth = pos.x;// Start at current position and scan ahead.
for(size_t j = i; j < text.size(); j++) {
wchar_t c2 = text[j];
if(c2 == L' ' || c2 == L'\n') {
break;// If we hit another space, we are OK.
}
// Will this character fit on the row? If not the whole word will wrap.
auto info2 = texture->getCharacterData(c);
wordWidth += info.advance.x;
if(wordWidth > size.x) {
pos.x = 0;
pos.y += this->texture->fontSize;
break;
}
}
lastCharWasSpace = false;
}
// } else if(pos.x + info.size.x > subAlignedPosition.x + size.x) {
// // Not word wrap, but instead just overflow characters.
// pos.x = 0;
// pos.y += this->texture->fontSize;
}
ctx.addQuad(
{
subAlignedPosition.x + pos.x + info.offset.x,
subAlignedPosition.y + pos.y + info.offset.y,
subAlignedPosition.x + pos.x + info.size.x + info.offset.x,
subAlignedPosition.y + pos.y + info.size.y + info.offset.y
},
{
info.quad.x,
info.quad.y,
info.quad.z,
info.quad.w
},
this->color,
UIShaderQuadStyle::FONT,
texture->texture
);
pos += info.advance;
}
}
float_t UILabel::getContentWidth() {
if(this->texture == nullptr || this->text.empty()) return 0.0f;
float_t lineWidth = 0.0f;
float_t width = 0.0f;
for(wchar_t c : text) {
if(c == L'\n') {
width = Math::max<float_t>(width, lineWidth);
lineWidth = 0.0f;
continue;
}
auto info = texture->getCharacterData(c);
lineWidth += info.advance.x;
if(
this->hasExplicitWidth() &&
lineWidth >= size.x
) return size.x;
}
width = Math::max<float_t>(width, lineWidth);
return width;
}
float_t UILabel::getContentHeight() {
if(this->texture == nullptr || this->text.empty()) return 0.0f;
float_t height = this->texture->fontSize;
float_t lineWidth = 0.0f;
bool_t lastCharWasSpace = false;
for(wchar_t c : text) {
if(c == L'\n') {
height += this->texture->fontSize;
continue;
}
auto info = texture->getCharacterData(c);
if(c == L' ') {
lineWidth += info.advance.x;
lastCharWasSpace = true;
continue;
}
if(wordWrap) {
if(lastCharWasSpace) {
// Scan ahead to next space
float_t wordWidth = lineWidth;// Start at current position and scan ahead.
for(size_t j = 0; j < text.size(); j++) {
wchar_t c2 = text[j];
if(c2 == L' ' || c2 == L'\n') {
break;// If we hit another space, we are OK.
}
// Will this character fit on the row? If not the whole word will wrap.
auto info2 = texture->getCharacterData(c);
wordWidth += info.advance.x;
if(wordWidth > size.x) {
height += this->texture->fontSize;
lineWidth = 0.0f;
break;
}
}
lastCharWasSpace = false;
}
// } else if(lineWidth + info.size.x > size.x) {
// height += this->texture->fontSize;
// lineWidth = 0.0f;
}
}
return height;
}
std::shared_ptr<TrueTypeTexture> UILabel::getFont() {
return this->texture;
}
std::wstring UILabel::getText() {
return this->text;
}
void UILabel::setFont(std::shared_ptr<TrueTypeTexture> texture) {
this->texture = texture;
}
void UILabel::setText(const std::wstring &text) {
this->text = text;
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "ui/UISubAlignableElement.hpp"
#include "display/font/TrueTypeTexture.hpp"
namespace Dawn {
class UILabel final : public UISubAlignableElement {
private:
std::shared_ptr<TrueTypeTexture> texture = nullptr;
std::wstring text = L"Hello World";
protected:
void getSelfQuads(UICanvas &ctx) override;
public:
bool_t wordWrap = true;
struct Color color = COLOR_WHITE;
float_t getContentWidth() override;
float_t getContentHeight() override;
/**
* Returns the font used for this label.
*
* @return The font used for this label.
*/
std::shared_ptr<TrueTypeTexture> getFont();
/**
* Returns the text used for this label.
*
* @return The text used for this label.
*/
std::wstring getText();
/**
* Sets the font to use for this label.
*
* @param texture TrueType texture to use for this label.
*/
void setFont(std::shared_ptr<TrueTypeTexture> texture);
/**
* Sets the text to use for this label.
*
* @param text The text to use for this label.
*/
void setText(const std::wstring &text);
};
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "UIRectangle.hpp"
using namespace Dawn;
void UIRectangle::getSelfQuads(UICanvas &ctx) {
std::vector<struct UIShaderQuad> quads;
ctx.addQuad(
glm::vec4(position, position + size),
uv,
color,
UIShaderQuadStyle::TEXTURED,
texture
);
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2023 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "ui/UIAlignableElement.hpp"
namespace Dawn {
class UIRectangle final : public UIAlignableElement {
protected:
void getSelfQuads(UICanvas &ctx) override;
public:
struct Color color = COLOR_WHITE;
std::shared_ptr<Texture> texture = nullptr;
glm::vec4 uv = glm::vec4(0,0,1,1);
};
}