Files
ReJUI/include/JUI/Base/Widget.hpp

451 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 PaddedElementStyle {
};
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;
[[nodiscard]] virtual Vector2 GetAbsoluteCentroid() 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;
};
}