443 lines
20 KiB
C++
443 lines
20 KiB
C++
/// Josh's User Interface Library
|
|
/// A C++20 Library for creating, styling, and rendering of a UI/UX widgets.
|
|
/// Developed and Maintained by Josh O'Leary @ Redacted Software.
|
|
/// Special Thanks to William Tomasine II and Maxine Hayes.
|
|
/// (c) 2024 Redacted Software
|
|
/// This work is dedicated to the public domain.
|
|
|
|
/// @file Widget.hpp
|
|
/// @desc Base Widget Class - All JUI widgets extend their behavior from this class.
|
|
/// @edit 2024-10-11
|
|
|
|
#pragma once
|
|
|
|
|
|
/// Ideal refactor:
|
|
/// * Completely separate Component for a given behavior / area of concern
|
|
/// * XYZStyler class, stored as a pointer in the widget.
|
|
/// * Smart pointers
|
|
/// * Intelligent layout.
|
|
/// *
|
|
|
|
|
|
|
|
#include <Event.h>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <J3ML/LinearAlgebra.hpp>
|
|
#include <JUI/UDim2.hpp>
|
|
#include <Color3.hpp>
|
|
#include <Color4.hpp>
|
|
#include <JGL/JGL.h>
|
|
#include <ReWindow/types/Key.h>
|
|
#include <JUI/Tween.hpp>
|
|
#include <JUI/JUI.hpp>
|
|
#include "JUI/DefaultStyle.hpp"
|
|
|
|
using namespace JGL;
|
|
|
|
namespace JUI {
|
|
|
|
using namespace J3ML::Math;
|
|
using namespace J3ML::LinearAlgebra;
|
|
|
|
struct EventArgs {
|
|
|
|
};
|
|
|
|
struct FocusedArgs {};
|
|
struct UnfocusedEventArgs {};
|
|
struct ChildAddedEventArgs {};
|
|
struct ChildRemovedEventArgs {};
|
|
|
|
/// TODO: Refactor the member functions providing the parent-child hierarchy into the Node mixin.
|
|
template <class TNode>
|
|
class Node {
|
|
public:
|
|
|
|
protected:
|
|
std::vector<TNode*> children;
|
|
TNode* parent;
|
|
};
|
|
|
|
|
|
/// Widget is the base class for all JUI elements and represents any object that can be in the tree hierarchy.
|
|
/// Some widgets are expressly used for layout, and therefore do not strictly-speaking 'Appear' on screen.
|
|
/// Widgets exist in a tree hierarchy where each object has one parent, and an arbitrary number of children. No circular references are allowed.
|
|
class Widget {
|
|
public:
|
|
/// The default constructor for Widget. Generally, JUI widgets will initialize member data,
|
|
/// and apply a default style in this constructor. Be aware of this, as this is not "idiomatic C++".
|
|
Widget();
|
|
/// Construct a new widget by specifying its parent,
|
|
explicit Widget(Widget* parent);
|
|
virtual ~Widget() = default;
|
|
public:
|
|
#pragma region Events
|
|
/// An event that triggers when the widget is in-focus, generally when the mouse is hovering it.
|
|
/// (The actual trigger may differ for each widget)
|
|
Event<> Focused;
|
|
/// An event that triggers when the widget loses focus.
|
|
Event<> Unfocused;
|
|
/// This event triggers when a new widget is added to this widget, or any descendant of this widget.
|
|
Event<Widget *> DescendantAdded; // TODO: Debug
|
|
/// This event triggers when a widget is removed from this widget's children, or any of it's childrens' children, and so forth.
|
|
Event<Widget *> DescendantRemoved; // TODO: Debug
|
|
/// This event triggers when the hierarchy this widget is contained within changes.
|
|
Event<Widget *, Widget*> AncestryChanged;
|
|
/// This event triggers when a widget is added to this widget's list of children.
|
|
Event<Widget *> ChildAdded;
|
|
/// This event triggers when a widget is removed from this widget's list of children.
|
|
Event<Widget *> ChildRemoved;
|
|
/// This event triggers right before this widget gets deallocated.
|
|
Event<Widget *> Destroying;
|
|
#pragma endregion
|
|
public:
|
|
#pragma region Hierarchy
|
|
/// Adds a given widget to this widget's list of children.
|
|
/// @return The widget in question.
|
|
Widget* Add(Widget* newChild);
|
|
|
|
/// Returns true if this object is an ancestor to the given widget.
|
|
bool IsAncestorOf(Widget* descendant) const;
|
|
|
|
/// Returns true if this object is a descendant of the given widget.
|
|
bool IsDescendantOf(Widget* ancestor) const;
|
|
|
|
/// Returns the first child widget that has the given symbolic name
|
|
std::optional<Widget*> FindFirstChild(const std::string& name);
|
|
|
|
/// Returns a flat list of all nodes that are lower in the hierarchy list.
|
|
std::vector<Widget*> GetDescendants();
|
|
/// Returns a flat list of all nodes that are higher in the hierarchy list.
|
|
std::vector<Widget*> GetAncestors();
|
|
|
|
/// @returns the highest ancestor which is not the scene root.
|
|
Widget* GetHighestNonRootAncestor();
|
|
/// Returns the nodes directly descendant to this widget.
|
|
std::vector<Widget*> GetChildren();
|
|
|
|
/// Calculates the final size in pixels of this element.
|
|
[[nodiscard]] virtual Vector2 GetAbsoluteSize() const;
|
|
/// Calculates the final position in pixels of this element.
|
|
[[nodiscard]] virtual Vector2 GetAbsolutePosition() const;
|
|
[[nodiscard]] virtual float GetAbsoluteRotation() const;
|
|
|
|
/// Returns the parent of this widget, or a nullptr if there is no parent.
|
|
/// @see GetFamilyTreeRoot().
|
|
Widget* GetParent() const;
|
|
|
|
|
|
/// Sets the parent object of this widget. Positioning and sizing of a widget is relative to it's parent.
|
|
void Parent(Widget*);
|
|
|
|
/// Returns true if this widget is a 'descendant' of the specified potential ancestor. Otherwise returns false.
|
|
bool IsDescendantOf(Widget *ancestor);
|
|
|
|
/// Returns true if this widget is a 'ancestor' of the specified potential descendant. Otherwise returns false.
|
|
bool IsAncestorOf(Widget *descendant);
|
|
|
|
/// Returns the first ancestor in this widgets hierarchy that does not have its own parent.
|
|
/// In a well-formed JUI menu, this **should** always be a Scene.
|
|
Widget* GetFamilyTreeRoot() const;
|
|
|
|
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Layout
|
|
|
|
/// Returns the menu-coordinates that are used to position this widget in relation to its parent.
|
|
/// @see class UDim2, Position(const UDim2&),
|
|
[[nodiscard]] UDim2 Position() const;
|
|
|
|
/// Returns the Z-Index of this widget.
|
|
/// This value determines the order in which a widget renders to the screen relative to other Widgets.
|
|
/// @note This applies in ascending priority order,
|
|
/// meaning lower values are rendered first, placing higher values on top of lower values.
|
|
/// @note The range of valid values is -MAX_INT to MAX_INT.
|
|
/// @note This does not manipulate the OpenGL Z buffer, rather, when rendering,
|
|
/// we sort the direct children of a widget widget and render.
|
|
[[nodiscard]] int ZIndex() const;
|
|
|
|
/// Sets this widgets z-index.
|
|
/// @see ZIndex().
|
|
void ZIndex(int);
|
|
|
|
/// Returns the menu-coordinates that are used to size this widget in relation to its parent.
|
|
/// @see Size(), class UDim2
|
|
[[nodiscard]] UDim2 Size() const;
|
|
|
|
/// Sets the widgets pixel and scalar position using a combined data type.
|
|
/// This position is centered around the object's anchor point.
|
|
/// @see Position(), AnchorPoint(), AnchorPoint(), class UDim2.
|
|
void Position(const UDim2&);
|
|
|
|
/// Sets this widget's pixel and scalar size using a combined data type.
|
|
/// @see Size(), class UDim2.
|
|
void Size(const UDim2&);
|
|
|
|
/// Creates and runs an animation on the Position of this widget.
|
|
Tween* TweenPosition(const UDim2& goal, TweenInfo params = {});
|
|
|
|
/// Creates and runs an animation on the Size of this widget.
|
|
Tween* TweenSize(const UDim2& goal, TweenInfo params = {});
|
|
|
|
/// Determines the origin point of a Widget, relative to it's absolute size.
|
|
[[nodiscard]] Vector2 AnchorPoint() const;
|
|
|
|
///
|
|
void AnchorPoint(const Vector2 &point);
|
|
|
|
///
|
|
Tween* TweenAnchorPoint(const Vector2& goal, TweenInfo info = {});
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Padding
|
|
/// Returns the padding factor on the left of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
/// @see PaddingLeft(), class UDim.
|
|
[[nodiscard]] UDim PaddingLeft() const;
|
|
/// Returns the padding factor on the top of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
/// @see PaddingTop(), class UDim.
|
|
[[nodiscard]] UDim PaddingTop() const;
|
|
/// Returns the padding factor on the right of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
/// @see PaddingRight(), class UDim.
|
|
[[nodiscard]] UDim PaddingRight() const;
|
|
/// Returns the padding factor on the bottom of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
/// @see PaddingBottom(), class UDim.
|
|
[[nodiscard]] UDim PaddingBottom() const;
|
|
|
|
/// Sets the padding factor on the left of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
/// @see PaddingLeft(), class UDim.
|
|
void PaddingLeft(const UDim &pad_left);
|
|
/// Sets the padding factor on the top of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
/// @see PaddingLeft(), class UDim.
|
|
void PaddingTop(const UDim &pad_top);
|
|
/// Sets the padding factor on the right of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
/// @see PaddingLeft(), class UDim.
|
|
void PaddingRight(const UDim &pad_right);
|
|
/// Sets the padding factor on the bottom of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
/// @see PaddingLeft(), class UDim.
|
|
void PaddingBottom(const UDim &pad_bottom);
|
|
|
|
/// Sets the padding factor on the four respective sides of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
/// @param left
|
|
/// @param top
|
|
/// @param right
|
|
/// @param bottom
|
|
void Padding(const UDim& left, const UDim& top, const UDim& right, const UDim& bottom);
|
|
|
|
/// Sets the same padding factor on all four sides of this widget.
|
|
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
|
|
void Padding(const UDim& padding);
|
|
|
|
|
|
Tween* TweenPaddingLeft(const UDim &goal, TweenInfo info = {});
|
|
Tween* TweenPaddingRight(const UDim &goal, TweenInfo info = {});
|
|
Tween* TweenPaddingTop(const UDim &goal, TweenInfo info = {});
|
|
Tween* TweenPaddingBottom(const UDim &goal, TweenInfo info = {});
|
|
Tween* TweenPadding(const UDim& goalLeft, const UDim& goalTop, const UDim& goalRight, const UDim& goalBottom, TweenInfo info = {});
|
|
Tween* TweenPadding(const UDim& goal, TweenInfo info = {});
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Margin
|
|
/// Returns the margin factor on the left of this widget.
|
|
[[nodiscard]] UDim MarginLeft() const;
|
|
/// Returns the margin factor on the top of this widget.
|
|
[[nodiscard]] UDim MarginTop() const;
|
|
/// Returns the margin factor on the right of this widget.
|
|
[[nodiscard]] UDim MarginRight() const;
|
|
/// Returns the margin factor on the bottom of this widget.
|
|
[[nodiscard]] UDim MarginBottom() const;
|
|
|
|
/// Sets the amount (in Pixels + Scale) to apply margin on the left-side of the widget.
|
|
/// @see UDim, Margin()
|
|
void MarginLeft(const UDim &ml);
|
|
/// Sets the amount (in Pixels + Scale) to apply margin on the top-side of the widget.
|
|
/// @see UDim, Margin()
|
|
void MarginTop(const UDim &mt);
|
|
/// Sets the amount (in Pixels + Scale) to apply margin on the right-side of the widget.
|
|
/// @see UDim, Margin()
|
|
void MarginRight(const UDim &mr);
|
|
/// Sets the amount (in Pixels + Scale) to apply margin on the bottom-side of the widget.
|
|
/// @see UDim, Margin()
|
|
void MarginBottom(const UDim &mb);
|
|
|
|
/// Sets the margin factor of each side of the widget.
|
|
/// @param left The amount of margin to apply on the left. (Pixels, Scale)
|
|
/// @param top The amount of margin to apply on the top.
|
|
/// @param right The amount of margin to apply on the right.
|
|
/// @param bottom The amount of margin to apply on the bottom.
|
|
void Margin(const UDim& left, const UDim& top, const UDim& right, const UDim& bottom);
|
|
|
|
/// Sets the margin factor on all four sides of this widget to the same value.
|
|
/// @see Margin(const UDim&, const UDim&, const UDim&, const UDim&).
|
|
void Margin(const UDim& margin);
|
|
|
|
Tween* TweenMarginLeft(const UDim &goal, TweenInfo info = {});
|
|
Tween* TweenMarginTop(const UDim &goal, TweenInfo info = {});
|
|
Tween* TweenMarginBottom(const UDim &goal, TweenInfo info = {});
|
|
Tween* TweenMarginRight(const UDim &goal, TweenInfo info = {});
|
|
|
|
Tween* TweenMargin(const UDim& goalLeft, const UDim& goalTop, const UDim& goalRight, const UDim& goalBottom, TweenInfo info = {});
|
|
Tween* TweenMargin(const UDim& goal, TweenInfo info = {});
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Metadata
|
|
/// Returns this widgets mnemonic name.
|
|
/// Widgets can optionally be assigned a name that can be used to retrieve it from a widget tree node.
|
|
/// @see Name().
|
|
[[nodiscard]] std::string Name() const;
|
|
|
|
/// Sets this widgets mnemonic name.
|
|
/// Widgets can optionally be assigned a name that can be used to retrieve it from a widget tree node.
|
|
/// @see Name().
|
|
void Name(const std::string &new_name);
|
|
|
|
/// Returns whether the widget is to be rendered.
|
|
/// This function does not indicate whether the widget is **actually seeable** on-screen.
|
|
/// @see Visible().
|
|
[[nodiscard]] bool IsVisible() const;
|
|
|
|
/// Sets whether the widget is to be rendered. Children are also not rendered if disabled.
|
|
/// @see IsVisible().
|
|
void Visible(bool enabled);
|
|
|
|
|
|
|
|
/// Returns whether the mouse is inside this widget's approximate bounding-box.
|
|
bool IsMouseInside() const;
|
|
|
|
/// Returns whether the mouse is inside the bounding box of one of this widget's children.
|
|
/// @param include_this Whether to return true if the mouse is inside this widget, if that condition is also met.
|
|
bool IsMouseInsideChildren(bool include_this = false) const;
|
|
|
|
/// Returns whether the mouse is inside the bounding box of one of this widget's descendants (children of children, recursive.)
|
|
/// @param include_this Whether to return true if the mouse is inside this widget, if that condition is also met.
|
|
bool IsMouseInsideDescendants(bool include_this = false) const;
|
|
|
|
int LayoutOrder() const { return layout_order; }
|
|
void LayoutOrder(int value) { layout_order = value;}
|
|
|
|
#pragma endregion
|
|
|
|
/// Returns the complete bounding box around this instance that will be rendered onto.
|
|
/// This differs from AbsolutePosition and AbsoluteSize in that they are computed for positioning elements relative to each other.
|
|
/// Only this method represents the full bounding box of the widget.
|
|
[[nodiscard]] virtual AABB2D GetActualRenderBounds() const;
|
|
|
|
AABB2D AbsoluteBounds() const;
|
|
|
|
void SetViewportSize(const Vector2& vps);
|
|
|
|
float ComputeElementPadding(float size, const UDim& padding);
|
|
|
|
Vector2 ComputeElementPadding(const Vector2& size, const UDim2& padding) const;
|
|
|
|
public:
|
|
|
|
virtual void PreDraw() {}
|
|
virtual void PostDraw() {}
|
|
virtual void InnerDraw() {}
|
|
|
|
|
|
/// Renders the widget to the current OpenGL Context using JGL.
|
|
/// The user should call this on their Scene instances only. JUI will handle the rest.
|
|
virtual void Draw();
|
|
/// Performs the update logic of the widget.
|
|
/// The user should call this on their Scene instances only. JUI will handle the rest.
|
|
virtual void Update(float delta);
|
|
|
|
/// Informs a widget that the mouse has been moved to a new location.
|
|
/// This is designed in such a way that the end-user can plug this into their existing code.
|
|
/// The user should call this on their Scene instances only. JUI will handle the rest.
|
|
/// See ReWindowIntegrationDemo for an example.
|
|
/// @return True if this widget, or one of its descendants should "consume" the input event,
|
|
/// meaning it no longer needs to be passed to the next widgets in the hierarchy.
|
|
/// @note It is acceptable for a widget to "observe" the input event, but not "consume" it.
|
|
virtual bool ObserveMouseMovement(const Vector2& latest_known_pos);
|
|
|
|
/// Informs a widget that a mouse button has pressed or released.
|
|
/// This is designed in such a way that the end-user can plug this into their existing code.
|
|
/// The user should call this on their Scene instances only. JUI will handle the rest.
|
|
/// See ReWindowIntegrationDemo for an example.
|
|
/// @return True if this widget, or one of its descendants should "consume" the input event,
|
|
/// meaning it no longer needs to be passed to the next widgets in the hierarchy.
|
|
/// @note It is acceptable for a widget to "observe" the input event, but not "consume" it.
|
|
virtual bool ObserveMouseInput(MouseButton btn, bool pressed);
|
|
|
|
/// Informs a widget that the mouse wheel has been moved.
|
|
/// This is designed in such a way that the end-user can plug this into their existing code.
|
|
/// The user should call this on their Scene instances only. JUI will handle the rest.
|
|
/// See ReWindowIntegrationDemo for an example.
|
|
virtual bool ObserveMouseWheel(int mwheel);
|
|
|
|
/// Informs a widget that a key has been pressed or released.
|
|
/// This is designed in such a way that the end-user can plug this into their existing code.
|
|
/// The user should call this on their Scene instances only. JUI will handle the rest.
|
|
/// See ReWindowIntegrationDemo for an example.
|
|
virtual bool ObserveKeyInput(Key key, bool pressed);
|
|
|
|
protected:
|
|
void DrawChildWidgets();
|
|
void UpdateChildWidgets(float delta);
|
|
protected:
|
|
MouseButton mbtn = MouseButton::Left;
|
|
bool mb_state = false;
|
|
bool prev_mb_state = false;
|
|
Vector2 last_known_mouse_pos = {0,0};
|
|
UDim2 position = {0_px, 0_px};
|
|
UDim2 size = {50_px, 50_px};
|
|
Widget* parent = nullptr;
|
|
std::vector<Widget*> children{};
|
|
std::vector<Tween*> tweens{};
|
|
float rotation = 0;
|
|
std::string name;
|
|
bool selected = false;
|
|
UDim pad_left = Style::BasePadding;
|
|
UDim pad_right = Style::BasePadding;
|
|
UDim pad_top = Style::BasePadding;
|
|
UDim pad_bottom = Style::BasePadding;
|
|
UDim margin_left = Style::BasePadding;
|
|
UDim margin_right = Style::BasePadding;
|
|
UDim margin_top = Style::BasePadding;
|
|
UDim margin_bottom = Style::BasePadding;
|
|
Vector2 anchor_point = {0.f, 0.f};
|
|
bool visible = true;
|
|
Widget* next = nullptr;
|
|
Widget* prev = nullptr;
|
|
int zindex = 0;
|
|
bool z_dirty = false;
|
|
int layout_order = 0;
|
|
Vector2 viewport_size{0,0};
|
|
|
|
bool is_mouse_inside = false;
|
|
|
|
/// Returns the amount of pixels this widget will be padded by from the top-left.
|
|
/// Generally, the widget will be shrunk and moved over by this amount, relative to the parent.
|
|
Vector2 GetAbsolutePaddingTopLeft() const;
|
|
|
|
/// Returns the amount of pixels this widget will be padded by from bottom-right.
|
|
/// Generally, the widget will be shrunk by twice this amount, relative to the parent.
|
|
Vector2 GetAbsolutePaddingBottomRight() const;
|
|
Vector2 GetAbsoluteMarginTopLeft();
|
|
Vector2 GetAbsoluteMarginBottomRight();
|
|
void UpdateTweens(float elapsed);
|
|
|
|
/// Calculate if mouse is inside this element at the start of it's update frame.
|
|
bool ComputeIsMouseInside() const;
|
|
};
|
|
}
|