diff --git a/CMakeLists.txt b/CMakeLists.txt index 0738308..a35ac22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.18..3.27) project(JUI - VERSION 1.1 + VERSION 4.0 LANGUAGES CXX) if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) diff --git a/include/JUI/Base/Widget.hpp b/include/JUI/Base/Widget.hpp index 26ba4d4..14fda5a 100644 --- a/include/JUI/Base/Widget.hpp +++ b/include/JUI/Base/Widget.hpp @@ -11,6 +11,16 @@ #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 @@ -31,17 +41,37 @@ 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. + /// 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 it's parent, + /// Construct a new widget by specifying its parent, explicit Widget(Widget* parent); - virtual ~Widget() {} + virtual ~Widget() = default; public: #pragma region Events /// An event that triggers when the widget is in-focus, generally when the mouse is hovering it. @@ -111,6 +141,8 @@ namespace JUI { /// In a well-formed JUI menu, this **should** always be a Scene. Widget* GetFamilyTreeRoot() const; + + #pragma endregion #pragma region Layout @@ -391,6 +423,8 @@ namespace JUI { 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; @@ -401,5 +435,8 @@ namespace JUI { 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; }; } diff --git a/include/JUI/Mixins/Clickable.hpp b/include/JUI/Mixins/Clickable.hpp index 99bf001..e6547a5 100644 --- a/include/JUI/Mixins/Clickable.hpp +++ b/include/JUI/Mixins/Clickable.hpp @@ -14,10 +14,27 @@ #include #include +#include "Hoverable.hpp" + namespace JUI { + + + struct ClickEventArgs { + public: + protected: + private: + }; + struct ReleaseEventArgs { + public: + protected: + private: + }; + /// A mixin helper class that provides behavior and events for clickable widgets. class Clickable { public: + Event> Clicked; + Event Released; Event OnClickEvent; Event OnReleaseEvent; @@ -34,4 +51,4 @@ namespace JUI { bool clicked = false; bool click_debounce = false; }; -} \ No newline at end of file +} diff --git a/include/JUI/Mixins/Hoverable.hpp b/include/JUI/Mixins/Hoverable.hpp index 0831979..3d2a8cc 100644 --- a/include/JUI/Mixins/Hoverable.hpp +++ b/include/JUI/Mixins/Hoverable.hpp @@ -18,6 +18,14 @@ namespace JUI { + + /// Interface for anything that can report mouse presence. + class IMouseAware { + public: + virtual ~IMouseAware() = default; + virtual bool IsMouseAware() const = 0; + }; + /// A mixin helper class that provides behavior for hoverable objects. /// A hoverable object pays attention to when the mouse enters and leaves it's bounds. class Hoverable { @@ -39,7 +47,10 @@ namespace JUI virtual void OnExitChildren(const Vector2& mouse); virtual void OnExitDescendants(const Vector2& mouse); - void Update(const Vector2& m_pos, float delta); + void Update(bool mouse_inside, float delta); + + /// @note This member function **must** be implemented for classes that implement Hoverable. + //[[nodiscard]] virtual bool IsMouseInside() const = 0; //virtual void SetTooltip(const std::string& content, float delay) {} protected: //Tooltip* tooltip = nullptr; diff --git a/include/JUI/Widgets/ColorPicker.hpp b/include/JUI/Widgets/ColorPicker.hpp index c1df398..6a8643a 100644 --- a/include/JUI/Widgets/ColorPicker.hpp +++ b/include/JUI/Widgets/ColorPicker.hpp @@ -164,4 +164,11 @@ namespace JUI { private: }; + class ColorPickerDialog : public Window { + public: + protected: + private: + + }; + } diff --git a/include/JUI/Widgets/FpsGraph.hpp b/include/JUI/Widgets/FpsGraph.hpp index 56cecce..ae8306d 100644 --- a/include/JUI/Widgets/FpsGraph.hpp +++ b/include/JUI/Widgets/FpsGraph.hpp @@ -124,6 +124,5 @@ namespace JUI { protected: FpsGraph* data_graph; private: - }; } \ No newline at end of file diff --git a/include/JUI/Widgets/CheckboxLabel.hpp b/include/JUI/Widgets/LabeledCheckbox.hpp similarity index 100% rename from include/JUI/Widgets/CheckboxLabel.hpp rename to include/JUI/Widgets/LabeledCheckbox.hpp diff --git a/include/JUI/Widgets/Link.hpp b/include/JUI/Widgets/Link.hpp index 546e973..ff2225b 100644 --- a/include/JUI/Widgets/Link.hpp +++ b/include/JUI/Widgets/Link.hpp @@ -17,6 +17,8 @@ namespace JUI { + struct LinkInvokedEventArgs {}; + /// Controls when Link widgets fire their Cicked callback. It can fire right when the widget is clicked, or when it is is released. enum class LinkClickMode { Press, Release }; @@ -24,8 +26,8 @@ namespace JUI { class Link : public Text, public Clickable, public Hoverable { public: enum LinkClickMode ClickMode = LinkClickMode::Release; + Event> Invoked; /// This event is fired when the user clicks/releases the link. - Event<> Clicked; Link() : Text(), Clickable(), Hoverable() {} explicit Link(const std::string& content) : Link() { this->Content(content); @@ -54,7 +56,7 @@ namespace JUI { TextColor(Colors::White); if (!fire_on_release) { - Clicked.Invoke(); + Clicked.Invoke(std::nullopt); already_clicked = true; } } @@ -69,7 +71,7 @@ namespace JUI { TextColor(Colors::Black); if (fire_on_release) { - Clicked.Invoke(); + Clicked.Invoke(std::nullopt); already_clicked = true; } } @@ -80,9 +82,8 @@ namespace JUI { Text::Update(delta); // TODO: Why does hovered not handle this? - hovered = IsMouseInside(); - Hoverable::Update(last_known_mouse_pos, delta); + Hoverable::Update(IsMouseInside(), delta); // TODO: This is duplicated between here and Window.cpp // Will be abstracted away into Clickable shortly. diff --git a/include/JUI/Widgets/ToggleButton.hpp b/include/JUI/Widgets/ToggleButton.hpp new file mode 100644 index 0000000..8d0c06c --- /dev/null +++ b/include/JUI/Widgets/ToggleButton.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "Button.hpp" + + +namespace JUI { + /// The + class ToggleButton : public Button { + + }; +} diff --git a/main.cpp b/main.cpp index 21f99f1..02dc20b 100644 --- a/main.cpp +++ b/main.cpp @@ -331,13 +331,21 @@ JUI::Rect* CreateWidgetList(JUI::Widget* root) { auto* radio_c_btn = new Checkbox(radio_btn_set_layout); radio_c_btn->Size({20_px, 20_px}); - auto* linkbox = new Rect(collapsible->ContentBox()); - linkbox->Size({100_percent, 30_px}); - linkbox->Position({0_px, 20_px}); + auto* link_container = new Rect(collapsible->ContentBox()); + { + link_container->Size({100_percent, 30_px}); + link_container->Position({0_px, 20_px}); + + auto* link = new JUI::Link(link_container); + link->Content("Clickable Link"); + link->Clicked += [](auto event) { + std::cout << "Link Released" << std::endl; + }; + } + + - auto* link = new JUI::Link(linkbox); - link->Content("This Dick"); // What the FUCK? /*auto* radio_c_label = new TextRect(radio_btn_set_layout); diff --git a/src/JUI/Base/Widget.cpp b/src/JUI/Base/Widget.cpp index 5878efc..e51be31 100644 --- a/src/JUI/Base/Widget.cpp +++ b/src/JUI/Base/Widget.cpp @@ -281,6 +281,9 @@ void Widget::UpdateTweens(float elapsed) { } void Widget::Update(float delta) { + + is_mouse_inside = ComputeIsMouseInside(); + UpdateChildWidgets(delta); UpdateTweens(delta); } @@ -313,7 +316,8 @@ Widget* Widget::GetFamilyTreeRoot() const { return parent->GetFamilyTreeRoot(); } -bool Widget::IsMouseInside() const { + +bool Widget::ComputeIsMouseInside() const { float x = last_known_mouse_pos.x; float y = last_known_mouse_pos.y; auto pos = GetAbsolutePosition(); @@ -325,6 +329,20 @@ bool Widget::IsMouseInside() const { return false; } +bool Widget::IsMouseInside() const { + return is_mouse_inside; + + /*float x = last_known_mouse_pos.x; + float y = last_known_mouse_pos.y; + auto pos = GetAbsolutePosition(); + auto size = GetAbsoluteSize(); + + if (x > pos.x && y > pos.y && x < pos.x + size.x && y < pos.y + size.y) + return true; + + return false;*/ +} + bool Widget::IsMouseInsideChildren(bool include_this) const { if (include_this) if (IsMouseInside()) diff --git a/src/JUI/Mixins/Clickable.cpp b/src/JUI/Mixins/Clickable.cpp index caac156..7057843 100644 --- a/src/JUI/Mixins/Clickable.cpp +++ b/src/JUI/Mixins/Clickable.cpp @@ -1,7 +1,7 @@ #include void JUI::Clickable::Update(const Vector2 &mpos, const JUI::MouseButton &btn, bool hovering) { - + //Hoverable::Update(mpos, delta); } void JUI::Clickable::OnRelease(const Vector2 &MousePos, const JUI::MouseButton &MouseButton, bool MouseStillOver) { diff --git a/src/JUI/Mixins/Hoverable.cpp b/src/JUI/Mixins/Hoverable.cpp index 56ece83..babd6c1 100644 --- a/src/JUI/Mixins/Hoverable.cpp +++ b/src/JUI/Mixins/Hoverable.cpp @@ -27,8 +27,9 @@ void JUI::Hoverable::OnExitDescendants(const Vector2& mouse) { // TODO: Implement } -void JUI::Hoverable::Update(const Vector2 &m_pos, float delta) { +void JUI::Hoverable::Update(bool mouse_inside, float delta) { + hovered = mouse_inside; /*if (tooltip != nullptr) { if (IsHovered()) { @@ -41,12 +42,12 @@ void JUI::Hoverable::Update(const Vector2 &m_pos, float delta) { if (IsHovered() && !hover_debounce) { - OnHover(m_pos); + OnHover({0,0}); hover_debounce = true; } if (!IsHovered() && hover_debounce) { - OnExit(m_pos); + OnExit({0,0}); hover_debounce = false; } } diff --git a/src/JUI/Widgets/Button.cpp b/src/JUI/Widgets/Button.cpp index dff6532..6be999f 100644 --- a/src/JUI/Widgets/Button.cpp +++ b/src/JUI/Widgets/Button.cpp @@ -46,9 +46,8 @@ namespace JUI { } void Button::Update(float delta) { - hovered = IsMouseInside(); - Hoverable::Update(last_known_mouse_pos, delta); + Hoverable::Update(IsMouseInside(), delta); // TODO: This is duplicated between here and Window.cpp // Will be abstracted away into Clickable shortly. diff --git a/src/JUI/Widgets/Slider.cpp b/src/JUI/Widgets/Slider.cpp index 7ae98b0..84af5d8 100644 --- a/src/JUI/Widgets/Slider.cpp +++ b/src/JUI/Widgets/Slider.cpp @@ -28,12 +28,8 @@ namespace JUI } void Slider::Update(float delta) { - bool selected = Rect::IsMouseInside(); - - hovered = IsMouseInside(); - - Hoverable::Update(last_known_mouse_pos, delta); + Hoverable::Update(Rect::IsMouseInside(), delta); // TODO: This is duplicated between here and Window.cpp // Will be abstracted away into Clickable shortly. diff --git a/src/JUI/Widgets/Window.cpp b/src/JUI/Widgets/Window.cpp index 89c1bcf..c34fec5 100644 --- a/src/JUI/Widgets/Window.cpp +++ b/src/JUI/Widgets/Window.cpp @@ -215,9 +215,8 @@ namespace JUI { } Widget::Update(delta); - hovered = IsMouseInside(); - Hoverable::Update(last_known_mouse_pos, delta); + Hoverable::Update(IsMouseInside(), delta); // TODO: This is duplicated between here and Window.cpp // Will be abstracted away into Clickable shortly. @@ -226,7 +225,6 @@ namespace JUI { OnClick(last_known_mouse_pos, mbtn); } - if (prev_mb_state && !mb_state) { OnRelease(last_known_mouse_pos, mbtn, IsHovered());