diff --git a/Resources/Editor/Shaders/Gizmo.ovfx b/Resources/Editor/Shaders/Gizmo.ovfx
index 62d19b6a9..1641d559e 100644
--- a/Resources/Editor/Shaders/Gizmo.ovfx
+++ b/Resources/Editor/Shaders/Gizmo.ovfx
@@ -21,7 +21,9 @@ out VS_OUT
uniform bool u_IsBall;
uniform bool u_IsPickable;
+uniform int u_VisibleAxes;
uniform int u_HighlightedAxis;
+uniform float u_GizmoScale;
mat4 rotationMatrix(vec3 axis, float angle)
{
@@ -41,6 +43,17 @@ mat4 rotationMatrix(vec3 axis, float angle)
void main()
{
+ /* Instances are ordered Z, X, Y, while axis bits are ordered X, Y, Z */
+ int axisBit = gl_InstanceID == 1 ? 1 : (gl_InstanceID == 2 ? 2 : 4);
+
+ /* The ball is drawn with a single instance and isn't tied to an axis */
+ if (!u_IsBall && (u_VisibleAxes & axisBit) == 0)
+ {
+ vs_out.Color = vec3(0.0f);
+ gl_Position = vec4(2.0f, 2.0f, 2.0f, 1.0f);
+ return;
+ }
+
mat4 instanceModel = ubo_Model;
if (gl_InstanceID == 1)
@@ -49,10 +62,11 @@ void main()
instanceModel *= rotationMatrix(vec3(1, 0, 0), radians(90)); /* Y axis */
float distanceToCamera = distance(ubo_ViewPos, instanceModel[3].xyz);
+ float gizmoScale = u_GizmoScale > 0.0 ? u_GizmoScale : distanceToCamera * 0.1f;
vec3 pos = geo_Pos;
- vec3 fragPos = vec3(instanceModel * vec4(pos * distanceToCamera * 0.1f, 1.0));
+ vec3 fragPos = vec3(instanceModel * vec4(pos * gizmoScale, 1.0));
if (u_IsPickable)
{
@@ -110,4 +124,4 @@ uniform bool u_IsPickable;
void main()
{
FRAGMENT_COLOR = vec4(fs_in.Color, 1.0f);
-}
\ No newline at end of file
+}
diff --git a/Resources/Engine/Lua/Components/Canvas.lua b/Resources/Engine/Lua/Components/Canvas.lua
new file mode 100644
index 000000000..578d61157
--- /dev/null
+++ b/Resources/Engine/Lua/Components/Canvas.lua
@@ -0,0 +1,63 @@
+---@meta
+
+--- Defines how a Canvas scales UI elements
+---@enum CanvasScalerMode
+CanvasScalerMode = {
+ CONSTANT_PIXEL_SIZE = 0,
+ SCALE_WITH_SCREEN_SIZE = 1
+}
+
+---@enum CanvasScreenMatchMode
+CanvasScreenMatchMode = {
+ MATCH_WIDTH_OR_HEIGHT = 0,
+ EXPAND = 1,
+ SHRINK = 2
+}
+
+--- Represents a root canvas for in-game user interface elements
+---@class Canvas : Component
+Canvas = {}
+
+--- Returns the actor that owns this component
+---@return Actor
+function Canvas:GetOwner() end
+
+--- Returns the reference resolution used by the canvas
+---@return Vector2
+function Canvas:GetReferenceResolution() end
+
+--- Defines the reference resolution used by the canvas
+---@param referenceResolution Vector2
+function Canvas:SetReferenceResolution(referenceResolution) end
+
+--- Returns the canvas scale factor
+---@return number
+function Canvas:GetScaleFactor() end
+
+--- Defines the canvas scale factor
+---@param scaleFactor number
+function Canvas:SetScaleFactor(scaleFactor) end
+
+--- Returns the canvas scaler mode
+---@return CanvasScalerMode
+function Canvas:GetScalerMode() end
+
+--- Defines the canvas scaler mode
+---@param scalerMode CanvasScalerMode
+function Canvas:SetScalerMode(scalerMode) end
+
+--- Returns the screen match mode used with SCALE_WITH_SCREEN_SIZE
+---@return CanvasScreenMatchMode
+function Canvas:GetScreenMatchMode() end
+
+--- Defines the screen match mode used with SCALE_WITH_SCREEN_SIZE
+---@param screenMatchMode CanvasScreenMatchMode
+function Canvas:SetScreenMatchMode(screenMatchMode) end
+
+--- Returns the match width/height factor in range [0, 1]
+---@return number
+function Canvas:GetMatchWidthOrHeight() end
+
+--- Defines the match width/height factor in range [0, 1]
+---@param value number
+function Canvas:SetMatchWidthOrHeight(value) end
diff --git a/Resources/Engine/Lua/Components/HorizontalLayout.lua b/Resources/Engine/Lua/Components/HorizontalLayout.lua
new file mode 100644
index 000000000..bed6f164a
--- /dev/null
+++ b/Resources/Engine/Lua/Components/HorizontalLayout.lua
@@ -0,0 +1,9 @@
+---@meta
+
+--- Arranges direct user interface children horizontally
+---@class HorizontalLayout : LayoutGroup
+HorizontalLayout = {}
+
+--- Returns the actor that owns this component
+---@return Actor
+function HorizontalLayout:GetOwner() end
diff --git a/Resources/Engine/Lua/Components/Image.lua b/Resources/Engine/Lua/Components/Image.lua
new file mode 100644
index 000000000..23e0c5093
--- /dev/null
+++ b/Resources/Engine/Lua/Components/Image.lua
@@ -0,0 +1,41 @@
+---@meta
+
+--- Represents a renderable user interface image
+---@class Image : Component
+Image = {}
+
+--- Returns the actor that owns this component
+---@return Actor
+function Image:GetOwner() end
+
+--- Returns the texture rendered by the image
+---@return Texture|nil
+function Image:GetTexture() end
+
+--- Defines the texture rendered by the image
+---@param texture Texture|nil
+function Image:SetTexture(texture) end
+
+--- Returns the image display size stored by Transform UI data
+---@return Vector2
+function Image:GetSize() end
+
+--- Defines the image display size stored by Transform UI data
+---@param size Vector2
+function Image:SetSize(size) end
+
+--- Returns the image tint
+---@return Vector4
+function Image:GetTint() end
+
+--- Defines the image tint
+---@param tint Vector4
+function Image:SetTint(tint) end
+
+--- Returns whether the image keeps its intrinsic aspect ratio when resized
+---@return boolean
+function Image:GetPreserveAspect() end
+
+--- Defines whether the image keeps its intrinsic aspect ratio when resized
+---@param preserveAspect boolean
+function Image:SetPreserveAspect(preserveAspect) end
diff --git a/Resources/Engine/Lua/Components/LayoutGroup.lua b/Resources/Engine/Lua/Components/LayoutGroup.lua
new file mode 100644
index 000000000..fd44a97ce
--- /dev/null
+++ b/Resources/Engine/Lua/Components/LayoutGroup.lua
@@ -0,0 +1,102 @@
+---@meta
+
+--- Defines how a LayoutGroup arranges direct UI children
+---@enum LayoutDirection
+LayoutDirection = {
+ HORIZONTAL = 0,
+ VERTICAL = 1
+}
+
+---@enum LayoutHorizontalAlignment
+LayoutHorizontalAlignment = {
+ LEFT = 0,
+ CENTER = 1,
+ RIGHT = 2
+}
+
+---@enum LayoutVerticalAlignment
+LayoutVerticalAlignment = {
+ TOP = 0,
+ CENTER = 1,
+ BOTTOM = 2
+}
+
+--- Arranges direct user interface children along an axis
+---@class LayoutGroup : Component
+LayoutGroup = {}
+
+--- Returns the actor that owns this component
+---@return Actor
+function LayoutGroup:GetOwner() end
+
+--- Returns the layout direction
+---@return LayoutDirection
+function LayoutGroup:GetDirection() end
+
+--- Defines the layout direction
+---@param direction LayoutDirection
+function LayoutGroup:SetDirection(direction) end
+
+--- Returns the spacing between children
+---@return number
+function LayoutGroup:GetSpacing() end
+
+--- Defines the non-negative spacing between children
+---@param spacing number
+function LayoutGroup:SetSpacing(spacing) end
+
+--- Returns the layout padding as left, right, top, bottom
+---@return Vector4
+function LayoutGroup:GetPadding() end
+
+--- Defines the layout padding as left, right, top, bottom
+---@param padding Vector4
+function LayoutGroup:SetPadding(padding) end
+
+--- Returns the horizontal children alignment
+---@return LayoutHorizontalAlignment
+function LayoutGroup:GetHorizontalAlignment() end
+
+--- Defines the horizontal children alignment
+---@param alignment LayoutHorizontalAlignment
+function LayoutGroup:SetHorizontalAlignment(alignment) end
+
+--- Returns the vertical children alignment
+---@return LayoutVerticalAlignment
+function LayoutGroup:GetVerticalAlignment() end
+
+--- Defines the vertical children alignment
+---@param alignment LayoutVerticalAlignment
+function LayoutGroup:SetVerticalAlignment(alignment) end
+
+--- Returns whether the layout controls children width
+---@return boolean
+function LayoutGroup:GetControlChildrenWidth() end
+
+--- Defines whether the layout controls children width
+---@param value boolean
+function LayoutGroup:SetControlChildrenWidth(value) end
+
+--- Returns whether the layout controls children height
+---@return boolean
+function LayoutGroup:GetControlChildrenHeight() end
+
+--- Defines whether the layout controls children height
+---@param value boolean
+function LayoutGroup:SetControlChildrenHeight(value) end
+
+--- Returns whether child layout slots expand to use all available width
+---@return boolean
+function LayoutGroup:GetForceExpandWidth() end
+
+--- Defines whether child layout slots expand to use all available width
+---@param value boolean
+function LayoutGroup:SetForceExpandWidth(value) end
+
+--- Returns whether child layout slots expand to use all available height
+---@return boolean
+function LayoutGroup:GetForceExpandHeight() end
+
+--- Defines whether child layout slots expand to use all available height
+---@param value boolean
+function LayoutGroup:SetForceExpandHeight(value) end
diff --git a/Resources/Engine/Lua/Components/Text.lua b/Resources/Engine/Lua/Components/Text.lua
new file mode 100644
index 000000000..d62a58084
--- /dev/null
+++ b/Resources/Engine/Lua/Components/Text.lua
@@ -0,0 +1,71 @@
+---@meta
+
+---@enum TextHorizontalAlignment
+TextHorizontalAlignment = {
+ LEFT = 0,
+ CENTER = 1,
+ RIGHT = 2
+}
+
+---@enum TextVerticalAlignment
+TextVerticalAlignment = {
+ TOP = 0,
+ CENTER = 1,
+ BOTTOM = 2
+}
+
+--- Represents a renderable user interface text
+---@class Text : Component
+Text = {}
+
+--- Returns the actor that owns this component
+---@return Actor
+function Text:GetOwner() end
+
+--- Returns the text content
+---@return string
+function Text:GetText() end
+
+--- Defines the text content
+---@param text string
+function Text:SetText(text) end
+
+--- Returns the font resource path
+---@return string
+function Text:GetFontPath() end
+
+--- Defines the font resource path
+---@param fontPath string
+function Text:SetFontPath(fontPath) end
+
+--- Returns the font size in canvas pixels
+---@return number
+function Text:GetFontSize() end
+
+--- Defines the font size in canvas pixels
+---@param fontSize number
+function Text:SetFontSize(fontSize) end
+
+--- Returns the text color
+---@return Vector4
+function Text:GetColor() end
+
+--- Defines the text color
+---@param color Vector4
+function Text:SetColor(color) end
+
+--- Returns the horizontal text alignment
+---@return TextHorizontalAlignment
+function Text:GetHorizontalAlignment() end
+
+--- Defines the horizontal text alignment
+---@param alignment TextHorizontalAlignment
+function Text:SetHorizontalAlignment(alignment) end
+
+--- Returns the vertical text alignment
+---@return TextVerticalAlignment
+function Text:GetVerticalAlignment() end
+
+--- Defines the vertical text alignment
+---@param alignment TextVerticalAlignment
+function Text:SetVerticalAlignment(alignment) end
diff --git a/Resources/Engine/Lua/Components/Transform.lua b/Resources/Engine/Lua/Components/Transform.lua
index cdae48b31..bc41fc18e 100644
--- a/Resources/Engine/Lua/Components/Transform.lua
+++ b/Resources/Engine/Lua/Components/Transform.lua
@@ -4,6 +4,27 @@
---@class Transform : Component
Transform = {}
+--- Defines the anchor preset used by Transform UI data
+---@enum AnchorPreset
+AnchorPreset = {
+ TOP_LEFT = 0,
+ TOP_CENTER = 1,
+ TOP_RIGHT = 2,
+ MIDDLE_LEFT = 3,
+ CENTER = 4,
+ MIDDLE_RIGHT = 5,
+ BOTTOM_LEFT = 6,
+ BOTTOM_CENTER = 7,
+ BOTTOM_RIGHT = 8,
+ HORIZONTAL_STRETCH_TOP = 9,
+ HORIZONTAL_STRETCH_MIDDLE = 10,
+ HORIZONTAL_STRETCH_BOTTOM = 11,
+ VERTICAL_STRETCH_LEFT = 12,
+ VERTICAL_STRETCH_CENTER = 13,
+ VERTICAL_STRETCH_RIGHT = 14,
+ STRETCH_BOTH = 15
+}
+
--- Alias for SetLocalPosition
---@param position Vector3
function Transform:SetPosition(position) end
@@ -112,6 +133,68 @@ function Transform:GetWorldUp() end
---@return Vector3
function Transform:GetWorldRight() end
+--- Enables UI transform data on this transform
+function Transform:EnableUIData() end
+
+--- Disables UI transform data on this transform
+function Transform:DisableUIData() end
+
+--- Returns whether this transform carries UI data
+---@return boolean
+function Transform:HasUIData() end
+
+--- Returns whether this transform is currently driven by a parent Canvas hierarchy
+---@return boolean
+function Transform:HasActiveUIData() end
+
+--- Returns the anchored UI position
+---@return Vector2
+function Transform:GetUIPosition() end
+
+--- Defines the anchored UI position
+---@param position Vector2
+function Transform:SetUIPosition(position) end
+
+--- Returns the UI rotation in degrees
+---@return number
+function Transform:GetUIRotation() end
+
+--- Defines the UI rotation in degrees
+---@param rotation number
+function Transform:SetUIRotation(rotation) end
+
+--- Returns the UI scale
+---@return Vector2
+function Transform:GetUIScale() end
+
+--- Defines the UI scale
+---@param scale Vector2
+function Transform:SetUIScale(scale) end
+
+--- Returns the UI size
+---@return Vector2
+function Transform:GetUISize() end
+
+--- Defines the UI size
+---@param size Vector2
+function Transform:SetUISize(size) end
+
+--- Returns the normalized pivot in range [-1, 1]
+---@return Vector2
+function Transform:GetUIPivot() end
+
+--- Defines the normalized pivot in range [-1, 1]
+---@param pivot Vector2
+function Transform:SetUIPivot(pivot) end
+
+--- Returns the UI anchor preset
+---@return AnchorPreset
+function Transform:GetUIAnchorPreset() end
+
+--- Defines the UI anchor preset
+---@param anchorPreset AnchorPreset
+function Transform:SetUIAnchorPreset(anchorPreset) end
+
--- Returns the actor that owns this component
---@return Actor
function Transform:GetOwner() end
diff --git a/Resources/Engine/Lua/Components/VerticalLayout.lua b/Resources/Engine/Lua/Components/VerticalLayout.lua
new file mode 100644
index 000000000..a6199c17c
--- /dev/null
+++ b/Resources/Engine/Lua/Components/VerticalLayout.lua
@@ -0,0 +1,9 @@
+---@meta
+
+--- Arranges direct user interface children vertically
+---@class VerticalLayout : LayoutGroup
+VerticalLayout = {}
+
+--- Returns the actor that owns this component
+---@return Actor
+function VerticalLayout:GetOwner() end
diff --git a/Resources/Engine/Lua/Scene/Actor.lua b/Resources/Engine/Lua/Scene/Actor.lua
index f1400bf21..6648776d3 100644
--- a/Resources/Engine/Lua/Scene/Actor.lua
+++ b/Resources/Engine/Lua/Scene/Actor.lua
@@ -146,6 +146,30 @@ function Actor:GetPostProcessStack() end
---@return ReflectionProbe|nil
function Actor:GetReflectionProbe() end
+--- Returns the Canvas attached to this actor (If any)
+---@return Canvas|nil
+function Actor:GetCanvas() end
+
+--- Returns the Image attached to this actor (If any)
+---@return Image|nil
+function Actor:GetImage() end
+
+--- Returns the LayoutGroup attached to this actor (If any)
+---@return LayoutGroup|nil
+function Actor:GetLayoutGroup() end
+
+--- Returns the HorizontalLayout attached to this actor (If any)
+---@return HorizontalLayout|nil
+function Actor:GetHorizontalLayout() end
+
+--- Returns the VerticalLayout attached to this actor (If any)
+---@return VerticalLayout|nil
+function Actor:GetVerticalLayout() end
+
+--- Returns the Text attached to this actor (If any)
+---@return Text|nil
+function Actor:GetText() end
+
--- Returns the Behaviour of the given type attached to this actor (If any)
---@param name string
---@return table|nil
@@ -219,6 +243,30 @@ function Actor:AddPostProcessStack() end
---@return ReflectionProbe
function Actor:AddReflectionProbe() end
+--- Adds a Canvas component to the actor and returns it
+---@return Canvas
+function Actor:AddCanvas() end
+
+--- Adds an Image component to the actor and returns it
+---@return Image
+function Actor:AddImage() end
+
+--- Adds a LayoutGroup component to the actor and returns it
+---@return LayoutGroup
+function Actor:AddLayoutGroup() end
+
+--- Adds a HorizontalLayout component to the actor and returns it
+---@return HorizontalLayout
+function Actor:AddHorizontalLayout() end
+
+--- Adds a VerticalLayout component to the actor and returns it
+---@return VerticalLayout
+function Actor:AddVerticalLayout() end
+
+--- Adds a Text component to the actor and returns it
+---@return Text
+function Actor:AddText() end
+
--- Removes the ModelRenderer component from the actor
function Actor:RemoveModelRenderer() end
--- Removes the PhysicalBox component from the actor
@@ -251,7 +299,18 @@ function Actor:RemoveAudioListener() end
function Actor:RemovePostProcessStack() end
--- Removes the ReflectionProbe component from the actor
function Actor:RemoveReflectionProbe() end
-
+--- Removes the Canvas component from the actor
+function Actor:RemoveCanvas() end
+--- Removes the Image component from the actor
+function Actor:RemoveImage() end
+--- Removes the LayoutGroup component from the actor
+function Actor:RemoveLayoutGroup() end
+--- Removes the HorizontalLayout component from the actor
+function Actor:RemoveHorizontalLayout() end
+--- Removes the VerticalLayout component from the actor
+function Actor:RemoveVerticalLayout() end
+--- Removes the Text component from the actor
+function Actor:RemoveText() end
--- Adds a behaviour of given type to the actor and returns it
---@param name string
---@param scriptPath string
diff --git a/Resources/Engine/Materials/Image.ovmat b/Resources/Engine/Materials/Image.ovmat
new file mode 100644
index 000000000..670a6f011
--- /dev/null
+++ b/Resources/Engine/Materials/Image.ovmat
@@ -0,0 +1,36 @@
+
+ :Shaders\Image.ovfx
+
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ true
+ 1
+ 1000
+
+
+
+ u_Image
+ ?
+
+
+ u_Tint
+
+ 1
+ 1
+ 1
+ 1
+
+
+
+
+
diff --git a/Resources/Engine/Materials/Text.ovmat b/Resources/Engine/Materials/Text.ovmat
new file mode 100644
index 000000000..0ae16b062
--- /dev/null
+++ b/Resources/Engine/Materials/Text.ovmat
@@ -0,0 +1,36 @@
+
+ :Shaders\Text.ovfx
+
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ true
+ 1
+ 1000
+
+
+
+ u_FontAtlas
+ ?
+
+
+ u_Color
+
+ 1
+ 1
+ 1
+ 1
+
+
+
+
+
diff --git a/Resources/Engine/Shaders/Image.ovfx b/Resources/Engine/Shaders/Image.ovfx
new file mode 100644
index 000000000..0bcaa4e3a
--- /dev/null
+++ b/Resources/Engine/Shaders/Image.ovfx
@@ -0,0 +1,30 @@
+#shader vertex
+#version 450 core
+
+#include ":Shaders/Common/Buffers/EngineUBO.ovfxh"
+
+layout (location = 0) in vec3 geo_Pos;
+layout (location = 1) in vec2 geo_TexCoords;
+
+out vec2 TexCoords;
+
+void main()
+{
+ TexCoords = geo_TexCoords;
+ gl_Position = ubo_Projection * ubo_View * ubo_Model * vec4(geo_Pos, 1.0);
+}
+
+#shader fragment
+#version 450 core
+
+in vec2 TexCoords;
+
+uniform sampler2D u_Image;
+uniform vec4 u_Tint = vec4(1.0);
+
+out vec4 FRAGMENT_COLOR;
+
+void main()
+{
+ FRAGMENT_COLOR = texture(u_Image, TexCoords) * u_Tint;
+}
diff --git a/Resources/Engine/Shaders/Text.ovfx b/Resources/Engine/Shaders/Text.ovfx
new file mode 100644
index 000000000..d7ac1febb
--- /dev/null
+++ b/Resources/Engine/Shaders/Text.ovfx
@@ -0,0 +1,31 @@
+#shader vertex
+#version 450 core
+
+#include ":Shaders/Common/Buffers/EngineUBO.ovfxh"
+
+layout (location = 0) in vec3 geo_Pos;
+layout (location = 1) in vec2 geo_TexCoords;
+
+out vec2 TexCoords;
+
+void main()
+{
+ TexCoords = geo_TexCoords;
+ gl_Position = ubo_Projection * ubo_View * ubo_Model * vec4(geo_Pos, 1.0);
+}
+
+#shader fragment
+#version 450 core
+
+in vec2 TexCoords;
+
+uniform sampler2D u_FontAtlas;
+uniform vec4 u_Color = vec4(1.0);
+
+out vec4 FRAGMENT_COLOR;
+
+void main()
+{
+ const float alpha = texture(u_FontAtlas, TexCoords).a;
+ FRAGMENT_COLOR = vec4(u_Color.rgb, u_Color.a * alpha);
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/CTransform.h b/Sources/OvCore/include/OvCore/ECS/Components/CTransform.h
index db6b3b850..90cf7ba95 100644
--- a/Sources/OvCore/include/OvCore/ECS/Components/CTransform.h
+++ b/Sources/OvCore/include/OvCore/ECS/Components/CTransform.h
@@ -6,14 +6,16 @@
#pragma once
+#include
+
#include "OvCore/ECS/Components/AComponent.h"
+#include
#include
+#include
#include
#include
-#include "AComponent.h"
-
namespace OvCore::ECS { class Actor; }
namespace OvCore::ECS::Components
@@ -24,6 +26,34 @@ namespace OvCore::ECS::Components
class CTransform : public AComponent
{
public:
+ enum class EUIAnchorPreset
+ {
+ TOP_LEFT,
+ TOP_CENTER,
+ TOP_RIGHT,
+ MIDDLE_LEFT,
+ CENTER,
+ MIDDLE_RIGHT,
+ BOTTOM_LEFT,
+ BOTTOM_CENTER,
+ BOTTOM_RIGHT,
+ HORIZONTAL_STRETCH_TOP,
+ HORIZONTAL_STRETCH_MIDDLE,
+ HORIZONTAL_STRETCH_BOTTOM,
+ VERTICAL_STRETCH_LEFT,
+ VERTICAL_STRETCH_CENTER,
+ VERTICAL_STRETCH_RIGHT,
+ STRETCH_BOTH
+ };
+
+ struct UIData
+ {
+ OvMaths::FVector2 position = OvMaths::FVector2::Zero;
+ OvMaths::FVector2 size = OvMaths::FVector2::Zero;
+ OvMaths::FVector2 pivot = OvMaths::FVector2::Zero;
+ EUIAnchorPreset anchorPreset = EUIAnchorPreset::CENTER;
+ };
+
/**
* Create a transform without setting a parent
* @param p_localPosition
@@ -189,6 +219,109 @@ namespace OvCore::ECS::Components
*/
OvMaths::FVector3 GetLocalRight() const;
+ /**
+ * Create the user interface data if the transform doesn't have any yet
+ */
+ void EnableUIData();
+
+ /**
+ * Discard the user interface data held by the transform
+ */
+ void DisableUIData();
+
+ /**
+ * Check if the transform holds user interface data
+ */
+ bool HasUIData() const;
+
+ /**
+ * Check if the user interface data is in effect, meaning the owner has a canvas ancestor
+ */
+ bool HasActiveUIData() const;
+
+ /**
+ * Return the user interface data, if any
+ */
+ const std::optional& GetUIData() const;
+
+ /**
+ * Set the anchored position, in canvas units
+ * @param p_position
+ */
+ void SetUIPosition(const OvMaths::FVector2& p_position);
+
+ /**
+ * Return the anchored position, in canvas units
+ */
+ const OvMaths::FVector2& GetUIPosition() const;
+
+ /**
+ * Set the rotation around the canvas plane, in degrees, stored in the local rotation
+ * @param p_rotation
+ */
+ void SetUIRotation(float p_rotation);
+
+ /**
+ * Return the rotation around the canvas plane, in degrees
+ */
+ float GetUIRotation() const;
+
+ /**
+ * Set the scale applied on the canvas plane, stored in the local scale
+ * @param p_scale
+ */
+ void SetUIScale(const OvMaths::FVector2& p_scale);
+
+ /**
+ * Return the scale applied on the canvas plane
+ */
+ OvMaths::FVector2 GetUIScale() const;
+
+ /**
+ * Set the size in canvas units, an axis left to zero being derived from the element itself
+ * @param p_size
+ */
+ void SetUISize(const OvMaths::FVector2& p_size);
+
+ /**
+ * Return the size in canvas units, an axis left to zero being derived from the element itself
+ */
+ const OvMaths::FVector2& GetUISize() const;
+
+ /**
+ * Set the normalized pivot, clamped to the [-1, 1] range on both axes
+ * @param p_pivot
+ */
+ void SetUIPivot(const OvMaths::FVector2& p_pivot);
+
+ /**
+ * Return the normalized pivot
+ */
+ const OvMaths::FVector2& GetUIPivot() const;
+
+ /**
+ * Set the anchor preset used to resolve the element against its parent
+ * @param p_anchorPreset
+ */
+ void SetUIAnchorPreset(EUIAnchorPreset p_anchorPreset);
+
+ /**
+ * Return the anchor preset used to resolve the element against its parent
+ */
+ EUIAnchorPreset GetUIAnchorPreset() const;
+
+ /**
+ * Check if the horizontal anchored position can be edited, which isn't the case when the
+ * axis is stretched or driven by a parent layout
+ */
+ bool IsHorizontalUIPositionEditable() const;
+
+ /**
+ * Check if the vertical anchored position can be edited, which isn't the case when the
+ * axis is stretched or driven by a parent layout
+ */
+ bool IsVerticalUIPositionEditable() const;
+
/**
* Serialize the component
* @param p_doc
@@ -209,8 +342,17 @@ namespace OvCore::ECS::Components
*/
virtual void OnInspector(OvUI::Internal::WidgetContainer& p_root) override;
+ private:
+ static EUIAnchorPreset ToUIAnchorPreset(int p_value);
+ static bool IsHorizontalUIPositionEditable(EUIAnchorPreset p_anchorPreset);
+ static bool IsVerticalUIPositionEditable(EUIAnchorPreset p_anchorPreset);
+
+ UIData& GetOrCreateUIData();
+ const UIData& GetUIDataOrDefault() const;
+
private:
OvMaths::FTransform m_transform;
+ std::optional m_uiData;
};
template<>
@@ -218,4 +360,4 @@ namespace OvCore::ECS::Components
{
static constexpr std::string_view Name = "class OvCore::ECS::Components::CTransform";
};
-}
\ No newline at end of file
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/CCanvas.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/CCanvas.h
new file mode 100644
index 000000000..6716c62d4
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/CCanvas.h
@@ -0,0 +1,142 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+#include
+
+namespace OvCore::ECS { class Actor; }
+
+namespace OvCore::ECS::Components::UI
+{
+ /**
+ * Represents a root canvas for in-game user interface elements
+ */
+ class CCanvas : public AComponent
+ {
+ public:
+ enum class EScalerMode
+ {
+ CONSTANT_PIXEL_SIZE,
+ SCALE_WITH_SCREEN_SIZE
+ };
+
+ enum class EScreenMatchMode
+ {
+ MATCH_WIDTH_OR_HEIGHT,
+ EXPAND,
+ SHRINK
+ };
+
+ /**
+ * Constructor
+ * @param p_owner
+ */
+ CCanvas(ECS::Actor& p_owner);
+
+ /**
+ * Returns the name of the component
+ */
+ std::string GetName() override;
+
+ /**
+ * Returns the type name of the component
+ */
+ virtual std::string GetTypeName() override;
+
+ /**
+ * Sets the canvas reference resolution
+ * @param p_referenceResolution
+ */
+ void SetReferenceResolution(const OvMaths::FVector2& p_referenceResolution);
+
+ /**
+ * Returns the canvas reference resolution
+ */
+ const OvMaths::FVector2& GetReferenceResolution() const;
+
+ /**
+ * Sets the canvas scale factor
+ * @param p_scaleFactor
+ */
+ void SetScaleFactor(float p_scaleFactor);
+
+ /**
+ * Returns the canvas scale factor
+ */
+ float GetScaleFactor() const;
+
+ /**
+ * Sets the canvas scaler mode
+ * @param p_scalerMode
+ */
+ void SetScalerMode(EScalerMode p_scalerMode);
+
+ /**
+ * Returns the canvas scaler mode
+ */
+ EScalerMode GetScalerMode() const;
+
+ /**
+ * Sets the screen match mode used when scaler mode is Scale With Screen Size
+ * @param p_screenMatchMode
+ */
+ void SetScreenMatchMode(EScreenMatchMode p_screenMatchMode);
+
+ /**
+ * Returns the screen match mode used when scaler mode is Scale With Screen Size
+ */
+ EScreenMatchMode GetScreenMatchMode() const;
+
+ /**
+ * Sets the width/height match factor in range [0, 1]
+ * @param p_matchWidthOrHeight
+ */
+ void SetMatchWidthOrHeight(float p_matchWidthOrHeight);
+
+ /**
+ * Returns the width/height match factor in range [0, 1]
+ */
+ float GetMatchWidthOrHeight() const;
+
+ /**
+ * Serialize the component
+ * @param p_doc
+ * @param p_node
+ */
+ virtual void OnSerialize(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node) override;
+
+ /**
+ * Deserialize the component
+ * @param p_doc
+ * @param p_node
+ */
+ virtual void OnDeserialize(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node) override;
+
+ /**
+ * Defines how the component should be drawn in the inspector
+ * @param p_root
+ */
+ virtual void OnInspector(OvUI::Internal::WidgetContainer& p_root) override;
+
+ private:
+ OvMaths::FVector2 m_referenceResolution = { 1920.0f, 1080.0f };
+ float m_scaleFactor = 1.0f;
+ EScalerMode m_scalerMode = EScalerMode::SCALE_WITH_SCREEN_SIZE;
+ EScreenMatchMode m_screenMatchMode = EScreenMatchMode::MATCH_WIDTH_OR_HEIGHT;
+ float m_matchWidthOrHeight = 0.5f;
+ };
+}
+
+namespace OvCore::ECS::Components
+{
+ template<>
+ struct ComponentTraits
+ {
+ static constexpr std::string_view Name = "class OvCore::ECS::Components::UI::CCanvas";
+ };
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/CHorizontalLayout.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/CHorizontalLayout.h
new file mode 100644
index 000000000..8decd6ac8
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/CHorizontalLayout.h
@@ -0,0 +1,52 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+
+namespace OvCore::ECS::Components::UI
+{
+ /**
+ * Arranges direct user interface children horizontally
+ */
+ class CHorizontalLayout : public CLayoutGroup
+ {
+ public:
+ /**
+ * Constructor
+ * @param p_owner
+ */
+ CHorizontalLayout(ECS::Actor& p_owner);
+
+ /**
+ * Returns the name of the component
+ */
+ std::string GetName() override;
+
+ /**
+ * Returns the type name of the component
+ */
+ std::string GetTypeName() override;
+
+ /**
+ * Keeps the horizontal layout direction
+ */
+ void SetDirection(EDirection p_direction) override;
+
+ protected:
+ bool IsDirectionEditable() const override;
+ };
+}
+
+namespace OvCore::ECS::Components
+{
+ template<>
+ struct ComponentTraits
+ {
+ static constexpr std::string_view Name = "class OvCore::ECS::Components::UI::CHorizontalLayout";
+ };
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/CImage.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/CImage.h
new file mode 100644
index 000000000..557b304ad
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/CImage.h
@@ -0,0 +1,159 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace OvCore::ECS { class Actor; }
+
+namespace OvCore::ECS::Components::UI
+{
+ /**
+ * Represents a renderable user interface image
+ */
+ class CImage : public AComponent
+ {
+ public:
+ /**
+ * Constructor
+ * @param p_owner
+ */
+ CImage(ECS::Actor& p_owner);
+
+ /**
+ * Returns the name of the component
+ */
+ std::string GetName() override;
+
+ /**
+ * Returns the type name of the component
+ */
+ virtual std::string GetTypeName() override;
+
+ /**
+ * Sets the texture rendered by the image
+ * @param p_texture
+ */
+ void SetTexture(OvRendering::Resources::Texture* p_texture);
+
+ /**
+ * Returns the texture rendered by the image
+ */
+ OvRendering::Resources::Texture* GetTexture() const;
+
+ /**
+ * Sets the image size
+ * @param p_size
+ */
+ void SetSize(const OvMaths::FVector2& p_size);
+
+ /**
+ * Returns the image display size stored by the owner Transform UI data
+ */
+ OvMaths::FVector2 GetSize() const;
+
+ /**
+ * Returns the stable quad size used before Transform UI/layout scaling
+ */
+ OvMaths::FVector2 GetIntrinsicSize() const;
+
+ /**
+ * Sets the image tint
+ * @param p_tint
+ */
+ void SetTint(const OvMaths::FVector4& p_tint);
+
+ /**
+ * Returns the image tint
+ */
+ const OvMaths::FVector4& GetTint() const;
+
+ /**
+ * Defines whether the image keeps its intrinsic aspect ratio when resized
+ * @param p_preserveAspect
+ */
+ void SetPreserveAspect(bool p_preserveAspect);
+
+ /**
+ * Returns whether the image keeps its intrinsic aspect ratio when resized
+ */
+ bool GetPreserveAspect() const;
+
+ /**
+ * Returns the generated quad mesh
+ */
+ OvRendering::Resources::Mesh& GetMesh() const;
+
+ /**
+ * Returns the generated UI image material, or nullptr if it cannot be initialized
+ */
+ OvCore::Resources::Material* GetMaterial();
+
+ /**
+ * Serialize the component
+ * @param p_doc
+ * @param p_node
+ */
+ virtual void OnSerialize(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node) override;
+
+ /**
+ * Deserialize the component
+ * @param p_doc
+ * @param p_node
+ */
+ virtual void OnDeserialize(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node) override;
+
+ /**
+ * Defines how the component should be drawn in the inspector
+ * @param p_root
+ */
+ virtual void OnInspector(OvUI::Internal::WidgetContainer& p_root) override;
+
+ private:
+ void SynchronizeTextureState() const;
+ void UpdateIntrinsicSize() const;
+ void RebuildMesh() const;
+ void RefreshMaterial();
+
+ private:
+ mutable OvRendering::Resources::Texture* m_texture = nullptr;
+ OvMaths::FVector4 m_tint = { 1.0f, 1.0f, 1.0f, 1.0f };
+ mutable OvMaths::FVector2 m_intrinsicSize = { 100.0f, 100.0f };
+ bool m_preserveAspect = false;
+
+ mutable std::unique_ptr m_mesh;
+ std::unique_ptr m_material;
+ OvTools::Eventing::Event<> m_textureChangedEvent;
+ OvRendering::Resources::Texture* m_materialTexture = nullptr;
+
+ mutable uint64_t m_textureManagerRevision = 0;
+ mutable uint64_t m_textureRevision = 0;
+ uint64_t m_materialManagerRevision = 0;
+ mutable bool m_textureReferenceDirty = true;
+ bool m_materialStateDirty = true;
+ mutable bool m_materialTextureDirty = true;
+ bool m_materialTintDirty = true;
+ };
+}
+
+namespace OvCore::ECS::Components
+{
+ template<>
+ struct ComponentTraits
+ {
+ static constexpr std::string_view Name = "class OvCore::ECS::Components::UI::CImage";
+ };
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/CLayoutGroup.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/CLayoutGroup.h
new file mode 100644
index 000000000..448b6a27b
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/CLayoutGroup.h
@@ -0,0 +1,307 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace OvCore::ECS { class Actor; }
+
+namespace OvCore::ECS::Components::UI
+{
+ /**
+ * Arranges direct user interface children along an axis
+ */
+ class CLayoutGroup : public AComponent
+ {
+ public:
+ using ChildOffset = std::pair;
+
+ struct ChildLayout
+ {
+ const ECS::Actor* actor = nullptr;
+ OvMaths::FVector2 offset = OvMaths::FVector2::Zero;
+ OvMaths::FVector2 size = OvMaths::FVector2::Zero;
+ bool hasDirectWidth = false;
+ bool hasDirectHeight = false;
+ bool valid = false;
+ };
+
+ enum class EDirection
+ {
+ HORIZONTAL,
+ VERTICAL
+ };
+
+ enum class EHorizontalAlignment
+ {
+ LEFT,
+ CENTER,
+ RIGHT
+ };
+
+ enum class EVerticalAlignment
+ {
+ TOP,
+ CENTER,
+ BOTTOM
+ };
+
+ /**
+ * Constructor
+ * @param p_owner
+ */
+ CLayoutGroup(ECS::Actor& p_owner);
+
+ /**
+ * Returns the name of the component
+ */
+ std::string GetName() override;
+
+ /**
+ * Returns the type name of the component
+ */
+ virtual std::string GetTypeName() override;
+
+ /**
+ * Sets the layout direction
+ * @param p_direction
+ */
+ virtual void SetDirection(EDirection p_direction);
+
+ /**
+ * Returns the layout direction
+ */
+ EDirection GetDirection() const;
+
+ /**
+ * Sets the non-negative spacing between children
+ * @param p_spacing
+ */
+ void SetSpacing(float p_spacing);
+
+ /**
+ * Returns the non-negative spacing between children
+ */
+ float GetSpacing() const;
+
+ /**
+ * Returns the actual layout container size after children, padding and Transform UI size are resolved
+ */
+ OvMaths::FVector2 GetComputedSize() const;
+
+ /**
+ * Sets the layout padding as left, right, top, bottom
+ * @param p_padding
+ */
+ void SetPadding(const OvMaths::FVector4& p_padding);
+
+ /**
+ * Returns the layout padding as left, right, top, bottom
+ */
+ const OvMaths::FVector4& GetPadding() const;
+
+ /**
+ * Sets the horizontal children alignment
+ * @param p_alignment
+ */
+ void SetHorizontalAlignment(EHorizontalAlignment p_alignment);
+
+ /**
+ * Returns the horizontal children alignment
+ */
+ EHorizontalAlignment GetHorizontalAlignment() const;
+
+ /**
+ * Sets the vertical children alignment
+ * @param p_alignment
+ */
+ void SetVerticalAlignment(EVerticalAlignment p_alignment);
+
+ /**
+ * Returns the vertical children alignment
+ */
+ EVerticalAlignment GetVerticalAlignment() const;
+
+ /**
+ * Sets whether the layout should control children width
+ * @param p_controlChildrenWidth
+ */
+ void SetControlChildrenWidth(bool p_controlChildrenWidth);
+
+ /**
+ * Returns whether the layout controls children width
+ */
+ bool GetControlChildrenWidth() const;
+
+ /**
+ * Sets whether the layout should control children height
+ * @param p_controlChildrenHeight
+ */
+ void SetControlChildrenHeight(bool p_controlChildrenHeight);
+
+ /**
+ * Returns whether the layout controls children height
+ */
+ bool GetControlChildrenHeight() const;
+
+ /**
+ * Sets whether child layout slots expand to use all available width
+ * @param p_forceExpandWidth
+ */
+ void SetForceExpandWidth(bool p_forceExpandWidth);
+
+ /**
+ * Returns whether child layout slots expand to use all available width
+ */
+ bool GetForceExpandWidth() const;
+
+ /**
+ * Sets whether child layout slots expand to use all available height
+ * @param p_forceExpandHeight
+ */
+ void SetForceExpandHeight(bool p_forceExpandHeight);
+
+ /**
+ * Returns whether child layout slots expand to use all available height
+ */
+ bool GetForceExpandHeight() const;
+
+ /**
+ * Returns the layout offset for a direct child
+ * @param p_child
+ */
+ OvMaths::FVector2 GetChildOffset(const ECS::Actor& p_child) const;
+
+ /**
+ * Returns the resolved layout data for a direct child
+ * @param p_child
+ */
+ std::optional GetChildLayout(const ECS::Actor& p_child) const;
+
+ /**
+ * Returns the resolved layout data for a direct child using an effective container size
+ * @param p_child
+ * @param p_containerSize
+ */
+ std::optional GetChildLayout(
+ const ECS::Actor& p_child,
+ const OvMaths::FVector2& p_containerSize
+ ) const;
+
+ /**
+ * Returns the layout offsets for direct children
+ */
+ std::vector GetChildOffsets() const;
+
+ /**
+ * Returns the resolved layout data for direct children
+ */
+ std::vector GetChildLayouts() const;
+
+ /**
+ * Serialize the component
+ * @param p_doc
+ * @param p_node
+ */
+ virtual void OnSerialize(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node) override;
+
+ /**
+ * Deserialize the component
+ * @param p_doc
+ * @param p_node
+ */
+ virtual void OnDeserialize(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node) override;
+
+ /**
+ * Defines how the component should be drawn in the inspector
+ * @param p_root
+ */
+ virtual void OnInspector(OvUI::Internal::WidgetContainer& p_root) override;
+
+ protected:
+ virtual bool IsDirectionEditable() const;
+
+ private:
+ struct LayoutInputChild
+ {
+ ECS::Actor* actor = nullptr;
+ OvMaths::FVector2 preferredSize = OvMaths::FVector2::Zero;
+ };
+
+ struct LayoutCacheChildSignature
+ {
+ const ECS::Actor* actor = nullptr;
+ OvMaths::FVector2 preferredSize = OvMaths::FVector2::Zero;
+ };
+
+ struct LayoutCacheSignature
+ {
+ EDirection direction = EDirection::HORIZONTAL;
+ float spacing = 0.0f;
+ OvMaths::FVector4 padding = OvMaths::FVector4::Zero;
+ EHorizontalAlignment horizontalAlignment = EHorizontalAlignment::CENTER;
+ EVerticalAlignment verticalAlignment = EVerticalAlignment::CENTER;
+ bool controlChildrenWidth = false;
+ bool controlChildrenHeight = false;
+ bool forceExpandWidth = false;
+ bool forceExpandHeight = false;
+ OvMaths::FVector2 containerSize = OvMaths::FVector2::Zero;
+ std::vector children;
+ };
+
+ struct LayoutCacheInput
+ {
+ LayoutCacheSignature signature;
+ std::vector children;
+ };
+
+ struct LayoutCache
+ {
+ bool valid = false;
+ LayoutCacheSignature signature;
+ OvMaths::FVector2 size = OvMaths::FVector2::Zero;
+ std::vector children;
+ };
+
+ LayoutCacheInput BuildLayoutCacheInput(const OvMaths::FVector2& p_containerSize) const;
+ const LayoutCache& GetResolvedLayout() const;
+ const LayoutCache& GetResolvedLayout(const OvMaths::FVector2& p_containerSize) const;
+ const LayoutCache& ResolveLayout(
+ const OvMaths::FVector2& p_containerSize,
+ LayoutCache& p_cache
+ ) const;
+ void InvalidateLayoutCache() const;
+ static bool HasSameLayoutSignature(const LayoutCacheSignature& p_lhs, const LayoutCacheSignature& p_rhs);
+
+ EDirection m_direction = EDirection::HORIZONTAL;
+ float m_spacing = 0.0f;
+ OvMaths::FVector4 m_padding = OvMaths::FVector4::Zero;
+ EHorizontalAlignment m_horizontalAlignment = EHorizontalAlignment::CENTER;
+ EVerticalAlignment m_verticalAlignment = EVerticalAlignment::CENTER;
+ bool m_controlChildrenWidth = false;
+ bool m_controlChildrenHeight = false;
+ bool m_forceExpandWidth = false;
+ bool m_forceExpandHeight = false;
+ mutable LayoutCache m_layoutCache;
+ mutable LayoutCache m_effectiveLayoutCache;
+ };
+}
+
+namespace OvCore::ECS::Components
+{
+ template<>
+ struct ComponentTraits
+ {
+ static constexpr std::string_view Name = "class OvCore::ECS::Components::UI::CLayoutGroup";
+ };
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/CText.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/CText.h
new file mode 100644
index 000000000..fd019a96c
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/CText.h
@@ -0,0 +1,221 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace OvCore::ECS { class Actor; }
+
+namespace OvCore::ECS::Components::UI
+{
+ /**
+ * Represents a renderable user interface text
+ */
+ class CText : public AComponent
+ {
+ public:
+ enum class EHorizontalAlignment
+ {
+ LEFT,
+ CENTER,
+ RIGHT
+ };
+
+ enum class EVerticalAlignment
+ {
+ TOP,
+ CENTER,
+ BOTTOM
+ };
+
+ /**
+ * Constructor
+ * @param p_owner
+ */
+ CText(ECS::Actor& p_owner);
+
+ /**
+ * Returns the name of the component
+ */
+ std::string GetName() override;
+
+ /**
+ * Returns the type name of the component
+ */
+ virtual std::string GetTypeName() override;
+
+ /**
+ * Sets the text content
+ * @param p_text
+ */
+ void SetText(const std::string& p_text);
+
+ /**
+ * Returns the text content
+ */
+ const std::string& GetText() const;
+
+ /**
+ * Sets the font resource path
+ * @param p_fontPath
+ */
+ void SetFontPath(const std::string& p_fontPath);
+
+ /**
+ * Returns the font resource path
+ */
+ const std::string& GetFontPath() const;
+
+ /**
+ * Sets the font size in canvas pixels
+ * @param p_fontSize
+ */
+ void SetFontSize(float p_fontSize);
+
+ /**
+ * Returns the font size in canvas pixels
+ */
+ float GetFontSize() const;
+
+ /**
+ * Sets the text color
+ * @param p_color
+ */
+ void SetColor(const OvMaths::FVector4& p_color);
+
+ /**
+ * Returns the text color
+ */
+ const OvMaths::FVector4& GetColor() const;
+
+ /**
+ * Sets the horizontal text alignment
+ * @param p_alignment
+ */
+ void SetHorizontalAlignment(EHorizontalAlignment p_alignment);
+
+ /**
+ * Returns the horizontal text alignment
+ */
+ EHorizontalAlignment GetHorizontalAlignment() const;
+
+ /**
+ * Sets the vertical text alignment
+ * @param p_alignment
+ */
+ void SetVerticalAlignment(EVerticalAlignment p_alignment);
+
+ /**
+ * Returns the vertical text alignment
+ */
+ EVerticalAlignment GetVerticalAlignment() const;
+
+ /**
+ * Returns the generated text mesh, or nullptr if the text cannot be rendered
+ */
+ OvRendering::Resources::Mesh* GetMesh() const;
+
+ /**
+ * Returns the generated text mesh for a resolved UI size, or nullptr if the text cannot be rendered
+ */
+ OvRendering::Resources::Mesh* GetMesh(const OvMaths::FVector2& p_resolvedSize) const;
+
+ /**
+ * Returns the generated text material, or nullptr if it cannot be initialized
+ */
+ OvRendering::Data::Material* GetMaterial();
+
+ /**
+ * Returns the generated text bounds size
+ */
+ const OvMaths::FVector2& GetSize() const;
+
+ /**
+ * Returns the generated text bounds size for a resolved UI size
+ */
+ OvMaths::FVector2 GetSize(const OvMaths::FVector2& p_resolvedSize) const;
+
+ /**
+ * Serialize the component
+ * @param p_doc
+ * @param p_node
+ */
+ virtual void OnSerialize(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node) override;
+
+ /**
+ * Deserialize the component
+ * @param p_doc
+ * @param p_node
+ */
+ virtual void OnDeserialize(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node) override;
+
+ /**
+ * Defines how the component should be drawn in the inspector
+ * @param p_root
+ */
+ virtual void OnInspector(OvUI::Internal::WidgetContainer& p_root) override;
+
+ private:
+ OvRendering::Resources::Font* GetFont() const;
+ void MarkMeshDirty();
+ void MarkMaterialSourceDirty();
+ void MarkMaterialColorDirty();
+ void RebuildLayout() const;
+ void RebuildLayout(const OvMaths::FVector2& p_uiSize) const;
+ void RebuildMesh() const;
+ void RebuildMesh(const OvMaths::FVector2& p_uiSize) const;
+ void RefreshMaterial();
+
+ private:
+ std::string m_text = "Text";
+ std::string m_fontPath;
+ float m_fontSize = 32.0f;
+ OvMaths::FVector4 m_color = { 1.0f, 1.0f, 1.0f, 1.0f };
+ EHorizontalAlignment m_horizontalAlignment = EHorizontalAlignment::LEFT;
+ EVerticalAlignment m_verticalAlignment = EVerticalAlignment::TOP;
+ mutable std::string m_unavailableFontPath;
+
+ mutable bool m_layoutDirty = true;
+ mutable bool m_layoutAvailable = false;
+ mutable OvMaths::FVector2 m_lastLayoutUISize = OvMaths::FVector2::Zero;
+ mutable OvRendering::Resources::Font* m_layoutFont = nullptr;
+ mutable uint64_t m_layoutFontRevision = 0;
+ mutable uint64_t m_layoutFontManagerRevision = 0;
+ mutable bool m_meshDirty = true;
+ mutable OvMaths::FVector2 m_lastMeshUISize = OvMaths::FVector2::Zero;
+ mutable OvMaths::FVector2 m_size = OvMaths::FVector2::Zero;
+ mutable TextLayoutEngine::Output m_layout;
+ mutable std::unique_ptr m_mesh;
+ std::unique_ptr m_material;
+ OvRendering::Resources::Font* m_materialFont = nullptr;
+ OvRendering::Resources::Shader* m_materialShader = nullptr;
+ uint64_t m_materialFontRevision = 0;
+ uint64_t m_materialFontManagerRevision = 0;
+ uint64_t m_materialManagerRevision = 0;
+ bool m_materialSourceDirty = true;
+ bool m_materialColorDirty = true;
+ };
+}
+
+namespace OvCore::ECS::Components
+{
+ template<>
+ struct ComponentTraits
+ {
+ static constexpr std::string_view Name = "class OvCore::ECS::Components::UI::CText";
+ };
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/CVerticalLayout.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/CVerticalLayout.h
new file mode 100644
index 000000000..83dcf468f
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/CVerticalLayout.h
@@ -0,0 +1,52 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+
+namespace OvCore::ECS::Components::UI
+{
+ /**
+ * Arranges direct user interface children vertically
+ */
+ class CVerticalLayout : public CLayoutGroup
+ {
+ public:
+ /**
+ * Constructor
+ * @param p_owner
+ */
+ CVerticalLayout(ECS::Actor& p_owner);
+
+ /**
+ * Returns the name of the component
+ */
+ std::string GetName() override;
+
+ /**
+ * Returns the type name of the component
+ */
+ std::string GetTypeName() override;
+
+ /**
+ * Keeps the vertical layout direction
+ */
+ void SetDirection(EDirection p_direction) override;
+
+ protected:
+ bool IsDirectionEditable() const override;
+ };
+}
+
+namespace OvCore::ECS::Components
+{
+ template<>
+ struct ComponentTraits
+ {
+ static constexpr std::string_view Name = "class OvCore::ECS::Components::UI::CVerticalLayout";
+ };
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/LayoutSolver.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/LayoutSolver.h
new file mode 100644
index 000000000..e1f3d4168
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/LayoutSolver.h
@@ -0,0 +1,81 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+
+namespace OvCore::ECS { class Actor; }
+
+namespace OvCore::ECS::Components::UI
+{
+ /**
+ * Settings driving the resolution of a layout group
+ */
+ struct LayoutSettings
+ {
+ CLayoutGroup::EDirection direction = CLayoutGroup::EDirection::HORIZONTAL;
+ float spacing = 0.0f;
+ OvMaths::FVector4 padding = OvMaths::FVector4::Zero;
+ CLayoutGroup::EHorizontalAlignment horizontalAlignment = CLayoutGroup::EHorizontalAlignment::CENTER;
+ CLayoutGroup::EVerticalAlignment verticalAlignment = CLayoutGroup::EVerticalAlignment::CENTER;
+ bool controlChildrenWidth = false;
+ bool controlChildrenHeight = false;
+ bool forceExpandWidth = false;
+ bool forceExpandHeight = false;
+ OvMaths::FVector2 containerSize = OvMaths::FVector2::Zero;
+ };
+
+ /**
+ * Child submitted to a layout resolution, with the size it would take on its own
+ */
+ struct LayoutChildInput
+ {
+ ECS::Actor* actor = nullptr;
+ OvMaths::FVector2 preferredSize = OvMaths::FVector2::Zero;
+ };
+
+ /**
+ * Placement resolved for a layout child, expressed in the centered layout coordinate system
+ */
+ struct LayoutChildResult
+ {
+ ECS::Actor* actor = nullptr;
+ OvMaths::FVector2 offset = OvMaths::FVector2::Zero;
+ OvMaths::FVector2 size = OvMaths::FVector2::Zero;
+ bool valid = false;
+ };
+
+ /**
+ * Container size and child placements produced by a layout resolution
+ */
+ struct LayoutResult
+ {
+ OvMaths::FVector2 size = OvMaths::FVector2::Zero;
+ std::vector children;
+ };
+
+ /**
+ * Arranges layout children along an axis, distributing the leftover space when requested
+ */
+ class LayoutSolver
+ {
+ public:
+ /**
+ * Resolve the container size and the placement of every child
+ * @param p_settings
+ * @param p_children
+ */
+ static LayoutResult Solve(
+ const LayoutSettings& p_settings,
+ const std::vector& p_children
+ );
+ };
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/TextLayoutEngine.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/TextLayoutEngine.h
new file mode 100644
index 000000000..e72430089
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/TextLayoutEngine.h
@@ -0,0 +1,68 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+#include
+
+#include
+#include
+
+namespace OvCore::ECS::Components::UI
+{
+ /**
+ * Resolves text wrapping, alignment and bounds without creating render geometry.
+ */
+ class TextLayoutEngine
+ {
+ public:
+ enum class EHorizontalAlignment
+ {
+ LEFT,
+ CENTER,
+ RIGHT
+ };
+
+ enum class EVerticalAlignment
+ {
+ TOP,
+ CENTER,
+ BOTTOM
+ };
+
+ struct Input
+ {
+ std::string_view text;
+ OvRendering::Resources::Font* font = nullptr;
+ float fontSize = 1.0f;
+ OvMaths::FVector2 uiSize = OvMaths::FVector2::Zero;
+ EHorizontalAlignment horizontalAlignment = EHorizontalAlignment::LEFT;
+ EVerticalAlignment verticalAlignment = EVerticalAlignment::TOP;
+ };
+
+ struct Glyph
+ {
+ float left = 0.0f;
+ float right = 0.0f;
+ float bottom = 0.0f;
+ float top = 0.0f;
+ float uMin = 0.0f;
+ float uMax = 0.0f;
+ float vMin = 0.0f;
+ float vMax = 0.0f;
+ };
+
+ struct Output
+ {
+ OvMaths::FVector2 size = OvMaths::FVector2::Zero;
+ OvMaths::FVector2 contentSize = OvMaths::FVector2::Zero;
+ std::vector glyphs;
+ };
+
+ static Output Layout(const Input& p_input);
+ };
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/TextMeshBuilder.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/TextMeshBuilder.h
new file mode 100644
index 000000000..577ad85d2
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/TextMeshBuilder.h
@@ -0,0 +1,35 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+
+#include
+#include
+
+namespace OvCore::ECS::Components::UI
+{
+ /**
+ * Builds text layout geometry from resolved font data.
+ */
+ class TextMeshBuilder
+ {
+ public:
+ using EHorizontalAlignment = TextLayoutEngine::EHorizontalAlignment;
+ using EVerticalAlignment = TextLayoutEngine::EVerticalAlignment;
+ using Input = TextLayoutEngine::Input;
+
+ struct Output
+ {
+ OvMaths::FVector2 size = OvMaths::FVector2::Zero;
+ std::unique_ptr mesh;
+ };
+
+ static Output Build(const Input& p_input);
+ static Output Build(const TextLayoutEngine::Output& p_layout);
+ };
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/UIInspectorUtils.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/UIInspectorUtils.h
new file mode 100644
index 000000000..43a603751
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/UIInspectorUtils.h
@@ -0,0 +1,21 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+
+namespace OvCore::ECS { class Actor; }
+
+namespace OvCore::ECS::Components::UI::UIInspectorUtils
+{
+ /**
+ * Draws a message when the given actor isn't part of a canvas hierarchy, and therefore isn't rendered
+ * @param p_root
+ * @param p_owner
+ */
+ void DrawCanvasRequirement(OvUI::Internal::WidgetContainer& p_root, const ECS::Actor& p_owner);
+}
diff --git a/Sources/OvCore/include/OvCore/ECS/Components/UI/UITransformResolver.h b/Sources/OvCore/include/OvCore/ECS/Components/UI/UITransformResolver.h
new file mode 100644
index 000000000..cf8f7f265
--- /dev/null
+++ b/Sources/OvCore/include/OvCore/ECS/Components/UI/UITransformResolver.h
@@ -0,0 +1,91 @@
+/**
+* @project: Overload
+* @author: Overload Tech.
+* @licence: MIT
+*/
+
+#pragma once
+
+#include
+#include
+
+namespace OvCore::ECS { class Actor; }
+
+namespace OvCore::ECS::Components::UI::UITransformResolver
+{
+ /**
+ * Convert a serialized value to an anchor preset, falling back to CENTER when out of range
+ * @param p_value
+ */
+ CTransform::EUIAnchorPreset ToAnchorPreset(int p_value);
+
+ /**
+ * Return the normalized offset applied to the parent size for the given anchor preset
+ * @param p_anchorPreset
+ */
+ OvMaths::FVector2 GetAnchorRatio(CTransform::EUIAnchorPreset p_anchorPreset);
+
+ /**
+ * Check if the given anchor preset stretches the element horizontally
+ * @param p_anchorPreset
+ */
+ bool IsHorizontalStretch(CTransform::EUIAnchorPreset p_anchorPreset);
+
+ /**
+ * Check if the given anchor preset stretches the element vertically
+ * @param p_anchorPreset
+ */
+ bool IsVerticalStretch(CTransform::EUIAnchorPreset p_anchorPreset);
+
+ /**
+ * Check if the horizontal anchored position is editable for the given anchor preset
+ * @param p_anchorPreset
+ */
+ bool IsHorizontalPositionEditable(CTransform::EUIAnchorPreset p_anchorPreset);
+
+ /**
+ * Check if the vertical anchored position is editable for the given anchor preset
+ * @param p_anchorPreset
+ */
+ bool IsVerticalPositionEditable(CTransform::EUIAnchorPreset p_anchorPreset);
+
+ /**
+ * Return the closest actor holding a canvas, the given actor included, or nullptr
+ * @param p_owner
+ */
+ ECS::Actor* FindCanvasOwner(ECS::Actor& p_owner);
+
+ /**
+ * Return the closest actor holding a canvas, the given actor included, or nullptr
+ * @param p_owner
+ */
+ const ECS::Actor* FindCanvasOwner(const ECS::Actor& p_owner);
+
+ /**
+ * Return the closest ancestor holding a canvas, excluding the given actor, or nullptr
+ * @param p_owner
+ */
+ const ECS::Actor* FindActiveCanvasOwner(const ECS::Actor& p_owner);
+
+ /**
+ * Check if the given actor has a canvas ancestor, meaning its user interface data is in effect
+ * @param p_owner
+ */
+ bool HasActiveUIData(const ECS::Actor& p_owner);
+
+ /**
+ * Check if the given actor is a direct child of a layout group
+ * @param p_owner
+ */
+ bool IsDrivenByLayout(const ECS::Actor& p_owner);
+
+ /**
+ * Return the transform size, falling back to the element size on any axis left to zero
+ * @param p_transform
+ * @param p_elementSize
+ */
+ OvMaths::FVector2 GetEffectiveSize(
+ const CTransform& p_transform,
+ const OvMaths::FVector2& p_elementSize
+ );
+}
diff --git a/Sources/OvCore/include/OvCore/Helpers/GUIHelpers.h b/Sources/OvCore/include/OvCore/Helpers/GUIHelpers.h
index e9d701d18..c24e6a0d3 100644
--- a/Sources/OvCore/include/OvCore/Helpers/GUIHelpers.h
+++ b/Sources/OvCore/include/OvCore/Helpers/GUIHelpers.h
@@ -61,6 +61,7 @@ namespace OvCore::Helpers
using FileItemBuilderCallback = std::function, bool, bool)>;
using OpenProviderCallback = std::function;
using PickerProviderCallback = std::function;
+ using PickerCloseProviderCallback = std::function;
using PickerSearchTextProviderCallback = std::function;
using IconProviderCallback = std::function;
using ActorSelectionProviderCallback = std::function;
@@ -86,6 +87,8 @@ namespace OvCore::Helpers
static void SetPickerProvider(PickerProviderCallback p_provider);
static void OpenPicker(PickerItemList p_items, std::string p_title);
+ static void SetPickerCloseProvider(PickerCloseProviderCallback p_provider);
+ static void ClosePicker();
static void SetPickerSearchTextProvider(PickerSearchTextProviderCallback p_provider);
static std::string GetPickerSearchText();
diff --git a/Sources/OvCore/include/OvCore/Rendering/EngineBufferRenderFeature.h b/Sources/OvCore/include/OvCore/Rendering/EngineBufferRenderFeature.h
index 6a9c1ce6f..5dc4b8646 100644
--- a/Sources/OvCore/include/OvCore/Rendering/EngineBufferRenderFeature.h
+++ b/Sources/OvCore/include/OvCore/Rendering/EngineBufferRenderFeature.h
@@ -7,8 +7,8 @@
#pragma once
#include
-#include