/// 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 #include #include #include #include #include #include #include #include #include #include #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 Node { public: protected: std::vector 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 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 DescendantRemoved; // TODO: Debug /// This event triggers when the hierarchy this widget is contained within changes. Event AncestryChanged; /// This event triggers when a widget is added to this widget's list of children. Event ChildAdded; /// This event triggers when a widget is removed from this widget's list of children. Event ChildRemoved; /// This event triggers right before this widget gets deallocated. Event 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 FindFirstChild(const std::string& name); /// Returns a flat list of all nodes that are lower in the hierarchy list. std::vector GetDescendants(); /// Returns a flat list of all nodes that are higher in the hierarchy list. std::vector GetAncestors(); /// @returns the highest ancestor which is not the scene root. Widget* GetHighestNonRootAncestor(); /// Returns the nodes directly descendant to this widget. std::vector 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 children{}; std::vector 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; }; }