51 Commits

Author SHA1 Message Date
8d90f990f0 Fix ScrollingRect behavior with arrow keys, and add PageUp and PageDown key support. 2025-04-10 13:25:11 -05:00
5ccbb84e55 Add Window::Open, Close, SetOpen, IsOpen, and Toggle 2025-04-10 13:38:16 -04:00
74ae05321b add ObserveMouseInput override 2025-04-08 15:27:25 -04:00
906bde163a Other small fixes. 2025-04-07 00:09:36 -04:00
d0fcf9cce2 Refactor tweening some. 2025-04-07 00:09:17 -04:00
2e0ce74753 Implement debounce for collapsible. 2025-04-07 00:09:05 -04:00
47a7b19648 Continued. 2025-04-05 14:51:23 -04:00
80c28921b1 Refactored ScrollingRect to properly clamp the scrollbar size and position. 2025-04-05 14:51:13 -04:00
6166984daa Add Window::DragTo() 2025-04-05 14:49:28 -04:00
8589431cc7 Moving functions to cpp file 2025-04-05 14:49:06 -04:00
1092ef2b38 Add VerticalListLayout::CurrentContentHeight() and HorizontalListLayout::CurrentContentWidth() 2025-04-05 14:48:27 -04:00
1a11fa824b Implement window focusing. 2025-04-04 14:58:00 -04:00
1bd11c5235 Implement CommandLine Widget 2025-04-04 13:54:29 -04:00
956183bade Writing Logger 2025-04-03 23:36:10 -04:00
86d01099e2 Additions 2025-04-03 13:34:51 -04:00
90272e35f4 Editing demo program to have context menu that opens the console. 2025-04-02 15:26:23 -05:00
e2528f54fb Add BindMenu widget header 2025-04-02 15:25:59 -05:00
fffcc7f12d Add CommandLine window widget. 2025-04-02 15:25:41 -05:00
b3f65b3396 Adding new widgets 2025-04-02 13:19:06 -04:00
4193a2a693 Implementing input consumption refactor. 2025-04-01 18:56:43 -04:00
ee0e92d826 Specify default state of 'Dragging' for Slider. Add Slider::Dragging and Slider::SetDragging 2025-03-19 01:26:25 -04:00
86aa41a3db Added Widget::ObserveMouseWheel virtual method 2025-03-08 16:02:47 -05:00
3486ee03d7 yes 2025-03-02 05:24:51 -05:00
34257eb256 Merge remote-tracking branch 'origin/master' 2025-02-24 14:33:56 -05:00
c4f88c9021 Fix incorrect draw color on TextInputForm rendering the input buffer 2025-02-24 14:33:49 -05:00
052157f484 Windows check 2025-02-19 20:38:04 -06:00
bd02c91c45 Lit & Based 2025-02-19 21:21:18 -05:00
73a461ab56 Add widget counter 2025-02-19 16:54:44 -06:00
efb89dbcee Implement more Style 2025-02-19 16:54:36 -06:00
e607db2ae2 Edits 2025-02-19 16:54:22 -06:00
c7d214c851 Widget::ComputeElementPadding 2025-02-19 16:54:12 -06:00
116c2e2e2f etc 2025-02-18 23:13:50 -05:00
73ef647156 TextInputForm cleanup 2025-02-18 16:21:02 -06:00
bdbfe61b87 TweenAnchorPoint stub 2025-02-18 14:48:39 -05:00
f105053fb2 Edits 2025-02-17 22:35:08 -05:00
f772369686 Refactoring, bug fixes, and implementing styling data. 2025-02-14 15:29:00 -05:00
9cf78742ae Add TextInputForm::ClearTextOnReturn and ::DropFocusOnReturn 2025-02-13 22:41:33 -05:00
3aa9f419a2 Add Scene::GlobalUIScale 2025-02-13 22:41:16 -05:00
c338cc66a4 Update to latest JGL and mcolor 2025-02-11 19:29:51 -05:00
64ee666a83 Update main.cpp demo 2025-02-11 18:35:44 -05:00
13cbaa733c Added ImageButton::HoveredImageColor and other state variants 2025-02-11 18:35:31 -05:00
ad2e3e9a45 Added ImageRect::FitImageToBounds 2025-02-11 18:35:10 -05:00
24d5065f01 Changes to RectBase 2025-02-11 18:34:53 -05:00
bfd898b4f6 Change Window default style, implement Window::TitlebarHeight 2025-02-11 18:33:30 -05:00
2579b587c4 Added ImageBase::Draw overload that takes size of parent object to compute scale for image. 2025-02-11 18:32:49 -05:00
883eabb5e3 Awesome collaborative changes. 2025-02-10 21:07:31 -05:00
24e38b9ec3 Implement Widget tweening and layout order. 2025-02-10 16:42:33 -05:00
1b2fdecc18 Implement sorting of children for ListLayouts. 2025-02-10 16:42:14 -05:00
6e58e30022 Fully implement Tween class 2025-02-10 16:41:56 -05:00
030da85166 Implement UDim::Equals and UDim::Lerp 2025-02-10 16:41:44 -05:00
bd19b29a27 Implement UDim2::Equals and UDim2::Lerp 2025-02-10 16:41:39 -05:00
58 changed files with 2326 additions and 1015 deletions

View File

@@ -38,7 +38,7 @@ CPMAddPackage(
CPMAddPackage(
NAME mcolor
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-5.zip
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-6.2.zip
)
CPMAddPackage(
@@ -58,7 +58,7 @@ CPMAddPackage(
CPMAddPackage(
NAME JGL
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-50.zip
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-52.zip
)
target_include_directories(JUI PUBLIC ${Event_SOURCE_DIR}/include)

View File

@@ -17,10 +17,11 @@
using J3ML::LinearAlgebra::Vector2;
namespace JUI
{
namespace JUI {
/// The ImageBase class is an object that handles managing and rendering a texture reference.
/// This class is used as a mixin on widgets that must render images. i.e. Image class
/// This class is used as a mixin on widgets that must render images. i.e. Image class.
/// This object is complex, stateful, and manages resources. Do not use this as a general-purpose texture data type.
/// @see JGL::Texture and JGL::RenderTarget for more suitable classes.
class ImageBase
{
public:
@@ -46,6 +47,9 @@ namespace JUI
void Scale(const Vector2& newScale);
void Origin(const Vector2& newOrigin);
public:
/// Draws the image at the given position, with the instances' scale and origin.
void Draw(const Vector2& pos);
/// Draws the image at the given pos, manually scaled to fit the given size.
void Draw(const Vector2& pos, const Vector2& size);
protected:
JGL::Texture* texture;

View File

@@ -16,10 +16,10 @@
#include <Color4.hpp>
#include "JGL/types/RenderTarget.h"
#include <JUI/DefaultStyle.hpp>
namespace JUI
{
enum class BorderMode
{
Outline, /// As BorderWidth increases, the border grows outward. The dimensions of the widgets contents do not change.
@@ -37,9 +37,9 @@ namespace JUI
//Event<Vector2> MousePress;
//Event<Vector2, bool> MouseRelease;
public:
void CornerRounding(float radius);
virtual void CornerRounding(float radius);
float CornerRounding() const;
[[nodiscard]] float CornerRounding() const;
// TODO: Implement per-corner rounding in JGL::Outline/FillRect
//void CornerRounding(float tlRadius, float trRadius, float blRadius, float brRadius);
@@ -51,13 +51,13 @@ namespace JUI
void SetClipsDescendants(bool clipping);
void BGColor(const Color4& col);
void BorderColor(const Color4& col);
void SetBorderWidth(float w);
void BorderWidth(float w);
Color4 BGColor() const;
Color4 GetBorderColor() const;
float GetBorderWidth() const;
enum BorderMode BorderMode() const;
[[nodiscard]] Color4 BGColor() const;
[[nodiscard]] Color4 GetBorderColor() const;
[[nodiscard]] float GetBorderWidth() const;
[[nodiscard]] enum BorderMode BorderMode() const;
void BorderMode(const enum BorderMode& mode);
@@ -70,11 +70,11 @@ namespace JUI
protected:
enum BorderMode border_mode = BorderMode::Middle;
bool mouse_press_debounce;
bool mouse_inside_debounce;
float border_width = 1.f;
Color4 bg_color = {128,128,128, 255};
Color4 border_color = {192, 192, 192, 0};
bool mouse_press_debounce{};
bool mouse_inside_debounce{};
float border_width = Style::BorderLineWidth;
Color4 bg_color = Style::BackgroundColor;
Color4 border_color = Style::BorderColor;
bool clips_descendants = false; // Controls if child objects can render outside of their parent's rectangle bounds.
float corner_rounding_radius = 0.f; // Curves the rectangle corners by N degrees.
};

View File

@@ -58,7 +58,7 @@ protected:
protected:
// I don't know why this function even exists, or why it was public. It lets you circumvent
// the whole purpose of storing the state things are in :/ - Redacted.
void Draw(const Vector2& abs_pos, const Vector2& abs_size, const std::string& content, uint size, const Color4& color);
void Draw(const Vector2& abs_pos, const Vector2& abs_size, const std::string& content, unsigned int size, const Color4& color);
/// Renders the aligned text string within a bounding-box specified by abs_pos (top-left corner), and abs_size.
/// @see Widget::Draw(), Text::Draw().

View File

@@ -22,6 +22,7 @@
#include <ReWindow/types/Key.h>
#include <JUI/Tween.hpp>
#include <JUI/JUI.hpp>
#include "JUI/DefaultStyle.hpp"
using namespace JGL;
@@ -33,8 +34,7 @@ namespace JUI {
/// 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
{
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++".
@@ -43,15 +43,16 @@ namespace JUI {
Widget(Widget* parent);
virtual ~Widget() {}
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;
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;
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.
@@ -60,66 +61,9 @@ namespace JUI {
Event<Widget *> ChildRemoved;
/// This event triggers right before this widget gets deallocated.
Event<Widget *> Destroying;
#pragma endregion
public:
void TweenPositionTo(const UDim2& goal)
{
UDim2 start_position = this->Position();
float progress = 0;
Tween t;
std::function<void(float)> updateTillGoalReached = [this, t, start_position, progress, goal] (float elapsed) mutable
{
UDim2 current_pos = this->Position();
// TODO: Implement UDim equality operators (with epsilon)
// TODO: Implement UDim::Lerp
// TODO: Implement UDim2::Lerp
if (current_pos.X.Pixels == goal.X.Pixels && current_pos.Y.Pixels == goal.Y.Pixels && current_pos.X.Scale == goal.X.Scale && current_pos.Y.Scale == goal.Y.Scale)
{
// Reached Goal, Complete this tween.
return;
}
progress += elapsed;
if (progress >= 1.f)
{
progress = 1.f;
t.Completed.Invoke();
return;
}
float modified_progress = EasingFunctions::EaseOutElastic(progress);
UDim2 pos = current_pos;
pos.X.Pixels = Math::Lerp(start_position.X.Pixels, goal.X.Pixels, modified_progress);
pos.Y.Pixels = Math::Lerp(start_position.Y.Pixels, goal.Y.Pixels, modified_progress);
pos.X.Scale = Math::Lerp(start_position.X.Scale, goal.X.Scale, modified_progress);
pos.Y.Scale = Math::Lerp(start_position.Y.Scale, goal.Y.Scale, modified_progress);
Position(pos);
};
t.tick_func = updateTillGoalReached;
tweens.push_back(t);
}
void TweenPositionFromTo();
void TweenSizeTo();
void TweenSizeFromTo();
#pragma region Hierarchy
/// Adds a given widget to this widget's list of children.
/// @return The widget in question.
Widget* Add(Widget* newChild);
@@ -150,6 +94,24 @@ namespace JUI {
/// @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;
@@ -180,21 +142,24 @@ namespace JUI {
/// @see Size(), class UDim2.
void Size(const UDim2&);
/// Sets the parent object of this widget. Positioning and sizing of a widget is relative to it's parent.
void Parent(Widget*);
/// Creates and runs an animation on the Position of this widget.
Tween* TweenPosition(const UDim2& goal, TweenInfo params = {});
/// 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);
/// 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.
/// TODO: Better explain what this allows for.
[[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.
@@ -228,6 +193,7 @@ namespace JUI {
/// 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
@@ -240,6 +206,17 @@ namespace JUI {
/// 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.
@@ -273,6 +250,17 @@ namespace JUI {
/// @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().
@@ -292,14 +280,15 @@ namespace JUI {
/// @see IsVisible().
void Visible(bool enabled);
/// 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;
/// Returns whether or not the mouse is inside this widget's approximate bounding-box.
bool IsMouseInside() 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.
@@ -310,73 +299,85 @@ namespace JUI {
void SetViewportSize(const Vector2& vps);
float ComputeElementPadding(float size, const UDim& padding);
Vector2 ComputeElementPadding(const Vector2& size, const UDim2& padding) const;
public:
// TODO: Consider calling J2D::Begin here.
virtual void PreDraw() {}
// TODO: Consider calling J2D::End here.
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.
virtual void ObserveMouseMovement(const Vector2& latest_known_pos);
/// @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.
virtual void ObserveMouseInput(MouseButton btn, bool pressed);
/// @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 void ObserveKeyInput(Key key, bool pressed);
virtual bool ObserveKeyInput(Key key, bool pressed);
protected:
void DrawChildWidgets();
void UpdateChildWidgets(float delta);
protected:
MouseButton mbtn;
MouseButton mbtn = MouseButton::Left;
bool mb_state = false;
bool prev_mb_state = false;
//int last_known_mouse_button;
//bool last_known_mouse_button_state;
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;
std::vector<Tween*> tweens;
float rotation = 0;
std::string name;
bool selected = false;
UDim pad_left = 0_px;
UDim pad_right = 0_px;
UDim pad_top = 0_px;
UDim pad_bottom = 0_px;
UDim margin_left = 0_px;
UDim margin_right = 0_px;
UDim margin_top = 0_px;
UDim margin_bottom = 0_px;
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;
int layout_order = 0;
Vector2 viewport_size{0,0};
/// 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;
@@ -384,13 +385,8 @@ namespace JUI {
/// 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);
};
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include <Color4.hpp>
#include <Colors.hpp>
#include <JUI/UDim.hpp>
#include <JUI/UDim2.hpp>
namespace JUI {
namespace DefaultStyle {
const Color4 BackgroundColor = {128, 128, 128};
const Color4 BorderColor = {192, 192, 192};
const float BorderLineWidth = 1.f;
const UDim BasePadding = 0_px;
namespace Button {
const Color4 BaseBackgroundColor = Colors::White;
const Color4 BaseBorderColor = Colors::Black;
const Color4 HoverBackgroundColor = Colors::Blues::SkyBlue;
const Color4 HoverBorderColor = Colors::Blues::DarkSlateBlue;
const Color4 PressBackgroundColor = Colors::Blues::DarkSlateBlue;
const Color4 PressBorderColor = Colors::Blues::SkyBlue;
const Color4 DisableBackgroundColor = Colors::Gray;
const Color4 DisableBorderColor = Colors::Gray;
}
namespace Image
{
//const
}
namespace Text {
namespace H1 {
}
namespace H2 {
}
}
namespace InputForm {
const Color4 AutocompleteTextColor = Colors::Black;
}
namespace Collapsible {
const UDim HeaderHeight = 16_px;
}
namespace Checkbox {
const Color4 CheckmarkColor = Colors::Red;
}
namespace Window {
const UDim2 DefaultSize = {200_px, 200_px};
const Vector2 DefaultMinimumSize = {200, 200};
const int TitlebarHeight = 20;
const Color4 OutlineColor = {92, 92, 192};
const Color4 UnfocusedOutlineColor = Colors::Gray;
const Color4 ViewportBackgroundColor = Colors::DarkGray;
const int OutlineWidth = 2;
}
}
namespace ExperimentalStyle {
}
namespace Style = DefaultStyle;
}

View File

@@ -1,10 +1,25 @@
#pragma once
#include <jlog/Logger.hpp>
#include <Event.h>
namespace JUI
{
extern jlog::GenericLogger UILogs;
namespace JUI {
class JUILogger : public jlog::GenericLogger {
public:
Event<std::string, Color4> OnLog;
JUILogger(const std::string& context,
std::ofstream& file,
Color4 contextColor = Colors::White,
Color4 timestampColor = Colors::Purples::Fuchsia,
Color4 locationColor = Colors::Pinks::Pink,
Color4 pointerColor = Colors::Pinks::LightPink,
Color4 messageColor = Colors::Greens::LightGreen);
void Log(const std::string& message, const std::source_location &location = std::source_location::current(), const jlog::Timestamp &ts = jlog::Timestamp()) override;
};
extern JUILogger UILogs;
/// An enumeration for mouse buttons, used by JUI to decouple from external systems.
/// Some boilerplate is required in order to get input mechanisms up and running. See the demo files for reference.

View File

@@ -6,6 +6,9 @@
namespace JUI
{
using EasingFunc = std::function<float(float)>;
using TweenTickFunc = std::function<void(float, float)>;
namespace EasingFunctions
{
@@ -76,39 +79,59 @@ namespace JUI
float EaseInOutBounce(float t);
}
struct TweenInfo {
float time = 1.f;
float delay = 0.f;
int repeats = 0;
bool reverse = false;
EasingFunc easing = &EasingFunctions::EaseInOutLinear;
bool overwritable = true;
};
/// A class that represents an animation-in-action.
class Tween {
public:
Tween(TweenTickFunc tick_func);
Tween(TweenTickFunc tick_func, const TweenInfo& info) {
this->tick_func = tick_func;
this->time = info.time;
this->delay_time = info.delay;
this->repeat_count = info.repeats;
this->reverses = info.reverse;
this->easing_func = info.easing;
this->overwritable = info.overwritable;
}
Event<> Completed;
void Update(float elapsed)
{
void Update(float elapsed);
}
std::function<float(float)> easing_func = &EasingFunctions::EaseInOutLinear;
std::function<void(float, float)> tick_func;
std::function<float(float)> easing_func;
std::function<void(float)> tick_func;
/// Duration of the tween, in seconds.
float time;
float time = 5;
/// Time of delay until the tween begins, in seconds.
float delay_time;
float delay_time = 0;
/// Number of times the tween repeats. -1 indicates indefinite repetition.
int repeat_count;
int repeat_count = 0; // TODO: Implement
/// Whether or not the tween interpolates in reverse once the initial tween completes.
bool reverses;
bool reverses = false; // TODO: Implement
float lifetime = 5;
float progress = 0;
bool alive = true;
bool paused = false;
bool completed = false;
bool overwritable = true;
void Cancel();
void ForceFinish();
void Pause() { paused = true; }
void Resume() { paused = false; }
void Start();
bool Paused() const { return paused; }
bool HasCompleted() const { return completed; }
bool Paused() const;
bool HasCompleted() const;
};
}

View File

@@ -11,6 +11,8 @@
#pragma once
#include "J3ML/J3ML.hpp"
namespace JUI
{
/// A coordinate system data type for user interfaces specifically.
@@ -32,6 +34,10 @@ namespace JUI
UDim operator * (float rhs) const;
UDim operator / (float rhs) const;
bool Equals(const UDim& rhs, float epsilon = 1e-3f);
UDim Lerp(const UDim& goal, float t);
};
namespace UDimLiterals {

View File

@@ -42,6 +42,10 @@ namespace JUI
static UDim2 FromScale(float x, float y);
UDim2 Lerp(const UDim2& goal, float t);
bool Equals(const UDim2& rhs, float epsilon = 1e-3f);
public: // Operators
UDim2 operator +(const UDim2& rhs) const;

View File

@@ -0,0 +1,25 @@
#pragma once
#include <JUI/Widgets/Window.hpp>
namespace JUI
{
struct BindAction
{
std::string id;
std::string display_name;
std::vector<std::string> key_ids;
};
class BindMenu : public Window {
public:
explicit BindMenu();
void AddAction(std::string id, std::string disp_name);
};
}

View File

@@ -108,14 +108,15 @@ namespace JUI
//void SetTooltip(const std::string &content, float delay = 0.2f) override;
protected:
bool disabled = false;
Color4 hover_bg = Colors::Blues::SkyBlue;
Color4 hover_border = Colors::Blues::DarkSlateBlue;
Color4 pressed_bg = Colors::Blues::DarkSlateBlue;
Color4 pressed_border = Colors::Blues::SkyBlue;;
Color4 disabled_bg = Colors::Gray;
Color4 disabled_border = Colors::Gray;
Color4 base_bg = Colors::White;
Color4 base_border = Colors::Black;
Color4 hover_bg = Style::Button::HoverBackgroundColor;
Color4 hover_border = Style::Button::HoverBorderColor;
Color4 pressed_bg = Style::Button::PressBackgroundColor;
Color4 pressed_border = Style::Button::PressBorderColor;
Color4 disabled_bg = Style::Button::DisableBackgroundColor;
Color4 disabled_border = Style::Button::DisableBorderColor;
Color4 base_bg = Style::Button::BaseBackgroundColor;
Color4 base_border = Style::Button::BaseBorderColor;
void UpdateVisualState();
};

View File

@@ -13,92 +13,43 @@
#include <JUI/Base/Widget.hpp>
#include <JUI/Widgets/Rect.hpp>
#include "JUI/Mixins/Clickable.hpp"
#include "JUI/Mixins/Hoverable.hpp"
#include "Button.hpp"
#include "JUI/Base/ImageBase.hpp"
#include <JUI/Mixins/Clickable.hpp>
#include <JUI/Mixins/Hoverable.hpp>
#include <JUI/Widgets/Button.hpp>
#include <JUI/Base/ImageBase.hpp>
#include <JUI/Widgets/ImageButton.hpp>
namespace JUI
{
class Checkbox : public Button {
namespace JUI {
// TODO: Find a nice way to implement a checkmark.
class CheckboxBase {
public:
Checkbox() : Button() { }
explicit Checkbox(Widget *parent) : Checkbox()
{
this->Parent(parent);
}
void Update(float delta) override
{
Button::Update(delta);
}
void OnRelease(const J3ML::LinearAlgebra::Vector2 &mouse_pos, const JUI::MouseButton &bnt, bool still_hovering) override
{
Button::OnRelease(mouse_pos, bnt, still_hovering);
checked = !checked;
}
void InnerDraw() override
{
Rect::InnerDraw();
if (checked)
{
//J2D::Begin();
Vector2 check_padding = {2, 2};
RectBase::Draw(check_color, check_color, GetAbsolutePosition()+check_padding, GetAbsoluteSize()-(check_padding*2));
//J2D::End();
}
}
void Draw() override
{
if (!visible)
return;
//J2D::Begin();
Vector2 abs_pos = GetAbsolutePosition();
Vector2 abs_size = GetAbsoluteSize();
auto root_size = GetFamilyTreeRoot()->GetAbsoluteSize();
GLint *old_scissor_bounds;
bool clip_was_enabled;
if (clips_descendants) {
clip_was_enabled = glIsEnabled(GL_SCISSOR_TEST);
if (clip_was_enabled)
glGetIntegerv(GL_SCISSOR_BOX, old_scissor_bounds);
float presumed_screen_height = 600;
glScissor(abs_pos.x, presumed_screen_height-abs_size.y-abs_pos.y, abs_size.x, abs_size.y);
glEnable(GL_SCISSOR_TEST);
}
RectBase::Draw(abs_pos, abs_size);
// Draw Child Elements with scissor clipping still active
Widget::Draw();
// Returns clip to previous state
if (clips_descendants)
{
//glScissor(old_scissor_bounds[0], old_scissor_bounds[1], old_scissor_bounds[2], old_scissor_bounds[3]);
if (!clip_was_enabled) {}
glDisable(GL_SCISSOR_TEST);
}
//J2D::End();
}
[[nodiscard]] Color4 CheckedColor() const { return check_color; }
void CheckedColor(const Color4& color) { check_color = color; }
bool IsChecked() const { return checked;}
void SetChecked(bool value) { checked = value; }
Tween* TweenCheckedColor(const Color4& goal, TweenInfo info = {});
protected:
bool checked = false;
Color4 check_color = Style::Checkbox::CheckmarkColor;
};
class Checkbox : public Button, public CheckboxBase {
public:
Checkbox();
explicit Checkbox(Widget *parent);
void Update(float delta) override;
void OnRelease(const J3ML::LinearAlgebra::Vector2 &mouse_pos, const JUI::MouseButton &bnt, bool still_hovering) override;
void InnerDraw() override;
protected:
bool checked;
Color4 check_color = Colors::Red;
private:
};
class ImageCheckbox : public ImageButton, public CheckboxBase {
public:
protected:
private:
};
}

View File

@@ -0,0 +1,10 @@
#pragma once
namespace JUI
{
/// A composite class which combines a Checkbox and TextLabel into one element.
class CheckboxLabel : public Rect
{
};
}

View File

@@ -1,5 +1,45 @@
/// 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) 2025 Redacted Software
/// This work is dedicated to the public domain.
/// @file Collapsible.cpp
/// @desc A rectangular widget with a clickable header that enables hiding and showing the contents.
/// @edit 2025-02-14
#pragma once
namespace JUI
{
#include <JUI/Widgets/Rect.hpp>
#include <JUI/Widgets/TextButton.hpp>
namespace JUI {
class Collapsible : public Rect {
public:
Collapsible();
explicit Collapsible(Widget* parent);
TextButton* Header();
Rect* ContentBox();
void Collapse();
void Expand();
[[nodiscard]] bool Collapsed() const;
UDim HeaderHeight() const;
void HeaderHeight(const UDim& value);
void Title(const std::string& value);
std::string Title() const;
protected:
TextButton* header = nullptr;
Rect* content_box = nullptr;
TextRect* lil_arrow = nullptr;
bool collapsed = false;
UDim2 saved_size;
UDim header_height = Style::Collapsible::HeaderHeight;
Tween* resize_anim = nullptr;
};
}

View File

@@ -0,0 +1,78 @@
#pragma once
#include <JUI/Widgets/Window.hpp>
#include <JUI/Widgets/TextInputForm.hpp>
#include <JUI/Widgets/ScrollingRect.hpp>
#include <JUI/Widgets/ListLayout.hpp>
namespace JUI {
// TODO: Implement "Rich Text" message support.
// TODO: Implement command autocomplete helper API.
// TODO: Implement API for user-projects to bind commands.
// TODO: Implement API for user-projects to bind loggers.
// TODO: Implement message history.
// TODO: As of now, the API user is in charge of parsing and executing commands.
// TODO: A fairly-generic CommandInterpreter which suits our purposes.
// TODO: <command> <arg1> <arg2> <arg3>
// TODO: Tight JSON integration for specifying complex metadata in commands.
/// A generic "Game Console", which provides a log history, and input box for user-defined commands.
class CommandLine : public Window {
public:
/// This event is invoked when the user has sent a message to the command line.
Event<std::string> OnInput;
/// This event is invoked when the CommandLine window is opened.
Event<> OnOpen;
/// This event is invoked when the CommandLine window is closed.
Event<> OnClose;
/// The default constructor initializes the layout and events of child elements.
/// This constructor is a delegate called by the explicit constructor with parent parameter.
CommandLine();
/// Constructs a command line by specifying the widget it should be parented to.
/// @param parent
explicit CommandLine(Widget* parent);
/// Adds a message to the CommandLine message history.
/// @param message
/// @param color
void Log(const std::string& message, const Color4& color = Colors::White);
/// @return True if the window widget is currently open and visible. Input will be ignored if not.
[[nodiscard]] bool Opened() const;
/// @return True if the window widget is currently closed and invisible. Input will be ignored if so.
[[nodiscard]] bool Closed() const;
/// Opens the window widget.
void Open(bool openVal = true);
/// Closes the window widget.
void Close(bool openVal = false);
/// Sets the "open"-ness of the window widget. The widget will be invisible and ignore input if it is not open.
void SetOpen(bool openVal);
/// Passes input events down the widget hierarchy.
/// @return True if this widget will "consume" the input.
bool ObserveKeyInput(Key key, bool pressed) override;
/// Invoked when a message is sent to the command line.
virtual void OnInputBoxSend(const std::string& message);
protected:
TextInputForm* input_box;
ScrollingRect* message_log_box;
VerticalListLayout* message_log_list;
protected:
int index = 0;
int input_form_height = 20;
int message_height = 16;
bool open = false;
std::vector<std::string> msg_history;
private:
};
}

View File

@@ -22,7 +22,7 @@ namespace JUI
{
public:
GridLayout() : LayoutContainer() {
Name("GridLayout");
}
explicit GridLayout(Widget* parent) : GridLayout()
{

View File

@@ -1,3 +1,5 @@
#pragma once
#include <JUI/Base/ImageBase.hpp>
#include <JUI/Widgets/Button.hpp>
@@ -7,11 +9,29 @@ namespace JUI {
class JUI::ImageButton : public ImageBase, public Button {
public:
ImageButton();
explicit ImageButton(Widget* parent);
~ImageButton() override = default;
void Update(float delta) override;
void Draw() override;
public:
ImageButton() : ImageBase(), Button() {}
explicit ImageButton(Widget* parent) : ImageButton() { Parent(parent); }
public:
~ImageButton() override = default;
[[nodiscard]] Color4 HoveredImageColor() const;
[[nodiscard]] Color4 BaseImageColor() const;
[[nodiscard]] Color4 PressedImageColor() const;
[[nodiscard]] Color4 DisabledImageColor() const;
void HoveredImageColor(const Color4& value);
void BaseImageColor(const Color4& value);
void PressedImageColor(const Color4& value);
void DisabledImageColor(const Color4& value);
void ImageColors(const Color4& hover, const Color4& base, const Color4& press, const Color4& disabled);
protected:
void UpdateImageVisualState();
protected:
Color4 hover_img_color = Colors::White;
Color4 base_img_color = Colors::White;
Color4 pressed_img_color = Colors::White;
Color4 disabled_img_color = Colors::White;
};

View File

@@ -9,17 +9,23 @@
/// @desc A widget that contains and renders an image provided by JGL, within a rectangular frame.
/// @edit 2024-08-05
#pragma once
#include <JUI/Widgets/Rect.hpp>
#include <JUI/Base/ImageBase.hpp>
namespace JUI
{
class ImageRect: public Rect, public ImageBase
{
namespace JUI {
class ImageRect: public Rect, public ImageBase {
public:
ImageRect();
explicit ImageRect(Widget* parent);
void Update(float delta) override;
void Draw() override;
bool FitImageToBounds() const { return fit_image_to_bounds;}
void FitImageToBounds(bool value) { fit_image_to_bounds = value;}
protected:
bool fit_image_to_bounds;
};
}

View File

@@ -31,30 +31,35 @@ namespace JUI
/// Lays child elements out in a vertical list.
/// Child element positions are overridden by this widget.
class VerticalListLayout : public ListLayout
{
class VerticalListLayout : public ListLayout {
public:
VerticalListLayout();
explicit VerticalListLayout(Widget* parent);
/// Computes the order and positioning of all child elements to display them in an ordered list.
void ApplyLayout() override;
void LayoutOrder(LayoutOrder::V order);
[[nodiscard]] LayoutOrder::V LayoutOrder() const;
/// @return the most-recently-computed sum of the heights of all child elements, with padding also taken into account.
[[nodiscard]] float CurrentContentHeight() const;
protected:
LayoutOrder::V layout = LayoutOrder::V::TOP;
float content_height = 0;
};
/// Lays child elements out in a horizontal list.
/// Child element positions are overridden by this widget.
class HorizontalListLayout : public ListLayout
{
class HorizontalListLayout : public ListLayout {
public:
HorizontalListLayout();
explicit HorizontalListLayout(Widget* parent);
void ApplyLayout() override;
void LayoutOrder(LayoutOrder::H order);
[[nodiscard]] LayoutOrder::H LayoutOrder() const;
[[nodiscard]] float CurrentContentWidth() const;
protected:
LayoutOrder::H layout = LayoutOrder::H::LEFT;
float content_width = 0;
};
}

View File

@@ -31,11 +31,11 @@
& &amp;
*/
namespace RichTextFormat
{
// TODO: Simpler "One-liner" Rich text, which is a horizontal sequence of textlabels automatically generated from a format.
class RichTextToken
{
namespace RichTextFormat {
class RichTextToken {
public:
std::string font_face_name;
std::string content;

View File

@@ -23,12 +23,18 @@ namespace JUI
Event<Vector2> MouseMoved;
void GlobalUIScale(const Vector2& value);
[[nodiscard]] Vector2 GlobalUIScale() const;
void Update(float delta) override;
[[nodiscard]] Vector2 GetAbsolutePosition() const override;
[[nodiscard]] Vector2 GetAbsoluteSize() const override;
bool ObserveMouseMovement(const Vector2 &latest_known_pos) override;
protected:
Vector2 ui_scale = Vector2(1, 1);
};
}

View File

@@ -15,24 +15,22 @@ namespace JUI {
class ScrollingRect;
}
// TODO: Fix direction of ScrollBar itself
// TODO: MouseWheel scroll, and ScrollBar clickable
// TODO: Clamp range of ScrollBar
/// A Rectangle Widget which has a larger renderable area than the visible area.
/// This allows user-controlled scrolling of the viewable content.
class JUI::ScrollingRect : public Rect {
protected:
bool vertical_scrollbar_enabled = true;
bool horizontal_scrollbar_enabled = true;
bool scrollbar_visible = true;
float scrollbar_width = 12;
Color4 scrollbar_color = Colors::Whites::Azure;
float scroll = 0;
JGL::RenderTarget* canvas = nullptr;
protected:
/* This isn't public because nothing should ever
* have to do this from the outside. -Redacted */
void RecomputeRenderTarget();
public:
float scroll_size = 0;
~ScrollingRect() override;
/// The default constructor initializes all child elements and members to reasonable defaults.
ScrollingRect();
/// Constructs a ScrollingRect by specifying the parent widget to bind it to.
explicit ScrollingRect(Widget* parent);
public:
JGL::RenderTarget* GetCanvas();
[[nodiscard]] Vector2i CanvasSize() const;
[[nodiscard]] float ScrollPos() const { return scroll; }
@@ -43,30 +41,60 @@ public:
public:
void ScrollPos(float pos) { scroll = pos; }
void CanvasSize(const Vector2i& new_size);
/// Sets the scroll amount of this ScrollingRect.
/// The value will be clamped between ScrollMinimum() and ScrollMaximum().
/// @param value The new scrolling amount to apply.
/// @see ScrollMinimum(), ScrollMaximum().
void SetScrollAmount(float value);
/// Scrolls this ScrollingRect by the specified amount.
/// @param amount The quantity of pixels by which to scroll the object.
/// @see SetScrollAmount()
float Scroll(float amount);
/// @return The amount of pixels this scrolling rect is currently scrolled by.
float GetScrollAmount() const;
[[nodiscard]] bool CanScroll() const;
/// The lower-bound of how far up the scroll can be.
[[nodiscard]] float ScrollMinimum() const;
/// The upper-bound of how far down the scroll can go.
[[nodiscard]] float ScrollMaximum() const;
void SetAutoAdjustScrollAmount(bool value);
bool GetAutoAdjustScrollAmount() const;
void ScrollToTop();
void ScrollToBottom();
// TODO: Figure out how to automatically adjust this as child widgets are added, removed, and changed in size.
float canvas_height = 0;
public:
void InnerDraw() override;
void Draw() override;
void Update(float delta) override
{
//scroll += delta*5;
//canvas->Resize(Vector2i(GetAbsoluteSize().x, GetAbsoluteSize().y));
Rect::Update(delta);
}
void Update(float delta) override;
void ObserveKeyInput(Key key, bool pressed) override
{
if (key == Keys::UpArrow && pressed)
{
scroll -= 10;
}
if (key == Keys::DownArrow && pressed)
{
scroll += 10;
}
bool ObserveKeyInput(Key key, bool pressed) override;
bool ObserveMouseWheel(int mwheel) override;
protected:
bool vertical_scrollbar_enabled = true;
bool horizontal_scrollbar_enabled = true;
bool scrollbar_visible = true;
float scrollbar_width = 12;
Color4 scrollbar_color = Colors::Whites::Azure;
float scroll = 0;
float scroll_amount = 10;
JGL::RenderTarget* canvas = nullptr;
bool auto_adjust_scroll_amount = false;
}
public:
~ScrollingRect() override;
protected:
/* This isn't public because nothing should ever
* have to do this from the outside. -Redacted */
void RecomputeRenderTarget();
ScrollingRect();
explicit ScrollingRect(Widget* parent);;
void DrawVerticalScrollbar();
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include <JUI/Base/Widget.hpp>
namespace JUI {
enum class Orientation { HORIZONTAL, VERTICAL };
/// Fills space, and renders a single line through it, based on the given orientation.
class Separator : public Widget {
public:
Separator() : Widget()
{
Name("Separator");
}
explicit Separator(Widget* parent)
{
}
protected:
private:
};
}

View File

@@ -31,7 +31,7 @@ namespace JUI
public:
Event<float> ValueChanged;
Slider() = default;
Slider();
explicit Slider(JUI::Widget* parent);
[[nodiscard]] float Minimum() const;
@@ -40,7 +40,7 @@ namespace JUI
[[nodiscard]] float CurrentValue() const;
[[nodiscard]] Color4 ScrubberColor() const;
[[nodiscard]] float ScrubberWidth() const;
[[nodiscard]] bool Dragging() const;
[[nodiscard]] float Range() const;
void Minimum(float min);
@@ -49,6 +49,8 @@ namespace JUI
void CurrentValue(float value);
void ScrubberColor(const Color4& color);
void ScrubberWidth(float width);
void SetDragging(bool value);
void OnClick(const J3ML::LinearAlgebra::Vector2 &MousePos, const JUI::MouseButton &MouseButton) override;
@@ -63,7 +65,7 @@ namespace JUI
float maximum = 1;
float interval = 0.1;
float current;
bool dragging;
bool dragging = false;
float scrubber_width = 20;
Color4 scrubber_color = Colors::White;
private:

View File

@@ -11,14 +11,21 @@
#pragma once
#include <set>
#include <JUI/Mixins/Clickable.hpp>
#include "TextRect.hpp"
namespace JUI
{
class TextInputForm : public TextRect, public Clickable
{
namespace JUI {
// TODO: Text Selection
// TODO: Support Copy
// TODO: Support Paste
// TODO: Support Cut
// TODO: Support insert at cursor
// TODO: Simulate key repeat.
class TextInputForm : public TextRect, public Clickable {
public:
Event<> OnSelect;
Event<> OnDeselect;
@@ -28,9 +35,37 @@ namespace JUI
void Update(float elapsed) override;
void InnerDraw() override;
void Draw() override;
void ObserveKeyInput(Key key, bool pressed) override;
void ObserveMouseInput(MouseButton btn, bool pressed) override;
void ObserveMouseMovement(const Vector2 &latest_known_pos) override;
void MoveCursorLeft();
void MoveCursorRight();
/// Returns the maximum position of the cursor, which is determined by the input buffer's length.
[[nodiscard]] unsigned int CursorMaxPosition() const;
void SetCursorPosition(unsigned int pos);
/// Fires the input event, clears the input buffer, and sets the state to be ready for further input.
void SendInput(bool clear_input);
/// Copy the contents of the input form, or the selection if active, into the the system's clipboard.
void Copy();
/// Paste the contents of the system's clipboard
void Paste();
void Cut();
void Backspace();
void Delete();
void PushStringToCurrentPlaceInInputBuffer(const std::string &snippet);
void PushKeyToCurrentPlaceInInputBuffer(const Key &key);
bool ObserveKeyInput(Key key, bool pressed) override;
bool ObserveMouseInput(MouseButton btn, bool pressed) override;
bool ObserveMouseMovement(const Vector2 &latest_known_pos) override;
[[nodiscard]] std::string GetAutocompleteText() const;
void SetAutoCompleteText(const std::string& text);
[[nodiscard]] Color4 GetAutocompleteTextColor() const;
@@ -40,33 +75,50 @@ namespace JUI
[[nodiscard]] bool AutocompleteTextEnabled() const;
void SetAutocompleteTextEnabled(bool enabled);
[[nodiscard]] std::string InputBuffer() const;
void SetInputBuffer(const std::string& value);
void ClearInputBuffer();
[[nodiscard]] bool HasFocus() const;
void SetFocused(bool focused);
// TODO: Implement procedure to allow API consumer to validate the input, **before** clearing the buffer.
[[nodiscard]] bool ClearTextOnReturn() const;
void ClearTextOnReturn(bool value);
[[nodiscard]] bool DropFocusOnReturn() const;
void DropFocusOnReturn(bool value);
void GrabFocus();
void DropFocus();
[[nodiscard]] std::set<std::string> GetBlacklist() const;
void SetBlacklist(const std::set<std::string>& value);
void AddToBlacklist(const std::string& value);
// TODO: Implement selection of part of input text.
Color4 GetSelectionColor() const;
void SetSelectionColor(const Color4& color);
bool HasSelection() const;
std::string GetSelectedText() const;
bool HasFocus() const;
void SetFocused(bool focused);
void GrabFocus();
void DropFocus();
std::vector<char> GetBlacklist() const { return blacklist;}
protected:
bool clear_text_on_return;
bool clear_text_on_return = true;
bool drop_focus_on_return = false;
bool focused = false;
bool selection_enabled;
bool selection_active;
int selection_start_index;
int selection_end_index;
int cursor_position = 0;
unsigned int cursor_position = 0;
std::string input_buffer;
float cursor_blink_time = 0.f;
Color4 autocomplete_color = Colors::Black;
Color4 autocomplete_color = Style::InputForm::AutocompleteTextColor;
std::string autocomplete_text = "Hello World";
bool hide_autocomplete_on_select = true;
bool autocomplete_text_enabled = true;
std::vector<char> blacklist;
std::set<std::string> blacklist;
private:
};
}

View File

@@ -9,9 +9,8 @@ namespace JUI
class Tooltip : public TextRect
{
public:
Tooltip() : TextRect()
{
Tooltip() : TextRect() {
Name("Tooltip");
}
explicit Tooltip(Widget* parent) : Tooltip()
{

View File

@@ -6,6 +6,7 @@ namespace JUI
class ContextMenu : public Rect {
public:
ContextMenu() : Rect() {
Name("ContextMenu");
this->BGColor(Colors::White);
this->Margin(2_px);
this->Size({200, 200, 0, 0});
@@ -59,7 +60,7 @@ namespace JUI
this->Position({0,0,0,0});
this->BGColor(Colors::White);
this->BorderColor(Colors::Blues::CornflowerBlue);
this->SetBorderWidth(2);
this->BorderWidth(2);
this->Margin(2_px);
this->BorderMode(BorderMode::Outline);
layout = new HorizontalListLayout(this);
@@ -90,7 +91,7 @@ namespace JUI
btn->SetTextSize(14);
btn->SetTextColor(Colors::Black);
btn->Size({static_cast<int>(str_width.x)+16, 0, 0, 1});
btn->SetBorderWidth(0.f);
btn->BorderWidth(0.f);
btn->SetContent(name);
return btn;
}

View File

@@ -25,22 +25,19 @@
#include <JUI/Widgets/TextButton.hpp>
#include <JUI/Widgets/ImageButton.hpp>
namespace JUI
{
namespace JUI {
using J3ML::LinearAlgebra::Vector2;
class DockingStation {};
/// A container widget class, with title bar and buttons,
/// which can be dragged around, resized, and docked into other applicable widgets.
class Window : public Widget, public RectBase, public Clickable, public Hoverable, public Draggable, public Resizable, public Dockable
{
class Window : public Widget, public RectBase, public Clickable, public Hoverable, public Draggable, public Resizable, public Dockable {
public:
/// The default constructor sets a default style for this Window.
Window();
/// Construct a window widget by specifying it's parent.
Window(Widget* parent);
explicit Window(Widget* parent);
/// Returns the current size (in x,y pixels) of the Window widget.
[[nodiscard]] Vector2 CurrentSize() const;
@@ -54,10 +51,24 @@ namespace JUI
/// Sets the maximum size (in x,y pixels) that the Window widget is allowed to be.
void MaxSize(const Vector2& constraint);
/// Returns the height (in pixels) of the Window's titlebar.
// TODO: Decide if this will auto-scale with the titlebar's text height, or the other way around.
int TitlebarHeight() const;
[[nodiscard]] int TitlebarHeight() const;
/// @return True if this window widget is on-top of all other window-widgets with the same parent.
bool OnTop() const;
/// Brings this window widget to the top of the window-widget stack.
void BringToTop();
/// Moves this window up in the window-widget stack.
void MoveUp() const;
/// Moves this window down in the window-widget stack.
void MoveDown() const;
void TitlebarHeight(int height);
/// Returns the text displayed as the Window's title.
[[nodiscard]] std::string Title() const;
@@ -66,7 +77,7 @@ namespace JUI
/// @see class Draggable.
bool IsDraggable() const;
/// Sets the window to have the ability to be dragged around by the mouse.
void SetDraggable(bool value);
/// Returns whether this Window is able to be 'Docked' into another widget.
@@ -87,6 +98,8 @@ namespace JUI
void SetResizable(bool value);
void CornerRounding(float radius) override;
/// Returns a pointer to the Text Widget that is used to render the title bar's text.
Text* TitleInstance();
/// Returns a pointer to the Rect Widget that is used to layout the title bar contents.
@@ -100,17 +113,11 @@ namespace JUI
Vector2 AbsoluteViewportSize() const;
AABB2D AbsoluteViewportBounds() const;
/// Sets the font used by the title-bar text on this Window.
void SetTitleFont(const Font& f);
/// Toggles whether this window is actively being dragged by the mouse.
/// @see class Draggable.
void SetDrag(bool d) override;
/// @see class Widget.
void Update(float delta) override;
/// @see class Widget.
@@ -119,21 +126,64 @@ namespace JUI
void OnClick(const Vector2& m_pos, const MouseButton& m_btn) override;
/// @see class Clickable.
void OnRelease(const Vector2& m_pos, const MouseButton& m_btn, bool still_hovering) override;
/// @see class Hoverable.
void OnHover(const J3ML::LinearAlgebra::Vector2 &MousePos) override
{
Hoverable::OnHover(MousePos);
zindex++;
focused = true;
this->BorderColor(Style::Window::OutlineColor);
this->BGColor(Style::Window::OutlineColor);
}
/// @see class Hoverable.
void OnExit(const J3ML::LinearAlgebra::Vector2 &MousePos) override {
Hoverable::OnExit(MousePos);
zindex--;
focused = false;
this->BorderColor(Style::Window::UnfocusedOutlineColor);
this->BGColor(Style::Window::UnfocusedOutlineColor);
}
bool ObserveMouseInput(JUI::MouseButton btn, bool pressed) override
{
// TODO: Consider how this plays with Clickable::OnClick and Clickable::OnRelease
if (this->visible && this->focused)
return Widget::ObserveMouseInput(btn, pressed);
return false;
}
[[nodiscard]] bool IsOpen() const;
void Open();
void Close();
void SetOpen(bool value);
void Toggle();
protected:
JUI::Rect* Topbar;
JUI::Rect* Viewport;
JUI::Text* TitleLabel;
JUI::ImageButton* exit_btn;
JUI::ImageButton* fs_btn;
void UpdateInternalWidgetsTitlebarHeight();
protected:
JUI::Rect* Topbar = nullptr;
JUI::Rect* Viewport = nullptr;
JUI::Text* TitleLabel = nullptr;
JUI::ImageButton* exit_btn = nullptr;
JUI::ImageButton* fs_btn = nullptr;
std::string title = "JUI Window";
bool resizable = true;
bool focused = false;
bool open = false;
//bool resizing = false;
bool draggable = true;
bool dockable = false;
int titlebar_height = 20;
int titlebar_height = Style::Window::TitlebarHeight;
int title_font_size = 16;
Vector2 max_size;
Vector2 min_size; //= {30, 30};
Vector2 max_size = {800, 600};
Vector2 min_size = {200, 100}; //= {30, 30};
UDim2 size_when_restart_began;
void DragTo(const Vector2 &pos);
};
}

203
main.cpp
View File

@@ -27,24 +27,46 @@
#include <ReWindow/types/Window.h>
#include <ReWindow/Logger.h>
#include "JUI/Widgets/NineSlice.hpp"
#include <JUI/Widgets/Collapsible.hpp>
#include <JUI/Widgets/CommandLine.hpp>
JUI::Scene* scene;
JGL::Texture* sample_texture;
JGL::Texture* slicer;
JUI::VerticalListLayout* list;
JUI::ScrollingRect* scroller;
JUI::TextRect* widget_count;
JUI::CommandLine* console;
int count_descendants(JUI::Widget* w) {
return w->GetDescendants().size();
}
JUI::Scene* CreateScene() {
using namespace JUI;
Scene *root = new Scene();
auto *root = new Scene();
/*root->DescendantAdded += [&] (auto* node) {
widget_count->SetContent("Widgets: " + count_descendants(root));
};
root->DescendantRemoved += [&] (auto* node) {
widget_count->SetContent("Widgets: " + count_descendants(root));
};*/
auto* nineslice_demo_window = new JUI::Window(root);
nineslice_demo_window->CornerRounding(5);
nineslice_demo_window->Name("NineSlice Demo Window");
nineslice_demo_window->CornerRounding(10);
nineslice_demo_window->Size({50_percent, 50_percent});
nineslice_demo_window->SetTitle("9-Slice Demo");
nineslice_demo_window->Visible(false);
nineslice_demo_window->TitlebarHeight(12);
console = new JUI::CommandLine(root);
console->Close();
JUI::UILogs.OnLog += [&] (const std::string& msg, Color4 c){ console->Log(msg, c);};
auto* topbar = new UtilityBar(root);
topbar->ZIndex(3);
@@ -66,9 +88,15 @@ JUI::Scene* CreateScene() {
auto* open_scroll = ctx_menu->AddItem("Scroll Widget Demo");
open_scroll->OnClickEvent += [&] (Vector2 pos, JUI::MouseButton btn) {};
ctx_menu->AddItem("");
ctx_menu->ZIndex(3);
//ctx_menu->AddItem("");
auto* open_console = ctx_menu->AddItem("Command Line");
open_console->OnClickEvent += [&] (Vector2 pos, JUI::MouseButton btn)mutable {
console->Open();
};
ctx_menu->ZIndex(3);
};
topbar->AddButton("Edit");
@@ -76,76 +104,140 @@ JUI::Scene* CreateScene() {
topbar->AddButton("Help");
widget_count = new JUI::TextRect(topbar);
widget_count->Size(UDim2(100,20,0,0));
widget_count->AnchorPoint({1, 0});
widget_count->Position({100_percent, 0_percent});
widget_count->BGColor(Colors::Transparent);
widget_count->SetTextColor(Colors::Black);
widget_count->BorderColor(Colors::Transparent);
widget_count->BorderWidth(0);
widget_count->Center();
widget_count->AlignRight();
//auto* horizontal = new HorizontalListLayout(root);
//horizontal->ZIndex(1);
auto* sizer_1 = new Rect(root);
//sizer_1->ZIndex(4);
sizer_1->Size({0,-24,0.2f, 1.f});
sizer_1->Position({0, 24, 0, 0});
sizer_1->BGColor(Colors::Grays::Gainsboro);
auto* column_rect = new Rect(root);
//column_rect->ZIndex(4);
column_rect->Size({0, -24, 0.2f, 1.f});
column_rect->Position({0, 24, 0, 0});
column_rect->BGColor(Colors::Grays::Gainsboro);
auto* s1_vert = new VerticalListLayout(sizer_1);
auto* column_layout = new VerticalListLayout(column_rect);
auto* button = new TextButton(s1_vert);
auto* btn_container1 = new Rect(column_layout);
btn_container1->Size({100_percent, 24_px});
auto* btn_h_layout1 = new HorizontalListLayout(btn_container1);
btn_h_layout1->Padding({2_px});
auto* button = new TextButton(btn_h_layout1);
//button->Position({5, 105, 0, 0});
button->Size({0, 35, 1, 0});
button->Size({0, 20, 0.32f, 0});
button->SetTextColor(Colors::Black);
button->SetContent("Button");
button->AlignLeft();
button->Padding(5_px);
//button->Padding(5_px);
auto* tt2 = new JUI::Tooltip(button);
tt2->SetContent("Test 123");
auto* button2 = new TextButton(s1_vert);
auto* button2 = new TextButton(btn_h_layout1);
//button2->Position({5, 105, 0, 0});
button2->Size({0, 35, 1, 0});
button2->Size({0, 20, 0.32f, 0});
button2->SetTextColor(Colors::Black);
button2->SetContent("Button");
button2->AlignCenterHorizontally();
auto* button3 = new TextButton(s1_vert);
auto* button3 = new TextButton(btn_h_layout1);
//button2->Position({5, 105, 0, 0});
button3->Size({0, 35, 1, 0});
button3->Size({0, 20, 0.32f, 0});
button3->SetTextColor(Colors::Black);
button3->SetContent("Button");
button3->AlignRight();
auto* checkbox_container = new Rect(s1_vert);
checkbox_container->Size({0, 35, 1, 0});
auto* btn_container2 = new Rect(column_layout);
btn_container2->Size({100_percent, 24_px});
btn_container2->BGColor(Colors::DarkGray);
auto* btn_h_layout2 = new HorizontalListLayout(btn_container2);
btn_h_layout2->Padding({2_px});
auto* button4 = new TextButton(btn_h_layout2);
//button->Position({5, 105, 0, 0});
button4->Size({0, 20, 0.32f, 0});
button4->SetTextColor(Colors::Black);
button4->SetContent("Button");
button4->AlignLeft();
//button4->CornerRounding(4);
auto* button5 = new TextButton(btn_h_layout2);
//button2->Position({5, 105, 0, 0});
button5->Size({0, 20, 0.32f, 0});
button5->SetTextColor(Colors::Black);
button5->SetContent("Button");
button5->AlignCenterHorizontally();
//button5->CornerRounding(4);
auto* button6 = new TextButton(btn_h_layout2);
//button2->Position({5, 105, 0, 0});
button6->Size({0, 20, 0.32f, 0});
button6->SetTextColor(Colors::Black);
button6->SetContent("Button");
button6->AlignRight();
//button6->CornerRounding(4);
auto* checkbox_container = new Rect(column_layout);
checkbox_container->Size({0, 24, 1, 0});
auto* checkbox_horiz = new HorizontalListLayout(checkbox_container);
checkbox_horiz->Padding(2_px);
auto* label = new TextRect(checkbox_horiz);
label->SetContent("Checkboxes");
label->BorderWidth(0);
label->AutoFitSizeToText(true);
auto* check1 = new Checkbox(checkbox_horiz);
check1->Size({20_px, 20_px});
auto* check2 = new Checkbox(checkbox_horiz);
check2->Size({20_px, 20_px});
check2->CheckedColor(Colors::Blue);
check2->CornerRounding(7);
auto* check3 = new Checkbox(checkbox_horiz);
check3->Size({20_px, 20_px});
check3->CheckedColor(Colors::Oranges::Coral);
check3->CornerRounding(7);
auto* input_form = new TextInputForm(s1_vert);
input_form->Size({0,30, 1, 0});
auto* input_form = new TextInputForm(column_layout);
input_form->Size({0,24, 1, 0});
input_form->SetContent("");
input_form->SetTextSize(14);
auto* collapsible = new Collapsible(column_layout);
collapsible->Size({100_percent, 200_px});
auto* other_window = new JUI::Window(root);
other_window->Position({10_percent, 10_percent});
other_window->Size({30_percent, 25_percent});
other_window->SetTitle("Another Window");
other_window->TweenPositionTo({50_percent, 50_percent});
Tween* t = other_window->TweenPosition({50_percent, 50_percent}, {.time = 5});
t->Completed += [] () { std::cout << "Tween type test!!" << std::endl; };
scroller = new JUI::ScrollingRect(other_window->ViewportInstance());
scroller->Size({100_percent, 100_percent});
scroller->BGColor(Colors::Reds::LightCoral);
list = new JUI::VerticalListLayout(scroller);
list->LayoutOrder(JUI::LayoutOrder::V::BOTTOM);
//nineslice_demo_window->Padding(1_px);
// End Window //
auto* nineslice = new JUI::NineSliceRect(nineslice_demo_window);
nineslice->Content(slicer);
nineslice->Size({100_percent, 100_percent});
@@ -162,7 +254,6 @@ JUI::Scene* CreateScene() {
nineslice->CenterQuad({{96, 96}, {384-(96*2), 378-(96*2)}});
auto darkie = new JUI::Image(nineslice_demo_window->ViewportInstance(), sample_texture);
darkie->FitImageToParent(true);
darkie->Color({255,255,255,128});
@@ -170,8 +261,6 @@ JUI::Scene* CreateScene() {
auto list = new VerticalListLayout(nineslice_demo_window->ViewportInstance());
list->Padding(10_px);
TextRect* a = new TextRect(list);
a->SetTextSize(16);
a->Size({0, 20, 1, 0});
@@ -182,7 +271,7 @@ JUI::Scene* CreateScene() {
TextRect* b = new TextRect(list);
b->SetTextSize(16);
b->SetContent("You can drag it around via left-click, and resize via right-click.");
b->SetContent("You can drag it around via left-click, and resize via right-click. Isn't that cool??");
//b->FitText(true);
b->Size({0, 20, 1, 0});
b->Center();
@@ -192,6 +281,7 @@ JUI::Scene* CreateScene() {
return root;
}
float scale = 1.f;
float accum = 0;
int iter = 0;
@@ -211,21 +301,25 @@ public:
void Update(float elapsed)
{
widget_count->SetContent(std::format("Widgets: {}", count_descendants(scene)));
using namespace JUI::UDimLiterals;
accum += elapsed;
scene->Update(elapsed);
if (accum > 1.f)
{
if (accum > 1.f) {
iter--;
accum = 0.f;
auto* text = new JUI::TextRect(list);
text->Size({50_percent, 20_px});
text->ZIndex(iter);
text->LayoutOrder(-iter);
text->SetContent(std::format("{} Sampled Delta: {}ms", -iter, Math::Floor(elapsed*1000.f)));
scroller->scroll_size += 20;
scroller->canvas_height += text->GetAbsoluteSize().y;
}
@@ -268,29 +362,47 @@ public:
}
void OnMouseMove(const ReWindow::MouseMoveEvent &) override
void OnMouseMove(const ReWindow::MouseMoveEvent &e) override
{
//JUI::UILogs.Log("Big Stepper");
Vector2 new_mouse_pos = Vector2(e.Position.x, e.Position.y);
if (scene->ObserveMouseMovement(new_mouse_pos))
return;
}
void OnKeyDown(const ReWindow::KeyDownEvent &) override
{
void OnKeyDown(const ReWindow::KeyDownEvent &) override {
}
//bool OnResizeRequest(const ReWindow::WindowResizeRequestEvent &e) override {}
//JUIDevelopmentTestWindow() : ReWindow::OpenGLWindow() {}
void OnMouseWheel(const ReWindow::MouseWheelEvent &w) override {
scale += w.WheelMovement * 0.125f;
if (scene->ObserveMouseWheel(w.WheelMovement))
return;
/// As a demo, change the scene's UI scale.
scene->GlobalUIScale({scale, scale});
}
};
int main()
{
for (float i = 0; i < 1; i += 0.01f)
void inspect_widget(JUI::Widget* w, int depth = 1) {
std::cout << std::setw(depth*4);
std::cout << w->Name() << std::endl;
std::cout << std::setw(0);
depth++;
for (auto* child : w->GetChildren())
{
std::cout << JUI::EasingFunctions::EaseInSine(i) << std::endl;
inspect_widget(child, depth);
}
}
int main() {
using namespace ReWindow;
// TODO: Find out new jlog api for silencing specific loggers.
@@ -310,6 +422,8 @@ int main()
slicer = new JGL::Texture("assets/9slice.png");
scene = CreateScene();
inspect_widget(scene);
window->OnResizeRequestEvent += [&] (ReWindow::WindowResizeRequestEvent e){
Vector2i size = Vector2i(e.Size.x, e.Size.y);//window->getLastKnownResize();
@@ -318,10 +432,7 @@ int main()
JGL::Update(size);
};
window->OnMouseMoveEvent += [&] (MouseMoveEvent e)
{
scene->ObserveMouseMovement(Vector2(e.Position.x, e.Position.y));
};
window->OnMouseMoveEvent += [&] (MouseMoveEvent e) { };
window->OnMouseButtonUpEvent += [&] (MouseButtonUpEvent e) {
/// Invalid operands to binary expression 'MouseButton' and 'const MouseButton'

View File

@@ -1,15 +1,33 @@
#include <JUI/Base/ImageBase.hpp>
#include <JGL/JGL.h>
#include <JUI/JUI.hpp>
using namespace JGL;
namespace JUI
{
void ImageBase::Draw(const Vector2 &pos, const Vector2 &size) {
//J2D::Begin();
void ImageBase::Draw(const Vector2 &pos) {
if (texture == nullptr) {
UILogs("Attempt to draw ImageBase that has nullptr texture!");
return;
}
// TODO: Support image rotation in the widget.
J2D::DrawSprite(*texture, pos, 0, origin, scale, image_color);
//J2D::End();
}
void ImageBase::Draw(const Vector2 &pos, const Vector2 &size) {
if (texture == nullptr) {
UILogs("Attempt to draw ImageBase that has nullptr texture!");
return;
}
Vector2 overridden_scale = size / Vector2(texture->GetDimensions());
// TODO: Support image rotation in the widget.
J2D::DrawSprite(*texture, pos, 0, origin, overridden_scale, image_color);
}
ImageBase::ImageBase()
@@ -39,5 +57,7 @@ namespace JUI
Vector2 ImageBase::Scale() const { return scale;}
Vector2 ImageBase::Origin() const { return origin;}
}

View File

@@ -4,44 +4,36 @@
using namespace JGL;
namespace JUI {
RectBase::RectBase() {}
RectBase::RectBase() = default;
bool RectBase::GetClipsDescendants() const { return clips_descendants;}
void RectBase::SetClipsDescendants(bool clipping) { clips_descendants = clipping;}
void RectBase::SetClipsDescendants(bool clipping) { clips_descendants = clipping; }
void RectBase::BGColor(const Color4 &col) { bg_color = col;}
void RectBase::BGColor(const Color4 &col) { bg_color = col; }
void RectBase::BorderColor(const Color4 &col) { border_color = col;}
void RectBase::BorderColor(const Color4 &col) { border_color = col; }
void RectBase::SetBorderWidth(float w) {border_width = w;}
void RectBase::BorderWidth(float w) { border_width = w; }
float RectBase::GetBorderWidth() const { return border_width;}
float RectBase::GetBorderWidth() const { return border_width; }
Color4 RectBase::GetBorderColor() const { return border_color;}
Color4 RectBase::GetBorderColor() const { return border_color; }
Color4 RectBase::BGColor() const { return bg_color; }
void RectBase::SetBorderStyling(const Color4 &color, float width) {
BorderColor(color);
SetBorderWidth(width);
BorderWidth(width);
}
void RectBase::Draw(const Vector2& abs_pos, const Vector2& abs_size)
{
void RectBase::Draw(const Vector2& abs_pos, const Vector2& abs_size) {
Draw(bg_color, border_color, abs_pos, abs_size);
}
//void RectBase::Draw(const Color4& bgColor, const Color4& borderColor, const Vector2 &abs_pos, const Vector2 &abs_size) {
//Draw(nullptr, bgColor, borderColor, abs_pos, abs_size);
//}
void RectBase::Draw(const Color4& bgColor, const Color4& borderColor, const Vector2 &abs_pos, const Vector2 &abs_size) {
//J2D::Begin(canvas, false);
// Background rect
if (corner_rounding_radius > 0)
J2D::FillRoundedRect(bgColor, abs_pos, abs_size, corner_rounding_radius);
@@ -59,16 +51,13 @@ namespace JUI {
border_offset = {border_width/2.f, border_width/2.f};
// Draw the outline.
if (border_width > 0)
{
if (border_width > 0) {
if (corner_rounding_radius > 0)
J2D::OutlineRoundedRect(borderColor, abs_pos - border_offset, abs_size + (border_offset*2), corner_rounding_radius, border_width);
else
J2D::OutlineRect(borderColor, abs_pos - border_offset, abs_size + (border_offset*2), border_width);
}
//J2D::End();
}
void RectBase::BorderMode(const enum BorderMode &mode) {

View File

@@ -42,11 +42,26 @@ void TextBase::AlignCenterVertically() {
void TextBase::SetWordWrap(bool wrap) { word_wrap = wrap; }
void TextBase::Draw(const Vector2& abs_pos, const Vector2& abs_size, const std::string& content, uint size, const Color4& color) {
void TextBase::Draw(const Vector2& abs_pos, const Vector2& abs_size, const std::string& content, unsigned int size, const Color4& color) {
// Calculate how much to origin the text based on alignment.
float align_x = abs_pos.x;
float align_y = abs_pos.y;
std::string start = content;
std::vector<std::string> lines;
/*for (int i = 0; i < start.length(); i++) {
std::string substr = start.substr(0, i);
auto sub_bounds = this->GetFont().MeasureString(substr, size);
if (sub_bounds.x > abs_size.x)
}*/
auto bounds = this->GetFont().MeasureString(content, size);
// Do nothing if there is no text.
if (bounds.x == 0 || bounds.y == 0)

View File

@@ -2,180 +2,169 @@
#include <jlog/Logger.hpp>
#include <J3ML/Geometry/AABB2D.hpp>
namespace JUI {
using namespace JUI;
Widget::Widget() {
name = "Widget";
children = std::vector<Widget *>();
}
Widget::Widget() {
name = "Widget";
children = std::vector<Widget *>();
}
Widget::Widget(Widget* parent) : Widget() {
this->Parent(parent);
}
Widget::Widget(Widget* parent) : Widget()
{
this->Parent(parent);
}
void Widget::Parent(Widget* newParent) {
// hold a reference to this so it doesn't get collected as we're working.
Widget* oldParent = this->parent;
void Widget::Parent(Widget* newParent) {
// hold a reference to this so it doesn't get collected as we're working.
Widget* oldParent = this->parent;
// New parent is old parent, do nothing, maybe raise a warning.
if (newParent == oldParent)
return;
// New parent is old parent, do nothing, maybe raise a warning.
if (newParent == oldParent)
return;
// Don't allow for an instance to be parented to itself
if (this == newParent)
throw std::runtime_error("Cannot parent a widget to itself.");
// You're trying to break the linearity of the object hierarchy by creating a circle in the graph.
if (this->IsAncestorOf(newParent))
throw std::runtime_error("Cannot create circular reference.");
// Don't allow for an instance to be parented to itself
if (this == newParent)
throw std::runtime_error("Cannot parent a widget to itself.");
// You're trying to break the linearity of the object hierarchy by creating a circle in the graph.
if (this->IsAncestorOf(newParent))
throw std::runtime_error("Cannot create circular reference.");
for (Widget* ancestor: this->GetAncestors()) {
if (oldParent && !ancestor->IsAncestorOf(newParent) && newParent != ancestor) {
ancestor->DescendantRemoved(this);
for (Widget* descendant : this->GetDescendants()) {
ancestor->DescendantRemoved(descendant);
}
}
}
// Remove ourselves from our parent (if we have one)
if (this->parent) {
// this->parent->ChildRemoved(this)
std::erase(this->parent->children, this);
}
// Update our old parent to the new one
this->parent = newParent;
// If our parent is set to nullptr, we can't update it's vector of children
if (!newParent) return;
// Add ourselves to our new parent's list of children
newParent->children.emplace_back(this);
//newParent->ChildAdded(this);
for (Widget* ancestor : this->GetAncestors()) {
if (!oldParent || (!oldParent->IsDescendantOf(newParent) && oldParent != ancestor)) {
// Don't fire unless an instance is actually a new descendant
ancestor->DescendantAdded(this);
for (Widget* descendant: this->GetDescendants()) {
ancestor->DescendantAdded(descendant);
}
}
for (Widget* ancestor: this->GetAncestors()) {
if (oldParent && !ancestor->IsAncestorOf(newParent) && newParent != ancestor) {
ancestor->DescendantRemoved(this);
for (Widget* descendant : this->GetDescendants())
ancestor->DescendantRemoved(descendant);
}
}
bool Widget::IsDescendantOf(Widget* ancestor) {
if (ancestor == nullptr) return false;
// Remove ourselves from our parent (if we have one)
if (this->parent)
std::erase(this->parent->children, this);
Widget* instance = this;
while (instance->GetParent()) {
instance = instance->GetParent();
if (instance == ancestor)
return true;
// Update our old parent to the new one
this->parent = newParent;
// If our parent is set to nullptr, we can't update it's vector of children
if (!newParent) return;
// Add ourselves to our new parent's list of children
newParent->children.emplace_back(this);
//newParent->ChildAdded(this);
for (Widget* ancestor : this->GetAncestors()) {
if (!oldParent || (!oldParent->IsDescendantOf(newParent) && oldParent != ancestor)) {
// Don't fire unless an instance is actually a new descendant
ancestor->DescendantAdded(this);
for (Widget* descendant: this->GetDescendants())
ancestor->DescendantAdded(descendant);
}
return false;
}
}
bool Widget::IsAncestorOf(Widget* descendant) {
if (descendant == nullptr) return false;
Widget* instance = descendant;
while (instance->GetParent()) {
instance = instance->GetParent();
if (instance == this)
return true;
}
return false;
bool Widget::IsDescendantOf(Widget* ancestor) {
if (ancestor == nullptr) return false;
Widget* instance = this;
while (instance->GetParent()) {
instance = instance->GetParent();
if (instance == ancestor)
return true;
}
return false;
}
Vector2 Widget::AnchorPoint() const {
return anchor_point;
bool Widget::IsAncestorOf(Widget* descendant) {
if (descendant == nullptr) return false;
Widget* instance = descendant;
while (instance->GetParent()) {
instance = instance->GetParent();
if (instance == this)
return true;
}
return false;
}
UDim Widget::PaddingLeft() const { return pad_left;}
UDim Widget::PaddingTop() const { return pad_top;}
UDim Widget::PaddingRight() const { return pad_right;}
UDim Widget::PaddingBottom() const { return pad_bottom;}
Vector2 Widget::AnchorPoint() const {
return anchor_point;
}
void Widget::PaddingLeft (const UDim& pad_left) { this->pad_left = pad_left; }
void Widget::PaddingTop (const UDim& pad_top) { this->pad_top = pad_top; }
void Widget::PaddingRight (const UDim& pad_right) { this->pad_right = pad_right; }
void Widget::PaddingBottom(const UDim& pad_bottom) { this->pad_bottom = pad_bottom; }
UDim Widget::PaddingLeft() const { return pad_left;}
UDim Widget::PaddingTop() const { return pad_top;}
UDim Widget::PaddingRight() const { return pad_right;}
UDim Widget::PaddingBottom() const { return pad_bottom;}
void Widget::Padding(const UDim &left, const UDim &top, const UDim &right, const UDim &bottom) {
PaddingLeft(left);
PaddingTop(top);
PaddingRight(right);
PaddingBottom(bottom);
void Widget::PaddingLeft (const UDim& value) { this->pad_left = value; }
void Widget::PaddingTop (const UDim& pad_top) { this->pad_top = pad_top; }
void Widget::PaddingRight (const UDim& pad_right) { this->pad_right = pad_right; }
void Widget::PaddingBottom(const UDim& pad_bottom) { this->pad_bottom = pad_bottom; }
void Widget::Padding(const UDim &left, const UDim &top, const UDim &right, const UDim &bottom) {
PaddingLeft(left);
PaddingTop(top);
PaddingRight(right);
PaddingBottom(bottom);
}
void Widget::Padding(const UDim &padding) {
Padding(padding, padding, padding, padding);
}
UDim Widget::MarginLeft () const { return margin_left;}
UDim Widget::MarginTop () const { return margin_top;}
UDim Widget::MarginRight () const { return margin_right;}
UDim Widget::MarginBottom() const { return margin_bottom;}
void Widget::MarginLeft(const UDim& ml) { margin_left = ml;}
void Widget::MarginTop(const UDim& mt) { margin_top = mt;}
void Widget::MarginRight(const UDim& mr) { margin_right = mr;}
void Widget::MarginBottom(const UDim& mb) { margin_bottom = mb;}
void Widget::Margin(const UDim &left, const UDim &top, const UDim &right, const UDim &bottom) {
MarginLeft(left);
MarginTop(top);
MarginRight(right);
MarginBottom(bottom);
}
void Widget::Margin(const UDim &margin) {
Margin(margin, margin, margin, margin);
}
std::string Widget::Name() const { return name; }
void Widget::Name(const std::string& new_name) { name = new_name;}
bool Widget::IsVisible() const { return visible; }
void Widget::Visible(bool enabled) { visible = enabled;}
Widget* Widget::GetParent() const { return parent; }
UDim2 Widget::Position() const { return position; }
void Widget::Position(const UDim2& pos) { position = pos; }
UDim2 Widget::Size() const { return size;}
void Widget::Size(const UDim2& s) { size = s; }
float Widget::GetAbsoluteRotation() const {
// TODO: implement rotation correctly!!
return rotation;
}
std::vector<Widget *> Widget::GetChildren() { return children; }
std::vector<Widget *> Widget::GetDescendants() {
std::vector<Widget*> descendants;
for (auto& child: this->GetChildren()) {
descendants.push_back(child);
std::vector<Widget*> recursiveDescendants = child->GetDescendants();
descendants.insert(descendants.end(), recursiveDescendants.begin(), recursiveDescendants.end());
}
return descendants;
}
void Widget::Padding(const UDim &padding) {
Padding(padding, padding, padding, padding);
}
UDim Widget::MarginLeft () const { return margin_left;}
UDim Widget::MarginTop () const { return margin_top;}
UDim Widget::MarginRight () const { return margin_right;}
UDim Widget::MarginBottom() const { return margin_bottom;}
void Widget::MarginLeft(const UDim& ml) { margin_left = ml;}
void Widget::MarginTop(const UDim& mt) { margin_top = mt;}
void Widget::MarginRight(const UDim& mr) { margin_right = mr;}
void Widget::MarginBottom(const UDim& mb) { margin_bottom = mb;}
void Widget::Margin(const UDim &left, const UDim &top, const UDim &right, const UDim &bottom) {
MarginLeft(left);
MarginTop(top);
MarginRight(right);
MarginBottom(bottom);
}
void Widget::Margin(const UDim &margin) {
Margin(margin, margin, margin, margin);
}
std::string Widget::Name() const { return name; }
void Widget::Name(const std::string& new_name) { name = new_name;}
bool Widget::IsVisible() const { return visible; }
void Widget::Visible(bool enabled) { visible = enabled;}
Widget* Widget::GetParent() const {
return parent;
}
UDim2 Widget::Position() const { return position; }
void Widget::Position(const UDim2& pos) { position = pos; }
UDim2 Widget::Size() const { return size;}
void Widget::Size(const UDim2& s) { size = s; }
float Widget::GetAbsoluteRotation() const {
// TODO: implement rotation correctly!!
return rotation;
}
std::vector<Widget *> Widget::GetChildren() { return children; }
std::vector<Widget *> Widget::GetDescendants() {
std::vector<Widget*> descendants;
for (auto& child: this->GetChildren()) {
descendants.push_back(child);
std::vector<Widget*> recursiveDescendants = child->GetDescendants();
descendants.insert(descendants.end(), recursiveDescendants.begin(), recursiveDescendants.end());
}
return descendants;
}
std::vector<Widget*> Widget::GetAncestors() {
std::vector<Widget*> ancestors;
for (Widget *ancestor = this->parent; ancestor; ancestor = ancestor->parent) {
ancestors.push_back(ancestor);
}
return ancestors;
}
std::vector<Widget*> Widget::GetAncestors() {
std::vector<Widget*> ancestors;
for (Widget *ancestor = this->parent; ancestor; ancestor = ancestor->parent)
ancestors.push_back(ancestor);
return ancestors;
}
/*float Widget::GetAbsoluteMarginLeft() {}
float Widget::GetAbsoluteMarginRight() {}
@@ -188,233 +177,456 @@ namespace JUI {
Vector2 Widget::GetAbsolutePaddingTopLeft() const {
auto parent_abs_size = this->GetParent()->GetAbsoluteSize();
Vector2 Widget::GetAbsolutePaddingTopLeft() const {
auto parent_abs_size = this->GetParent()->GetAbsoluteSize();
UDim2 pad_topleft = {parent->PaddingLeft(), parent->PaddingTop()};
return ComputeElementPadding(parent_abs_size, pad_topleft);
}
UDim padding_h = parent->PaddingLeft();
UDim padding_v = parent->PaddingTop();
Vector2 Widget::GetAbsoluteMarginTopLeft() {
// TODO: Implement correctly.
return {0,0};
}
float padding_x = padding_h.Pixels + (padding_h.Scale * parent_abs_size.x);
float padding_y = padding_v.Pixels + (padding_v.Scale * parent_abs_size.y);
Vector2 Widget::GetAbsolutePaddingBottomRight() const {
// Returns the amount by which the widget size should be shrunk.
// This combines the Left + Right for horizontal,
// and Top + Bottom for vertical padding.
return {padding_x, padding_y};
}
UDim padding_h = parent->PaddingLeft() + parent->PaddingRight();
UDim padding_v = parent->PaddingTop() + parent->PaddingBottom();
UDim2 pad_bottom_left = {padding_h, padding_v};
Vector2 Widget::GetAbsoluteMarginTopLeft()
{
// TODO: Implement correctly.
return ComputeElementPadding(parent->GetAbsoluteSize(), pad_bottom_left);
}
Vector2 Widget::GetAbsoluteMarginBottomRight() {
// TODO: Implement correctly.
return {0,0};
}
Vector2 Widget::GetAbsolutePosition() const {
if (this->parent == nullptr)
return {0,0};
}
Vector2 Widget::GetAbsolutePaddingBottomRight() const {
UDim padding_h = parent->PaddingLeft() + parent->PaddingRight();
float final_pad_x = padding_h.Pixels + (padding_h.Scale * parent->GetAbsoluteSize().x);
auto child_pos_scale = this->Position().GetScale();
auto child_pos_pixels = this->Position().GetPixels();
auto parent_abs_size = this->GetParent()->GetAbsoluteSize();
auto parent_abs_pos = this->GetParent()->GetAbsolutePosition();
UDim padding_v = parent->PaddingTop() + parent->PaddingBottom();
float final_pad_y = padding_v.Pixels + (padding_v.Scale * parent->GetAbsoluteSize().y);
auto abs_size = GetAbsoluteSize();
Vector2 padding = {final_pad_x, final_pad_y};
auto anchor_offset = abs_size * AnchorPoint();
return padding;
}
Vector2 padding_offset = GetAbsolutePaddingTopLeft();
Vector2 Widget::GetAbsoluteMarginBottomRight()
{
// TODO: Implement correctly.
return {0,0};
}
Vector2 Widget::GetAbsolutePosition() const {
if (this->parent == nullptr)
return {0,0};
auto child_pos_scale = this->Position().GetScale();
auto child_pos_pixels = this->Position().GetPixels();
auto parent_abs_size = this->GetParent()->GetAbsoluteSize();
auto parent_abs_pos = this->GetParent()->GetAbsolutePosition();
auto abs_size = GetAbsoluteSize();
auto anchor_offset = abs_size * AnchorPoint();
Vector2 padding_offset = GetAbsolutePaddingTopLeft();
Vector2 absolute_position =
Vector2 absolute_position =
parent_abs_pos + child_pos_pixels + (parent_abs_size * child_pos_scale) + padding_offset;
return absolute_position - anchor_offset;
}
Vector2 Widget::GetAbsoluteSize() const {
if (this->GetParent() == nullptr)
return {0,0};
Vector2 child_size_scale = this->Size().GetScale();
Vector2 child_size_px = this->Size().GetPixels();
Vector2 parent_abs_size = this->GetParent()->GetAbsoluteSize();
Vector2 pad_size_reduction = GetAbsolutePaddingBottomRight();
Vector2 abs_size = child_size_px + (parent_abs_size * child_size_scale) - pad_size_reduction;
return abs_size;
return absolute_position - anchor_offset;
}
Vector2 Widget::GetAbsoluteSize() const {
if (this->GetParent() == nullptr)
return {0,0};
Vector2 child_size_scale = this->Size().GetScale();
Vector2 child_size_px = this->Size().GetPixels();
Vector2 parent_abs_size = this->GetParent()->GetAbsoluteSize();
Vector2 pad_size_reduction = GetAbsolutePaddingBottomRight();
Vector2 abs_size = child_size_px + (parent_abs_size * child_size_scale) - pad_size_reduction;
return abs_size;
}
std::optional<Widget*> Widget::FindFirstChild(const std::string& search_name) {
for (auto& child : children) {
if (child->Name() == search_name)
return child;
}
return std::nullopt;
}
std::optional<Widget*> Widget::FindFirstChild(const std::string& search_name) {
for (auto& child : children) {
if (child->Name() == search_name)
return child;
}
return std::nullopt;
}
void Widget::Draw() {
if (!visible)
return;
PreDraw();
InnerDraw();
PostDraw();
DrawChildWidgets();
}
void Widget::Draw() {
if (!visible)
return;
void Widget::UpdateTweens(float elapsed) {
for (Tween* t: tweens)
t->Update(elapsed);
PreDraw();
InnerDraw();
PostDraw();
// TODO: Remove tweens if "dead"
DrawChildWidgets();
}
void Widget::UpdateTweens(float elapsed)
{
for (Tween& t: tweens)
tweens.erase(std::remove_if(tweens.begin(), tweens.end(), [](Tween* t){
if (t->HasCompleted())
{
t.tick(elapsed);
}
}
void Widget::Update(float delta) {
UpdateChildWidgets(delta);
UpdateTweens(delta);
}
struct {
bool operator()(Widget* a, Widget* b) const { return a->ZIndex() < b->ZIndex();}
} zIndexSort;
void Widget::DrawChildWidgets() {
std::sort(children.begin(), children.end(), zIndexSort);
for (auto child : children) {
child->Draw();
}
}
void Widget::UpdateChildWidgets(float delta) {
for (auto child : children) {
child->Update(delta);
}
}
Widget* Widget::GetFamilyTreeRoot() const {
// This is stupid, Fix ASAP!
auto parent = this->GetParent();
if (parent->GetParent() == nullptr)
return parent;
return parent->GetFamilyTreeRoot();
}
bool Widget::IsMouseInside() const {
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;
}), tweens.end());
}
void Widget::Update(float delta) {
UpdateChildWidgets(delta);
UpdateTweens(delta);
}
struct {
bool operator()(Widget* a, Widget* b) const { return a->ZIndex() < b->ZIndex();}
}
zIndexSort;
void Widget::DrawChildWidgets() {
std::sort(children.begin(), children.end(), zIndexSort);
for (auto child : children)
child->Draw();
}
void Widget::UpdateChildWidgets(float delta) {
for (auto child : children)
child->Update(delta);
}
Widget* Widget::GetFamilyTreeRoot() const {
// This is stupid, Fix ASAP!
auto parent = this->GetParent();
if (parent->GetParent() == nullptr)
return parent;
return parent->GetFamilyTreeRoot();
}
bool Widget::IsMouseInside() const {
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::ObserveMouseInput(MouseButton btn, bool pressed) {
mbtn = btn;
mb_state = pressed;
for (Widget* child : children) {
if (child->ObserveMouseInput(btn, pressed))
return true;
}
return false;
}
void Widget::ObserveMouseInput(MouseButton btn, bool pressed) {
mbtn = btn;
mb_state = pressed;
bool Widget::ObserveMouseMovement(const Vector2 &latest_known_pos) {
last_known_mouse_pos = latest_known_pos;
for (Widget* child : children)
{
child->ObserveMouseInput(btn, pressed);
}
for (Widget* child: children)
if (child->ObserveMouseMovement(latest_known_pos))
return true;
return false;
}
bool Widget::ObserveKeyInput(Key key, bool pressed) {
for (Widget* child : children)
if (child->ObserveKeyInput(key, pressed))
return true;
return false;
}
bool Widget::ObserveMouseWheel(int mwheel) {
// TODO: Log that this is a stub.
for (auto& child : children) {
if (child->ObserveMouseWheel(mwheel))
return true;
}
return false;
}
void Widget::ObserveMouseMovement(const Vector2 &latest_known_pos) {
last_known_mouse_pos = latest_known_pos;
for (Widget* child : children)
{
child->ObserveMouseMovement(latest_known_pos);
}
}
void Widget::ObserveKeyInput(Key key, bool pressed) {
for (Widget* child : children) {
child->ObserveKeyInput(key, pressed);
}
}
bool Widget::IsAncestorOf(Widget *descendant) const {
if (descendant == nullptr)
return false;
Widget *instance = descendant;
while (instance->GetParent()) {
instance = instance->GetParent();
if (instance == this)
return true;
}
bool Widget::IsAncestorOf(Widget *descendant) const {
if (descendant == nullptr)
return false;
}
bool Widget::IsDescendantOf(Widget *ancestor) const {
if (ancestor == nullptr)
return false;
const Widget *instance = this;
while (instance->GetParent()) {
instance = instance->GetParent();
if (instance == ancestor)
return true;
}
Widget* instance = descendant;
while (instance->GetParent()) {
instance = instance->GetParent();
if (instance == this)
return true;
}
return false;
}
bool Widget::IsDescendantOf(Widget *ancestor) const {
if (ancestor == nullptr)
return false;
const Widget *instance = this;
while (instance->GetParent()) {
instance = instance->GetParent();
if (instance == ancestor)
return true;
}
return false;
}
Widget *Widget::Add(Widget *newChild) {
newChild->Parent(this);
return newChild;
}
Widget *Widget::Add(Widget *newChild) {
newChild->Parent(this);
return newChild;
}
void Widget::AnchorPoint(const Vector2& point) {
anchor_point = point;
}
void Widget::AnchorPoint(const Vector2& point) {
anchor_point = point;
}
int Widget::ZIndex() const { return zindex;}
int Widget::ZIndex() const { return zindex;}
void Widget::ZIndex(int new_zindex) { zindex = new_zindex; }
void Widget::ZIndex(int new_zindex) { zindex = new_zindex; }
AABB2D Widget::GetActualRenderBounds() const {
return {GetAbsolutePosition(), GetAbsoluteSize()};
}
AABB2D Widget::GetActualRenderBounds() const {
return {GetAbsolutePosition(), GetAbsoluteSize()};
}
void Widget::SetViewportSize(const Vector2 &vps) {
viewport_size = vps;
void Widget::SetViewportSize(const Vector2 &vps) {
viewport_size = vps;
for(auto& child : children)
{
child->SetViewportSize(viewport_size);
}
}
for(auto& child : children)
child->SetViewportSize(viewport_size);
}
Vector2 Widget::ComputeElementPadding(const Vector2 &size, const UDim2 &padding) const {
float pad_x = padding.X.Pixels + (padding.X.Scale * size.x);
float pad_y = padding.Y.Pixels + (padding.Y.Scale * size.y);
return {pad_x, pad_y};
}
AABB2D Widget::AbsoluteBounds() const {
return {GetAbsolutePosition(), GetAbsoluteSize()};
}
Tween* Widget::TweenPosition(const UDim2& goal, TweenInfo params) {
UDim2 start_position = this->Position();
TweenTickFunc updateTillGoalReached = [this, start_position, goal] (float elapsed, float progress) mutable {
UDim2 pos = start_position.Lerp(goal, progress);
Position(pos);
};
Tween* t = new Tween(updateTillGoalReached, params);
tweens.push_back(t);
return t;
}
Tween* Widget::TweenSize(const UDim2& goal, TweenInfo params) {
UDim2 start_size = this->Size();
TweenTickFunc updateTillGoalReached = [this, start_size, goal] (float elapsed, float progress) mutable {
UDim2 step_size = start_size.Lerp(goal, progress);
Size(step_size);
};
Tween* t = new Tween(updateTillGoalReached, params);
tweens.push_back(t);
return t;
}
Tween* Widget::TweenPaddingLeft(const JUI::UDim& goal, JUI::TweenInfo info) {
//UDum2
UDim start_padding = this->PaddingLeft();
TweenTickFunc updateTillGoalReached = [this, start_padding, goal] (float elapsed, float progress) mutable {
UDim step_padding = start_padding.Lerp(goal, progress);
PaddingLeft(step_padding);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween* Widget::TweenPaddingRight(const JUI::UDim& goal, JUI::TweenInfo info) {
//UDum2
UDim start_padding = this->PaddingRight();
TweenTickFunc updateTillGoalReached = [this, start_padding, goal] (float elapsed, float progress) mutable {
UDim step_padding = start_padding.Lerp(goal, progress);
PaddingRight(step_padding);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween* Widget::TweenPaddingTop(const JUI::UDim& goal, JUI::TweenInfo info) {
//UDum2
UDim start_padding = this->PaddingTop();
TweenTickFunc updateTillGoalReached = [this, start_padding, goal] (float elapsed, float progress) mutable {
UDim step_padding = start_padding.Lerp(goal, progress);
PaddingTop(step_padding);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween* Widget::TweenPaddingBottom(const JUI::UDim& goal, JUI::TweenInfo info) {
//UDum2
UDim start_padding = this->PaddingBottom();
TweenTickFunc updateTillGoalReached = [this, start_padding, goal] (float elapsed, float progress) mutable {
UDim step_padding = start_padding.Lerp(goal, progress);
PaddingBottom(step_padding);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween *Widget::TweenPadding(const JUI::UDim &gl, const JUI::UDim &gt, const JUI::UDim &gr,
const JUI::UDim &gb, JUI::TweenInfo info) {
UDim spl = this->PaddingLeft();
UDim spr = this->PaddingRight();
UDim spt = this->PaddingTop();
UDim spb = this->PaddingBottom();
TweenTickFunc updateTillGoalReached = [this, spl, spr, spt, spb, gl, gt, gr, gb] (float elapsed, float progress) mutable {
UDim step_l = spl.Lerp(gl, progress);
UDim step_r = spr.Lerp(gr, progress);
UDim step_b = spb.Lerp(gb, progress);
UDim step_t = spt.Lerp(gt, progress);
Padding(step_l, step_r, step_b, step_t);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween *Widget::TweenPadding(const JUI::UDim &goal, JUI::TweenInfo info) {
UDim start_padding = this->PaddingTop(); // TODO: Determine which side is most appropriate as start_point.
TweenTickFunc updateTillGoalReached = [this, start_padding, goal] (float elapsed, float progress) mutable {
UDim step_padding = start_padding.Lerp(goal, progress);
Padding(step_padding);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween *Widget::TweenMargin(const JUI::UDim &gl, const JUI::UDim &gt, const JUI::UDim &gr,
const JUI::UDim &gb, JUI::TweenInfo info) {
UDim spl = this->MarginLeft();
UDim spr = this->MarginRight();
UDim spt = this->MarginTop();
UDim spb = this->MarginBottom();
TweenTickFunc updateTillGoalReached = [this, spl, spr, spt, spb, gl, gt, gr, gb] (float elapsed, float progress) mutable {
UDim step_l = spl.Lerp(gl, progress);
UDim step_r = spr.Lerp(gr, progress);
UDim step_b = spb.Lerp(gb, progress);
UDim step_t = spt.Lerp(gt, progress);
Margin(step_l, step_r, step_b, step_t);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween *Widget::TweenMargin(const JUI::UDim &goal, JUI::TweenInfo info) {
UDim start_Margin = this->MarginTop(); // TODO: Determine which side is most appropriate as start_point.
TweenTickFunc updateTillGoalReached = [this, start_Margin, goal] (float elapsed, float progress) mutable {
UDim step_Margin = start_Margin.Lerp(goal, progress);
Margin(step_Margin);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween* Widget::TweenMarginLeft(const JUI::UDim& goal, JUI::TweenInfo info) {
UDim start = this->MarginLeft();
TweenTickFunc updateTillGoalReached = [this, start, goal] (float elapsed, float progress) mutable {
UDim step = start.Lerp(goal, progress);
MarginLeft(step);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween* Widget::TweenMarginRight(const JUI::UDim& goal, JUI::TweenInfo info) {
UDim start = this->MarginRight();
TweenTickFunc updateTillGoalReached = [this, start, goal] (float elapsed, float progress) mutable {
UDim step = start.Lerp(goal, progress);
MarginRight(step);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween* Widget::TweenMarginTop(const JUI::UDim& goal, JUI::TweenInfo info) {
UDim start = this->MarginTop();
TweenTickFunc updateTillGoalReached = [this, start, goal] (float elapsed, float progress) mutable {
UDim step = start.Lerp(goal, progress);
MarginTop(step);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween* Widget::TweenMarginBottom(const JUI::UDim& goal, JUI::TweenInfo info) {
UDim start = this->MarginBottom();
TweenTickFunc updateTillGoalReached = [this, start, goal] (float elapsed, float progress) mutable {
UDim step = start.Lerp(goal, progress);
MarginBottom(step);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
Tween *Widget::TweenAnchorPoint(const Vector2 &goal, TweenInfo info) {
Vector2 start = this->AnchorPoint();
TweenTickFunc updateTillGoalReached = [this, start, goal] (float elapsed, float progress) mutable {
Vector2 step = start.Lerp(goal, progress);
AnchorPoint(step);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
float Widget::ComputeElementPadding(float size, const UDim &padding) {
return padding.Pixels + (padding.Scale * size);
}
AABB2D Widget::AbsoluteBounds() const {
return {GetAbsolutePosition(), GetAbsoluteSize()};
}
}

4
src/JUI/DefaultStyle.cpp Normal file
View File

@@ -0,0 +1,4 @@
#include <JUI/DefaultStyle.hpp>
namespace DefaultStyle
{ }

View File

@@ -2,8 +2,45 @@
namespace JUI
{
// TODO: This is duplicated here and in ReCaveGame::Loggers.cpp
// Bring into jlog as utility functions.
std::string format_timestamp(jlog::Timestamp ts) {
return std::format(
"{}-{}-{} {}:{}:{}.{}",
ts.Year(),
ts.Month(),
ts.Day(),
ts.Hour().count(),
ts.Minute().count(),
ts.Second().count(),
ts.Millisecond().count());
}
// TODO: This is duplicated here and in ReCaveGame::Loggers.cpp
// Bring into jlog as utility functions.
std::string format_source_location(const std::source_location& location) {
return std::format("{} @ {}:{}", location.function_name(), location.file_name(), location.line());
}
JUILogger::JUILogger(const std::string &context, std::ofstream &file, Color4 contextColor, Color4 timestampColor,
Color4 locationColor, Color4 pointerColor, Color4 messageColor)
: jlog::GenericLogger(context, file, contextColor, timestampColor, locationColor, pointerColor, messageColor) { }
void JUILogger::Log(const std::string &message, const std::source_location &location, const jlog::Timestamp &ts) {
GenericLogger::Log(message, location, ts);
std::string formatted_timestamp = format_timestamp(ts);
std::string formatted_source_location = format_source_location(location);
std::string final_result = std::format("[{}] [{}] []", formatted_timestamp, this->ConsoleLogger::context, message);
OnLog.Invoke(final_result, messageColor);
}
std::ofstream GlobalLogFile("latest.log", std::ios_base::app);
jlog::GenericLogger UILogs {"JUI", GlobalLogFile, Colors::Green, Colors::Gray, Colors::Gray, Colors::Green, Colors::White};
JUILogger UILogs {"JUI", GlobalLogFile, Colors::Green, Colors::Gray, Colors::Gray, Colors::Green, Colors::White};
}

View File

@@ -148,4 +148,55 @@ namespace JUI
}
float EasingFunctions::EaseInOutLinear(float t) { return t;}
void Tween::Update(float elapsed) {
if (time <= 0)
{
// TODO: Error in this case.
completed = true;
}
if (delay_time > 0)
{
delay_time -= elapsed;
return;
}
progress += (elapsed / time);
if (progress >= 1.f)
{
progress = 1.f;
if (!completed)
{
completed = true;
Completed.Invoke();
}
return;
}
float modified_progress = easing_func(progress);
tick_func(elapsed, modified_progress);
}
void Tween::ForceFinish() {
progress = 1.f;
}
void Tween::Cancel() {
completed = true;
}
bool Tween::Paused() const { return paused; }
bool Tween::HasCompleted() const { return completed; }
void Tween::Start() { progress = 0; Resume(); }
Tween::Tween(TweenTickFunc tick_func) {
this->tick_func = tick_func;
}
}

View File

@@ -31,3 +31,16 @@ JUI::UDim JUI::UDim::operator*(float rhs) const {
JUI::UDim JUI::UDim::operator/(float rhs) const {
return {static_cast<int>(Pixels / rhs), Scale/rhs};
}
JUI::UDim JUI::UDim::Lerp(const JUI::UDim &goal, float t) {
float scale = J3ML::Math::Lerp(Scale, goal.Scale, t);
float pixels = J3ML::Math::Lerp(Pixels, goal.Pixels, t);
return UDim(pixels, scale);
}
bool JUI::UDim::Equals(const JUI::UDim &rhs, float epsilon) {
return J3ML::Math::Equal((float)Pixels, rhs.Pixels, epsilon)
&& J3ML::Math::Equal(Scale, rhs.Scale, epsilon);
}

View File

@@ -47,3 +47,15 @@ Vector2 JUI::UDim2::GetScale() const {
Vector2 JUI::UDim2::GetPixels() const {
return {static_cast<float>(X.Pixels), static_cast<float>(Y.Pixels)};
}
JUI::UDim2 JUI::UDim2::Lerp(const JUI::UDim2 &goal, float t) {
return UDim2(
X.Lerp(goal.X, t),
Y.Lerp(goal.Y, t)
);
}
bool JUI::UDim2::Equals(const JUI::UDim2 &rhs, float epsilon) {
return X.Equals(rhs.X, epsilon) &&
Y.Equals(rhs.Y, epsilon);
}

View File

@@ -2,9 +2,9 @@
#include <JUI/Widgets/Button.hpp>
#include <jlog/Logger.hpp>
namespace JUI
{
namespace JUI {
Button::Button(): Rect(), Clickable() {
Name("Button");
BGColor(BaseBGColor());
BorderColor(BaseBorderColor());
}
@@ -13,15 +13,11 @@ namespace JUI
this->Parent(parent);
};
void Button::OnClick(const Vector2& mouse_pos, const MouseButton& btn)
{
void Button::OnClick(const Vector2& mouse_pos, const MouseButton& btn) {
if (disabled)
return;
Clickable::OnClick(mouse_pos, btn);
//BGColor(PressedBGColor());
//BorderColor(PressedBorderColor());
}
void Button::OnRelease(const J3ML::LinearAlgebra::Vector2 &mouse_pos, const JUI::MouseButton &btn,
@@ -31,14 +27,9 @@ namespace JUI
return;
Clickable::OnRelease(mouse_pos, btn, still_hovering);
//BGColor(BaseBGColor());
//BorderColor(BaseBorderColor());
}
void Button::UpdateVisualState()
{
void Button::UpdateVisualState() {
if (Disabled()) {
BGColor(DisabledBGColor());
BorderColor(DisabledBorderColor());
@@ -117,8 +108,6 @@ namespace JUI
if (Disabled() || IsClicked())
return;
//BGColor(HoveredBGColor());
//BorderColor(HoveredBorderColor());
}
void Button::OnExit(const Vector2 &MousePos) {
@@ -126,9 +115,6 @@ namespace JUI
if (Disabled() || IsClicked())
return;
//BGColor(BaseBGColor());
//BorderColor(BaseBorderColor());
}
Color4 Button::HoveredBGColor() const { return hover_bg; }
@@ -168,7 +154,6 @@ namespace JUI
HoveredBorderColor(hover);
PressedBorderColor(pressed);
DisabledBGColor(disabled);
UpdateVisualState();
}
@@ -176,19 +161,7 @@ namespace JUI
BaseBGColor(base);
HoveredBGColor(hover);
PressedBGColor(pressed);
DisabledBGColor(disabled);
DisabledBGColor(disabled);\
UpdateVisualState();
}
//void Button::SetTooltip(const std::string &content, float delay) {
//tooltip_limit = delay;
// TODO: Verify this is okay to do:
//tooltip = new Tooltip(this);
//tooltip->SetContent(content);
//tooltip->ZIndex(5);
//tooltip->Visible(false);
//}
}

View File

@@ -1,8 +1,33 @@
#include <JUI/Widgets/Checkbox.hpp>
namespace JUI
{
namespace JUI {
Checkbox::Checkbox() : Button(), CheckboxBase() {
Padding(2_px);
}
Checkbox::Checkbox(Widget *parent) : Checkbox() {
this->Parent(parent);
}
void Checkbox::Update(float delta) {
Button::Update(delta);
}
void Checkbox::OnRelease(const Vector2 &mouse_pos, const MouseButton &bnt, bool still_hovering) {
Button::OnRelease(mouse_pos, bnt, still_hovering);
checked = !checked;
}
void Checkbox::InnerDraw() {
Rect::InnerDraw();
if (checked) {
Vector2 pos_pad = ComputeElementPadding(GetAbsoluteSize(), {PaddingLeft(), PaddingTop()});
Vector2 size_pad = ComputeElementPadding(GetAbsoluteSize(), {PaddingLeft()+PaddingRight(), PaddingTop()+PaddingBottom()});
Vector2 padded_pos = GetAbsolutePosition() + pos_pad;
Vector2 padded_size = GetAbsoluteSize() - size_pad;
RectBase::Draw(check_color, check_color, padded_pos, padded_size);
}
}
}

View File

@@ -0,0 +1,84 @@
#include <JUI/Widgets/Collapsible.hpp>
namespace JUI {
Collapsible::Collapsible() : Rect() {
Name("Collapsible");
header = new TextButton(this);
header->Size({100_percent, header_height});
header->SetContent("Collapsible");
header->BaseBGColor(Colors::DarkGray);
header->BGColor(Colors::DarkGray);
header->Center();
//header->
header->OnClickEvent += [this] (auto a, auto b) {
if (Collapsed())
Expand();
else
Collapse();
};
lil_arrow = new TextRect(header);
lil_arrow->Size({header_height, header_height});
lil_arrow->BGColor(Colors::Transparent);
lil_arrow->SetContent("/\\");
lil_arrow->BorderWidth(0);
content_box = new Rect(this);
content_box->BGColor(Colors::Transparent);
content_box->BorderColor(Colors::Transparent);
content_box->BorderWidth(0);
content_box->Position({0_px, header_height});
content_box->Size({100_percent, 100_percent - header_height});
}
void Collapsible::Collapse() {
//Size(saved_size);
lil_arrow->SetContent("\\/");
content_box->Visible(false);
if (resize_anim != nullptr && !resize_anim->HasCompleted()) {
resize_anim->Cancel();
} else {
saved_size = Size();
}
resize_anim = TweenSize(UDim2(saved_size.X, header_height), {
.time = 0.25f
});
collapsed = true;
}
void Collapsible::Expand() {
lil_arrow->SetContent("/\\");
content_box->Visible(true);
if (resize_anim != nullptr && !resize_anim->HasCompleted())
resize_anim->Cancel();
resize_anim = TweenSize(saved_size, {
.time = 0.25f
});
collapsed = false;
}
bool Collapsible::Collapsed() const { return collapsed; }
Collapsible::Collapsible(Widget *parent) : Collapsible() {
this->Parent(parent);
}
TextButton *Collapsible::Header() { return header; }
Rect *Collapsible::ContentBox() {return content_box; }
UDim Collapsible::HeaderHeight() const { return header_height;}
void Collapsible::HeaderHeight(const UDim &value) { header_height = value; }
void Collapsible::Title(const std::string &value) {
header->SetContent(value);
}
std::string Collapsible::Title() const { return header->GetContent(); }
}

View File

@@ -0,0 +1,87 @@
#include <JUI/Widgets/CommandLine.hpp>
namespace JUI
{
// Get current date/time, format is YYYY-MM-DD.HH:mm:ss
const std::string currentDateTime() {
time_t now = time(0);
struct tm tstruct;
char buf[80];
tstruct = *localtime(&now);
// Visit http://en.cppreference.com/w/cpp/chrono/c/strftime
// for more information about date/time format
strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);
return buf;
}
CommandLine::CommandLine() {
this->SetTitle("Console");
message_log_box = new JUI::ScrollingRect(this->ViewportInstance());
message_log_box->Size(JUI::UDim2(0, -input_form_height, 1, 1));
message_log_list = new JUI::VerticalListLayout(message_log_box);
message_log_list->LayoutOrder(JUI::LayoutOrder::V::BOTTOM);
input_box = new JUI::TextInputForm(this->ViewportInstance());
input_box->SetAutoCompleteText(">Type Commands Here");
input_box->SetContent("");
input_box->Size(JUI::UDim2(0, 20, 1, 0));
input_box->Position(JUI::UDim2(0, -20, 0, 1));
input_box->BGColor(Colors::Grays::DarkSlateGray);
input_box->SetTextSize(16);
input_box->SetTextColor(Colors::White);
input_box->SetAutocompleteTextColor(Colors::Gray);
input_box->OnReturn += [this] (const std::string& msg) {
OnInputBoxSend(msg);
Log(std::format("[{}] >{}", currentDateTime(), msg));
};
}
CommandLine::CommandLine(Widget *parent): CommandLine() {
this->Parent(parent);
}
bool CommandLine::Opened() const { return this->open == true;}
bool CommandLine::Closed() const { return this->open == false;}
void CommandLine::Open(bool openVal) { SetOpen(openVal); }
void CommandLine::Close(bool openVal) { SetOpen(openVal);}
void CommandLine::SetOpen(bool openVal) {
this->Visible(openVal);
this->open = openVal;
}
void CommandLine::Log(const std::string &message, const Color4 &color) {
const Color4 light {60, 60, 60};
const Color4 dark {80, 80, 80};
auto* entry = new JUI::TextRect(message_log_list);
entry->Size({100_percent, UDim(message_height, 0)});
entry->SetContent(message);
entry->LayoutOrder(index++);
entry->BGColor(index%2 == 0 ? light : dark);
entry->SetTextColor(color);
entry->SetTextSize(14);
}
bool CommandLine::ObserveKeyInput(Key key, bool pressed) {
if (!IsVisible())
return false;
return Widget::ObserveKeyInput(key, pressed);
}
void CommandLine::OnInputBoxSend(const std::string &message) {
OnInput.Invoke(message);
input_box->SetContent("");
input_box->SetAutoCompleteText("");
}
}

View File

@@ -2,9 +2,8 @@
namespace JUI
{
Image::Image() : Widget(), ImageBase()
{
Image::Image() : Widget(), ImageBase() {
Name("Image");
}
Image::Image(Widget* parent) : Image()

View File

@@ -2,9 +2,58 @@
void JUI::ImageButton::Update(float delta) {
Button::Update(delta);
UpdateImageVisualState();
}
void JUI::ImageButton::Draw() {
Button::Draw();
ImageBase::Draw(GetAbsolutePosition()+GetAbsolutePaddingTopLeft(), GetAbsoluteSize()-GetAbsolutePaddingBottomRight());
}
JUI::ImageButton::ImageButton() : ImageBase(), Button() {
Name("ImageButton");
}
JUI::ImageButton::ImageButton(JUI::Widget *parent) : ImageButton() { Parent(parent); }
void JUI::ImageButton::HoveredImageColor(const Color4 &value) {
hover_img_color = value;
}
void JUI::ImageButton::BaseImageColor(const Color4 &value) {
base_img_color = value;
}
void JUI::ImageButton::PressedImageColor(const Color4 &value) {
pressed_img_color = value;
}
void JUI::ImageButton::DisabledImageColor(const Color4 &value) {
disabled_img_color = value;
}
void JUI::ImageButton::UpdateImageVisualState() {
if (Disabled())
ImageBase::Color(DisabledImageColor());
else if (IsClicked())
ImageBase::Color(PressedImageColor());
else if (IsHovered())
ImageBase::Color(HoveredImageColor());
else
ImageBase::Color(BaseImageColor());
}
Color4 JUI::ImageButton::HoveredImageColor() const {return hover_img_color;}
Color4 JUI::ImageButton::BaseImageColor() const { return base_img_color; }
Color4 JUI::ImageButton::PressedImageColor() const { return pressed_img_color; }
Color4 JUI::ImageButton::DisabledImageColor() const { return disabled_img_color; }
void JUI::ImageButton::ImageColors(const Color4 &hover, const Color4 &base, const Color4 &press, const Color4 &disabled) {
HoveredImageColor(hover);
BaseImageColor(base);
PressedImageColor(press);
DisabledImageColor(disabled);
}

View File

@@ -1,2 +1,18 @@
#include <JUI/Widgets/ImageRect.hpp>
JUI::ImageRect::ImageRect() : Rect(), ImageBase() {
Name("ImageRect");
}
JUI::ImageRect::ImageRect(JUI::Widget *parent) : ImageRect() {
Parent(parent);
}
void JUI::ImageRect::Update(float delta) {
Rect::Update(delta);
}
void JUI::ImageRect::Draw() {
Rect::Draw();
ImageBase::Draw(GetAbsolutePosition()+GetAbsolutePaddingTopLeft(), GetAbsoluteSize()-GetAbsolutePaddingBottomRight());
}

View File

@@ -2,17 +2,26 @@
namespace JUI
{
ListLayout::ListLayout() : LayoutContainer() {}
ListLayout::ListLayout() : LayoutContainer() {
Name("ListLayout");
}
ListLayout::ListLayout(Widget* parent) : ListLayout() {}
VerticalListLayout::VerticalListLayout() : ListLayout() {}
VerticalListLayout::VerticalListLayout(Widget* parent) : VerticalListLayout()
{
VerticalListLayout::VerticalListLayout(Widget* parent) : VerticalListLayout() {
this->Parent(parent);
}
struct {
bool operator()(Widget* a, Widget* b) const { return a->LayoutOrder() > b->LayoutOrder();}
} layoutOrderSort;
void VerticalListLayout::ApplyLayout() {
content_height = 0;
std::sort(children.begin(), children.end(), layoutOrderSort);
if (layout == LayoutOrder::V::TOP)
{
int consumed_height = 0;
@@ -21,10 +30,18 @@ namespace JUI
// TODO: Implement widget.LayoutOrder property
// TODO: Sort children by LayoutOrder
consumed_height += pad_top.Pixels;
content_height += pad_top.Pixels;
child_widget->Position({0, consumed_height, 0, 0});
consumed_height += child_widget->GetAbsoluteSize().y;
content_height += child_widget->GetAbsoluteSize().y;
consumed_height += pad_bottom.Pixels;
content_height += pad_bottom.Pixels;
}
}
if (layout == LayoutOrder::V::BOTTOM)
@@ -34,10 +51,18 @@ namespace JUI
{
// TODO: Implement widget.LayoutOrder property
// TODO: Sort children by LayoutOrder
consumed_height -= pad_top.Pixels;
content_height += pad_top.Pixels;
consumed_height -= child_widget->GetAbsoluteSize().y;
content_height += child_widget->GetAbsoluteSize().y;
child_widget->Position({0, consumed_height, 0, 0});
consumed_height -= pad_bottom.Pixels;
content_height += pad_bottom.Pixels;
}
}
}
@@ -48,6 +73,10 @@ namespace JUI
LayoutOrder::V VerticalListLayout::LayoutOrder() const { return layout;}
float VerticalListLayout::CurrentContentHeight() const {
return content_height;
}
HorizontalListLayout::HorizontalListLayout() : ListLayout() {}
@@ -58,15 +87,25 @@ namespace JUI
void HorizontalListLayout::ApplyLayout() {
// TODO: Implement widget.LayoutOrder and sort by that number.
std::sort(children.begin(), children.end(), layoutOrderSort);
if (layout == LayoutOrder::H::LEFT)
{
int consumed_width = 0;
for (auto &child_widget : children)
{
consumed_width += pad_left.Pixels;
content_width += pad_left.Pixels;
child_widget->Position({consumed_width, 0, 0, 0});
consumed_width += child_widget->GetAbsoluteSize().x;
content_width += child_widget->GetAbsoluteSize().x;
consumed_width += pad_right.Pixels;
content_width += pad_right.Pixels;
}
}
@@ -76,9 +115,15 @@ namespace JUI
for (auto &child_widget : children)
{
consumed_width -= pad_left.Pixels;
content_width += pad_left.Pixels;
consumed_width -= child_widget->GetAbsoluteSize().x;
content_width += child_widget->GetAbsoluteSize().x;
child_widget->Position({consumed_width, 0, 0, 0});
consumed_width -= pad_right.Pixels;
content_width += pad_right.Pixels;
}
}
}
@@ -90,4 +135,6 @@ namespace JUI
LayoutOrder::H HorizontalListLayout::LayoutOrder() const {
return layout;
}
float HorizontalListLayout::CurrentContentWidth() const { return content_width;}
}

View File

@@ -1,8 +1,7 @@
#include <JUI/Widgets/NineSlice.hpp>
#include <JGL/JGL.h>
namespace JUI
{
namespace JUI {
AABB2D JUI::NineSliceRect::TopLeftQuad() const { return top_left_quad; }
AABB2D NineSliceRect::TopRightQuad() const { return top_right_quad; }
@@ -42,10 +41,7 @@ namespace JUI
void NineSliceRect::Draw() {
Rect::Draw();
//JGL::J2D::Begin();
Vector2 abs_pos = GetAbsolutePosition();
Vector2 abs_size = GetAbsoluteSize();
// Draw Top-Left Quad.
@@ -58,7 +54,6 @@ namespace JUI
Vector2 tr_computed_pos = abs_pos + Vector2(abs_size.x - tr_quad.maxPoint.x, 0);
JGL::J2D::DrawPartialSprite(texture, tr_computed_pos, tr_quad.minPoint, tr_quad.maxPoint);
// Draw Bottom Left Quad
auto bl_quad = BottomLeftQuad();
Vector2 bl_computed_pos = abs_pos + Vector2(0, abs_size.y - bl_quad.maxPoint.y);
@@ -69,8 +64,6 @@ namespace JUI
Vector2 br_computed_pos = abs_pos + abs_size - br_quad.maxPoint;
JGL::J2D::DrawPartialSprite(texture, br_computed_pos, br_quad.minPoint, br_quad.maxPoint);
// Draw Top-Quad.
Vector2 t_computed_pos = abs_pos + Vector2(tl_quad.maxPoint.x, 0);
auto t_quad = TopQuad();
@@ -91,31 +84,25 @@ namespace JUI
float l_scaling = abs_height_minus_corners / l_quad.maxPoint.y;
JGL::J2D::DrawPartialSprite(texture, l_computed_pos, l_quad.minPoint, l_quad.maxPoint, 0, {0, 0}, {1, l_scaling});
// Draw Right Quad
auto r_quad = RightQuad();
Vector2 r_computed_pos = abs_pos + Vector2(abs_size.x - tr_quad.maxPoint.x, tr_quad.maxPoint.y);
float r_scaling = abs_height_minus_corners / r_quad.maxPoint.y;
JGL::J2D::DrawPartialSprite(texture, r_computed_pos, r_quad.minPoint, r_quad.maxPoint, 0, {0,0}, {1, r_scaling});
// Draw Center Quad
auto c_quad = CenterQuad();
Vector2 c_computed_pos = abs_pos + tl_quad.maxPoint;
Vector2 c_scaling = Vector2(abs_width_minus_corners, abs_height_minus_corners) / c_quad.maxPoint;
JGL::J2D::DrawPartialSprite(texture, c_computed_pos, c_quad.minPoint, c_quad.maxPoint, 0, {0,0}, c_scaling);
//JGL::J2D::End();
}
NineSliceRect::NineSliceRect() : Rect(), ImageBase() {}
NineSliceRect::NineSliceRect() : Rect(), ImageBase() {
Name("NineSliceRect");
}
NineSliceRect::NineSliceRect(Widget *parent) : Rect(parent), ImageBase() {}
NineSliceRect::NineSliceRect(Widget *parent, JGL::Texture *texture) : Rect(parent), ImageBase(texture) {}
}

View File

@@ -1,41 +1,20 @@
#include <JUI/Mixins/Toggleable.hpp>
#include <JUI/Widgets/RadioButton.hpp>
namespace JUI
{
RadioButton::RadioButton() : Button(), Toggleable() {}
namespace JUI {
RadioButton::RadioButton() : Button(), Toggleable() {
Name("RadioButton");
}
RadioButton::RadioButton(Widget* parent) : RadioButton() {
this->Parent(parent);
}
/*
void RadioButton::OnToggleOn() {
OnToggleOnEvent.Invoke();
}
void RadioButton::OnToggleOff() {
OnToggleOffEvent.Invoke();
}
*/
void RadioButton::OnClick(const J3ML::LinearAlgebra::Vector2 &mouse_pos, const JUI::MouseButton &btn) {
Button::OnClick(mouse_pos, btn);
Toggleable::Update();
/*
toggle = !toggle;
if (toggle) {
OnToggleOn();
} else {
OnToggleOff();
}
*/
}
void RadioButton::Update(float delta)
{
void RadioButton::Update(float delta) {
Button::Update(delta);
}
}

View File

@@ -3,14 +3,15 @@
#include <jlog/Logger.hpp>
namespace JUI {
Rect::Rect(): Widget(), RectBase() {}
Rect::Rect(): Widget(), RectBase() {
Name("Rect");
}
Rect::Rect(Widget *parent): Rect() {
this->Parent(parent);
}
void Rect::PreDraw()
{
Vector2 abs_pos = GetAbsolutePosition();
@@ -42,24 +43,17 @@ namespace JUI {
}
}
void Rect::InnerDraw()
{
//J2D::Begin();
void Rect::InnerDraw() {
RectBase::Draw(GetAbsolutePosition(), GetAbsoluteSize());
//J2D::End();
// Draw Child Elements with scissor clipping still active
//Widget::DrawChildWidgets();
}
void Rect::Update(float delta) {
if (IsMouseInside() && !mouse_inside_debounce)
{
if (IsMouseInside() && !mouse_inside_debounce) {
MouseEnter.Invoke(last_known_mouse_pos);
mouse_inside_debounce = true;
}
if (!IsMouseInside() && mouse_inside_debounce)
{
if (!IsMouseInside() && mouse_inside_debounce) {
MouseExit.Invoke(last_known_mouse_pos);
mouse_inside_debounce = false;
}
@@ -86,7 +80,6 @@ namespace JUI {
void Rect::Draw() {
Widget::Draw();
}
}

View File

@@ -1,21 +1,36 @@
#include "JUI/Widgets/Scene.hpp"
namespace JUI {
Scene::Scene(): Widget() {}
Scene::Scene(): Widget() {
Name("Scene");
}
Vector2 Scene::GetAbsolutePosition() const { return {0,0};}
Vector2 Scene::GetAbsoluteSize() const { return viewport_size;}
Vector2 Scene::GetAbsoluteSize() const { return viewport_size / ui_scale;}
void Scene::Draw() {
J2D::Begin();
glScalef(ui_scale.x, ui_scale.y, 0.f);
Widget::Draw();
J2D::End();
}
void Scene::Update(float delta) {
Widget::Update(delta);
}
void Scene::GlobalUIScale(const Vector2 &value) { ui_scale = value;}
Vector2 Scene::GlobalUIScale() const { return ui_scale; }
bool Scene::ObserveMouseMovement(const Vector2 &latest_known_pos) {
Vector2 new_pos = latest_known_pos / ui_scale;
return Widget::ObserveMouseMovement(new_pos);
}
}

View File

@@ -4,38 +4,58 @@
using namespace JUI;
void ScrollingRect::RecomputeRenderTarget() {
//
//Rect::PreDraw();
InnerDraw();
//Rect::PostDraw();
}
void ScrollingRect::DrawVerticalScrollbar() {
// TODO: Ratio for size is fine, but the computation for the scrollbar position is just a raw pixel-offset
// when it needs to be a size_ratio of the amount scrolled.
float size_ratio = canvas_height / GetAbsoluteSize().y;
float pos_ratio = scroll / canvas_height;
float travel = (GetAbsoluteSize().y * pos_ratio);
Vector2 scrollbar_base_pos = GetAbsolutePosition() + Vector2(GetAbsoluteSize().x - scrollbar_width, 0);
Vector2 scrollbar_base_size = {scrollbar_width, GetAbsoluteSize().y};
// Draw box for scrollbar to fit into.
J2D::FillRect(Colors::LightGray, scrollbar_base_pos, scrollbar_base_size);
// Compute change in size and position that is appropriate.
Vector2 scrollbar_pos = scrollbar_base_pos + Vector2(0, travel);
Vector2 scrollbar_size = {scrollbar_base_size.x, (scrollbar_base_size.y / size_ratio)};
J2D::FillRoundedRect(scrollbar_color, scrollbar_pos, scrollbar_size, 4);
// Draw little square box at the point where the vertical and horizontal scrollbars would meet.
//Vector2 lil_box_size = {scrollbar_width, scrollbar_width};
//Vector2 lil_box_pos = GetAbsolutePosition() + GetAbsoluteSize() - lil_box_size;
//J2D::FillRect(Colors::DarkGray, lil_box_pos, lil_box_size);
}
void ScrollingRect::Draw() {
// TODO: When `scroll` is 0, we are at the **bottom** of the render target.
// TODO: Flip this around so 0 is at the top
// TODO: Also flip the scroll-bar around.
// TODO: Only recompute when something has changed.
// Q: How do we know when something has changed?
RecomputeRenderTarget();
//J2D::DrawRenderTarget(canvas, GetAbsolutePosition());
J2D::DrawPartialRenderTarget(canvas, GetAbsolutePosition(), {0, 0}, Vector2(GetAbsoluteSize().x, GetAbsoluteSize().y));
bool canvas_larger_than_widget = scroll_size > GetAbsoluteSize().y;
float ratio = scroll_size / GetAbsoluteSize().y;
bool canvas_larger_than_widget = canvas_height > GetAbsoluteSize().y;
if (vertical_scrollbar_enabled && canvas_larger_than_widget)
{
//std::cout << "Ratio " << ratio << std::endl;
Vector2 vscroll_pos = GetAbsolutePosition() + Vector2(GetAbsoluteSize().x-scrollbar_width, 0);
Vector2 vscroll_size = {scrollbar_width, GetAbsoluteSize().y-scrollbar_width};
J2D::FillRect(Colors::LightGray, vscroll_pos, vscroll_size);
J2D::FillRoundedRect(scrollbar_color, {vscroll_pos.x, vscroll_pos.y + scroll}, {vscroll_size.x, (vscroll_size.y / ratio)}, 4);
// Draw little square box at the point where the vertical and horizontal scrollbars would meet.
Vector2 lil_box_size = {scrollbar_width, scrollbar_width};
Vector2 lil_box_pos = GetAbsolutePosition() + GetAbsoluteSize() - lil_box_size;
J2D::FillRect(Colors::DarkGray, lil_box_pos, lil_box_size);
if (vertical_scrollbar_enabled && canvas_larger_than_widget) {
DrawVerticalScrollbar();
}
//J2D::DrawString(Colors::Black, std::format("scroll {}, canvas {}", scroll, canvas_height), GetAbsolutePosition().x, GetAbsolutePosition().y, 1, 12);
}
Vector2 ScrollingRect::CanvasPosition() const {
@@ -55,19 +75,20 @@ void ScrollingRect::InnerDraw() {
// If you work the formulas out, the child widgets will believe their parent's AbsolutePosition is {0,0}.
Position(UDim2::FromPixels(-GetAbsolutePosition().x, -GetAbsolutePosition().y + scroll));
Size({200_percent, 500_percent});
// TODO: Do something with this fake canvas-size?
// TODO: How frequently can we update the RenderTarget size.
Size({200_percent, 500_percent});
Rect::InnerDraw();
DrawChildWidgets();
J2D::DrawPoint(Colors::Blue, {1,1}, 2);
J2D::DrawPoint(Colors::Blue, Vector2(GetAbsoluteSize()), 2);
//J2D::DrawPoint(Colors::Blue, {1,1}, 2);
//J2D::DrawPoint(Colors::Blue, Vector2(GetAbsoluteSize()), 2);
// Set our position back once we're done.
Size(saved_size);
Position(saved_pos);
J2D::End();
}
const Vector2i default_initialize_canvas_size {1024, 4096};
@@ -75,13 +96,90 @@ const Vector2i default_initialize_canvas_size {1024, 4096};
ScrollingRect::~ScrollingRect() { delete canvas; }
ScrollingRect::ScrollingRect() : canvas(new RenderTarget(default_initialize_canvas_size)) {
bool success = canvas->SetMSAAEnabled(JGL::MSAA_SAMPLE_RATE::MSAA_8X);
Name("ScrollingRect");
//bool success = canvas->SetMSAAEnabled(JGL::SampleRate::X8);
}
ScrollingRect::ScrollingRect(Widget *parent) : Rect(parent), canvas(new RenderTarget(default_initialize_canvas_size)) {
bool success = canvas->SetMSAAEnabled(JGL::MSAA_SAMPLE_RATE::MSAA_8X);
//bool success = canvas->SetMSAAEnabled(JGL::MSAA_SAMPLE_RATE::MSAA_8X);
}
JGL::RenderTarget *ScrollingRect::GetCanvas() { return canvas; }
Vector2i ScrollingRect::CanvasSize() const { return canvas->GetDimensions(); }
void ScrollingRect::Update(float delta) {
//scroll += delta*5;
//canvas->Resize(Vector2i(GetAbsoluteSize().x, GetAbsoluteSize().y));
Rect::Update(delta);
}
bool ScrollingRect::ObserveKeyInput(Key key, bool pressed) {
if (Widget::ObserveKeyInput(key, pressed))
return true;
// TODO: Forego this in favor of mouse scrolling and clicking.
if (key == Keys::UpArrow && pressed)
{
Scroll(-scroll_amount);
return true;
}
if (key == Keys::DownArrow && pressed)
{
Scroll(scroll_amount);
return true;
}
if (key == Keys::PageDown && pressed) {
ScrollToBottom();
return true;
}
if (key == Keys::PageUp && pressed) {
ScrollToTop();
return true;
}
return false;
}
bool ScrollingRect::CanScroll() const {
return canvas_height > GetAbsoluteSize().y;
}
void ScrollingRect::SetScrollAmount(float value) {
scroll = Math::Clamp(value, ScrollMinimum(), ScrollMaximum());
}
float ScrollingRect::Scroll(float amount) {
SetScrollAmount(scroll + amount);
return scroll;
}
float ScrollingRect::ScrollMaximum() const {
return Math::Max(0.f, canvas_height-GetAbsoluteSize().y);
}
void ScrollingRect::ScrollToTop() {
SetScrollAmount(ScrollMinimum());
}
void ScrollingRect::ScrollToBottom() {
SetScrollAmount(ScrollMaximum());
}
float ScrollingRect::ScrollMinimum() const { return 0; }
float ScrollingRect::GetScrollAmount() const { return scroll; }
bool ScrollingRect::ObserveMouseWheel(int mwheel) {
if (Widget::ObserveMouseWheel(mwheel))
return true;
if (IsMouseInside()) {
Scroll(mwheel*scroll_amount);
return true;
}
return false;
}

View File

@@ -4,7 +4,7 @@
namespace JUI
{
Slider::Slider(JUI::Widget *parent)
Slider::Slider(JUI::Widget *parent) : Slider()
{
this->Parent(parent);
}
@@ -115,4 +115,14 @@ namespace JUI
void Slider::ScrubberColor(const Color4 &color) { scrubber_color = color;}
void Slider::ScrubberWidth(float width) { scrubber_width = width;}
Slider::Slider() {
Name("Slider");
}
void Slider::SetDragging(bool value) {
dragging = value;
}
bool Slider::Dragging() const { return dragging; }
}

View File

@@ -2,7 +2,9 @@
namespace JUI {
Text::Text() : Widget(), TextBase() {}
Text::Text() : Widget(), TextBase() {
Name("Text");
}
Text::Text(Widget* parent) : Text()
{
@@ -24,12 +26,14 @@ namespace JUI {
//return {0, 0};
}
void Text::Draw()
{
void Text::Draw() {
auto abs_pos = this->GetAbsolutePosition();
auto abs_size = this->GetAbsoluteSize();
TextBase::Draw(abs_pos, abs_size);
auto pos_pad = GetAbsolutePaddingTopLeft();
auto size_pad = GetAbsolutePaddingBottomRight();
TextBase::Draw(abs_pos+pos_pad, abs_size-size_pad);
Widget::Draw();
}

View File

@@ -2,7 +2,9 @@
namespace JUI
{
TextButton::TextButton() : Button(), TextBase() {}
TextButton::TextButton() : Button(), TextBase() {
Name("TextButton");
}
TextButton::TextButton(Widget* parent) : TextButton() {
this->Parent(parent);
}

View File

@@ -2,13 +2,15 @@
#include <ReWindow/InputService.h>
namespace JUI {
TextInputForm::TextInputForm() : TextRect(), Clickable() { }
TextInputForm::TextInputForm() : TextRect(), Clickable() {
Name("TextInputForm");
}
TextInputForm::TextInputForm(Widget* parent) : TextInputForm() {
this->Parent(parent);
}
void TextInputForm::ObserveMouseMovement(const Vector2 &latest_known_pos) {
Widget::ObserveMouseMovement(latest_known_pos);
bool TextInputForm::ObserveMouseMovement(const Vector2 &latest_known_pos) {
return Widget::ObserveMouseMovement(latest_known_pos);
}
std::string TextInputForm::GetAutocompleteText() const { return autocomplete_text;}
@@ -27,22 +29,24 @@ namespace JUI {
void TextInputForm::SetAutocompleteTextEnabled(bool enabled) { autocomplete_text_enabled = enabled; }
void TextInputForm::ObserveMouseInput(MouseButton btn, bool pressed) {
Widget::ObserveMouseInput(btn, pressed);
bool TextInputForm::ObserveMouseInput(MouseButton btn, bool pressed) {
if (pressed && btn == MouseButton::Left) {
if (IsMouseInside()) {
if (!focused) {
OnSelect.Invoke();
}
focused = true;
}
else {
return true;
} else {
if (focused)
OnDeselect.Invoke();
focused = false;
}
}
return Widget::ObserveMouseInput(btn, pressed);
}
void TextInputForm::InnerDraw()
@@ -52,8 +56,6 @@ namespace JUI {
Vector2 abs_pos = this->GetAbsolutePosition();
Vector2 abs_size = this->GetAbsoluteSize();
// TODO: Apply this everywhere..
Vector2 pad_shifts_pos_by = {
pad_left.Pixels + (pad_left.Scale * abs_size.x),
@@ -71,7 +73,7 @@ namespace JUI {
if (!focused || !hide_autocomplete_on_select)
TextBase::Draw(pos, size, autocomplete_text, this->text_size, autocomplete_color);
TextBase::Draw(pos, size);
TextBase::Draw(pos, size, content, text_size, text_color);
//TextRect::InnerDraw();
}
@@ -113,96 +115,107 @@ namespace JUI {
return s;
}
void TextInputForm::MoveCursorLeft() {
if (cursor_position > 0)
cursor_position--;
}
void TextInputForm::MoveCursorRight() {
if (cursor_position < CursorMaxPosition()-1)
cursor_position++;
}
void TextInputForm::ObserveKeyInput(Key key, bool pressed) {
unsigned int TextInputForm::CursorMaxPosition() const {
return input_buffer.length();
}
void TextInputForm::SetCursorPosition(unsigned int pos) {
pos = Math::Clamp<unsigned int>(pos, 0, CursorMaxPosition());
cursor_position = pos;
}
void TextInputForm::SendInput(bool clear_input) {
OnReturn.Invoke(input_buffer);
if (clear_input) {
input_buffer = "";
cursor_position = 0;
}
}
/// Returns true if the given key is a 'special' key, which should not be typed into the buffer, and has no other special function.
bool IsNonAlphanumerosymbolic(const Key& key) {
return key == Keys::LeftShift || key == Keys::RightShift || key == Keys::LeftControl || key == Keys::RightControl
|| key == Keys::LeftAlt || key == Keys::RightAlt || key == Keys::Super || key == Keys::Escape
|| key == Keys::F1 || key == Keys::F2 || key == Keys::F3 || key == Keys::F4 || key == Keys::F5 || key == Keys::F6
|| key == Keys::F7 || key == Keys::F8 || key == Keys::F9 || key == Keys::F10 || key == Keys::F11 || key == Keys::F12;
}
void TextInputForm::Copy() {}
void TextInputForm::Paste() {}
void TextInputForm::Cut() {}
void TextInputForm::Delete() { }
void TextInputForm::Backspace() {
if (cursor_position > 0) {
input_buffer = input_buffer.erase(cursor_position-1, 1);
cursor_position--;
}
}
void TextInputForm::PushStringToCurrentPlaceInInputBuffer(const std::string& snippet) {
input_buffer = input_buffer.insert(cursor_position, snippet);
cursor_position += snippet.length();
}
void TextInputForm::PushKeyToCurrentPlaceInInputBuffer(const Key& key) {
std::string insertion = lowercase(key.Mnemonic);
if (InputService::IsKeyDown(Keys::LeftShift) || InputService::IsKeyDown(Keys::RightShift)) {
insertion = uppercase(insertion);
// characters that don't work with std::toupper, so we do it manually.
if (key == Keys::One) insertion = "!";
if (key == Keys::Two) insertion = "@";
if (key == Keys::Three) insertion = "#";
if (key == Keys::Four) insertion = "$";
if (key == Keys::Five) insertion = "%";
if (key == Keys::Six) insertion = "&";
if (key == Keys::Seven) insertion = "'";
if (key == Keys::Eight) insertion = "*";
if (key == Keys::Nine) insertion = "(";
if (key == Keys::Zero) insertion = ")";
}
PushStringToCurrentPlaceInInputBuffer(insertion);
}
bool TextInputForm::ObserveKeyInput(Key key, bool pressed) {
Widget::ObserveKeyInput(key, pressed);
if (!pressed)
return;
if (!focused)
return;
// TODO: Text Selection
// TODO: Support Copy
// TODO: Support Paste
// TODO: Support Cut
// TODO: Support insert at cursor
// TODO: Simulate key repeat.
if (!pressed || !focused) return false;
if (key == Keys::Return || key == Keys::NumPadReturn) {
OnReturn.Invoke(input_buffer);
if (clear_text_on_return) {
input_buffer = "";
cursor_position = 0;
}
return;
SendInput(clear_text_on_return);
return true;
}
if (key == Keys::LeftArrow) {
if (cursor_position > 0)
cursor_position -= 1;
return;
MoveCursorLeft();
return true;
}
if (key == Keys::RightArrow) {
if (cursor_position < input_buffer.length() - 1)
cursor_position += 1;
return;
MoveCursorRight();
return true;
}
if (InputService::IsKeyDown(Keys::LeftControl)) {
if (key == Keys::C) {
// TODO: Implement Copy
return;
}
if (key == Keys::V) {
// TODO: Implement Paste
return;
}
if (key == Keys::X) {
// TODO: Implement Cut
cursor_position = 0; // TODO: Set to size of pasted content.
return;
}
//input_buffer = input_buffer.insert(cursor_position, uppercase( key.Mnemonic));
//return;
if (key == Keys::C) { Copy(); return true; }
if (key == Keys::V) { Paste(); return true; }
if (key == Keys::X) { Cut(); return true; }
}
if (key == Keys::LeftShift || key == Keys::RightShift || key == Keys::LeftControl || key == Keys::RightControl
|| key == Keys::LeftAlt || key == Keys::RightAlt || key == Keys::Super || key == Keys::Escape
|| key == Keys::F1 || key == Keys::F2 || key == Keys::F3 || key == Keys::F4 || key == Keys::F5 || key == Keys::F6
|| key == Keys::F7 || key == Keys::F8 || key == Keys::F9 || key == Keys::F10 || key == Keys::F11 || key == Keys::F12) {
return;
}
if (key == Keys::Backspace) {
if (cursor_position > 0) {
input_buffer = input_buffer.erase(cursor_position-1, 1);
//input_buffer = input_buffer.substr(0, input_buffer.length()-1);
cursor_position--;
}
} else {
if (InputService::IsKeyDown(Keys::LeftShift) || InputService::IsKeyDown(Keys::RightShift)) {
input_buffer = input_buffer.insert(cursor_position, uppercase( key.Mnemonic));
cursor_position++;
} else {
input_buffer = input_buffer.insert(cursor_position, lowercase( key.Mnemonic));
cursor_position++;
}
//SetContent(GetContent() + key.Mnemonic);
}
if (IsNonAlphanumerosymbolic(key)) return false;
if (blacklist.contains(key.Mnemonic)) return false;
if (key == Keys::Backspace) {Backspace(); return true; }
// No other condition met, so we assume we should push this key into the input buffer.
PushKeyToCurrentPlaceInInputBuffer(key);
return true;
}
bool TextInputForm::HasFocus() const { return focused; }
@@ -213,4 +226,28 @@ namespace JUI {
void TextInputForm::DropFocus() { SetFocused(false); }
bool TextInputForm::ClearTextOnReturn() const { return clear_text_on_return; }
void TextInputForm::ClearTextOnReturn(bool value) { clear_text_on_return = value;}
bool TextInputForm::DropFocusOnReturn() const { return drop_focus_on_return;}
void TextInputForm::DropFocusOnReturn(bool value) { drop_focus_on_return = value;}
std::set<std::string> TextInputForm::GetBlacklist() const { return blacklist;}
std::string TextInputForm::InputBuffer() const { return input_buffer;}
void TextInputForm::SetInputBuffer(const std::string &value) { input_buffer = value;}
void TextInputForm::ClearInputBuffer() { SetInputBuffer(""); }
void TextInputForm::SetBlacklist(const std::set<std::string> &value) {
blacklist = value;
}
void TextInputForm::AddToBlacklist(const std::string& value) {
blacklist.insert(value);
}
}

View File

@@ -1,7 +1,9 @@
#include "JUI/Widgets/TextRect.hpp"
namespace JUI {
TextRect::TextRect() : Rect(), TextBase() {}
TextRect::TextRect() : Rect(), TextBase() {
Name("TextRect");
}
TextRect::TextRect(Widget* parent) : TextRect() {
this->Parent(parent);
}
@@ -45,22 +47,14 @@ namespace JUI {
Vector2 abs_pos = this->GetAbsolutePosition();
Vector2 abs_size = this->GetAbsoluteSize();
// TODO: Apply this everywhere..
Vector2 pad_shifts_pos_by = {
pad_left.Pixels + (pad_left.Scale * abs_size.x),
pad_top.Pixels + (pad_left.Scale * abs_size.y)
};
Vector2 pad_shrinks_size_by = {
pad_right.Pixels + (pad_left.Scale * abs_size.x),
pad_bottom.Pixels + (pad_right.Scale * abs_size.y)
};
Vector2 pos_pad = GetAbsolutePaddingTopLeft();
Vector2 size_pad = GetAbsolutePaddingBottomRight();
TextBase::Draw(abs_pos + pad_shifts_pos_by, abs_size - pad_shrinks_size_by*2);
TextBase::Draw(abs_pos + pos_pad, abs_size - size_pad);
}
void TextRect::Draw() {
Rect::Draw();
}

View File

@@ -3,15 +3,15 @@
#include <cstdlib>
namespace JUI
{
namespace JUI {
Window::Window() : Widget(), Clickable(), Hoverable(), RectBase(), Draggable(), Resizable(), Dockable() {
Name("Window");
this->Position({200, 200, 0, 0});
this->Size({400, 200, 0, 0});
min_size = {400, 200};
this->BGColor({64,64,64,255});
this->BorderColor({92,92,192});
this->SetBorderWidth(2);
this->BGColor(Style::Window::OutlineColor);
this->BorderColor(Style::Window::OutlineColor);
this->BorderWidth(Style::Window::OutlineWidth);
this->BorderMode(BorderMode::Middle);
// TODO: Move out of Event callback
@@ -23,13 +23,12 @@ namespace JUI
Viewport = new Rect(this);
Viewport->Name("Viewport");
Viewport->BGColor({64,64,64, 0});
Viewport->Size({0, -20, 1, 1});
Viewport->Position({0, 20, 0, 0});
Viewport->BGColor(Style::Window::ViewportBackgroundColor);
Viewport->Size({0, -titlebar_height, 1, 1});
Viewport->Position({0, titlebar_height, 0, 0});
// TODO: Viewport->AnchorPoint({0.f, 0.f});
Viewport->BorderColor({128, 128, 128, 255});
Viewport->SetBorderWidth(0);
Viewport->BorderWidth(0);
//Viewport->Padding(1_px);
// TODO: Viewport->SetBorderRadius(0);
@@ -37,39 +36,36 @@ namespace JUI
Topbar = new Rect(this);
Topbar->Position({0_px, 0_px});
Topbar->Size({100_percent, UDim(titlebar_height, 0)});
Topbar->BGColor({92,92,192, 255});
//Topbar->BGColor({92,92,192, 255});
Topbar->BGColor(Colors::Transparent);
Topbar->BorderColor({128, 128, 128, 0});
Topbar->SetBorderWidth(0);
Topbar->BorderWidth(0);
TitleLabel = new Text(Topbar);
TitleLabel->Center();
TitleLabel->SetContent(title);
TitleLabel->SetTextSize(title_font_size);
// TODO: Pull out this circle image generation code, make a Circle widget.
// TODO: auto* list = new HorizontalListLayout(Topbar);
// TODO: exit_btn
auto* exb_tex = new Texture({titlebar_height, titlebar_height });
auto* exb_tex = new Texture( Vector2i(titlebar_height, titlebar_height));
auto* exb_rt = new RenderTarget(exb_tex);
if (exb_rt->SetMSAAEnabled(JGL::MSAA_SAMPLE_RATE::MSAA_8X)) {/* using msaa to make the circle nicer. */}
std::array<Color4, 2> exb_circle_colors
{
Color4(168, 28, 28, 255),
Color4(212, 25, 25, 255)
if (exb_rt->SetMSAAEnabled(JGL::SampleRate::X8)) {/* using msaa to make the circle nicer. */}
std::array<Color4, 2> exb_circle_colors {
Colors::White,//Color4(168, 28, 28, 255),
Colors::White//Color4(212, 25, 25, 255)
};
std::array<Vector2, 2> exb_circle_positions
{
std::array<Vector2, 2> exb_circle_positions {
Vector2(exb_rt->GetDimensions()) / 2,
Vector2(exb_rt->GetDimensions()) / 2
};
std::array<float, 2> exb_circle_radii
{
std::array<float, 2> exb_circle_radii {
(float) (exb_rt->GetDimensions().x * 0.33),
(float) (exb_rt->GetDimensions().x * 0.28)
};
@@ -85,23 +81,16 @@ namespace JUI
//exit_btn->Size({30_px, 100_percent});
exit_btn->Size({titlebar_height, titlebar_height, 0, 0});
// exit_btn->BorderColor({128, 128, 128, 255});
exit_btn->BGColor(Colors::Transparent);
exit_btn->BGColors(Colors::Transparent, Colors::Transparent, Colors::Transparent, Colors::Transparent);
exit_btn->BorderWidth(0.f);
exit_btn->BaseImageColor(Colors::Reds::LightCoral);
exit_btn->HoveredImageColor(Colors::Red);
exit_btn->PressedImageColor(Colors::Reds::DarkRed);
exit_btn->BaseBGColor({92,92,192, 255});
exit_btn->BGColor(exit_btn->BaseBGColor());
exit_btn->BorderColors({0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0});
exit_btn->BorderColor({0,0,0,0});
exit_btn->SetBorderWidth(0.f);
//exit_btn_text->SetFont(TitleLabel->GetFont());
//exit_btn->SetTextSize(title_font_size);
//exit_btn->SetTextColor({255, 0, 0});
//exit_btn->Center();
exit_btn->OnClickEvent += [&] (auto... _) {
exit_btn->OnReleaseEvent += [&] (auto... _) {
this->Visible(false);
};
}
Window::Window(Widget* parent) : Window() {
@@ -164,11 +153,17 @@ namespace JUI
this->SetResize(false);
}
void Window::DragTo(const Vector2& pos)
{
this->Position(UDim2{(int) pos.x, (int) pos.y, 0, 0});
}
void Window::Update(float delta) {
if (dragging) {
//jlog::Debug(std::format("mpos {} {}", last_known_mouse_pos.x, last_known_mouse_pos.y));
Vector2 mpos = last_known_mouse_pos - initial_drag_offset;
this->Position(UDim2{(int) mpos.x, (int) mpos.y, 0, 0});
// Move the window with the mouse, accounting for initial offset of window-mouse when we first clicked.
Vector2 goal = last_known_mouse_pos - initial_drag_offset;
DragTo(goal);
}
if (resizing) {
@@ -193,7 +188,6 @@ namespace JUI
float needed_amt_to_resize = min_size.y - abs_size.y;
Size(Size() + UDim2(0, needed_amt_to_resize, 0, 0));
}
}
Widget::Update(delta);
@@ -272,4 +266,40 @@ namespace JUI
AABB2D Window::AbsoluteViewportBounds() const {
return {AbsoluteViewportPosition(), AbsoluteViewportSize()};
}
void Window::UpdateInternalWidgetsTitlebarHeight() {
Viewport->Size({0, -titlebar_height, 1, 1});
Viewport->Position({0, titlebar_height, 0, 0});
Topbar->Size({100_percent, UDim(titlebar_height, 0)});
exit_btn->Size({titlebar_height, titlebar_height, 0, 0});
}
void Window::TitlebarHeight(int height) {
titlebar_height = height;
UpdateInternalWidgetsTitlebarHeight();
}
int Window::TitlebarHeight() const {
return titlebar_height;
}
void Window::CornerRounding(float radius) {
RectBase::CornerRounding(radius);
}
void Window::SetOpen(bool value) {
open = value;
Visible(open);
}
void Window::Toggle() {
SetOpen(!IsOpen());
}
void Window::Close() { SetOpen(false); }
void Window::Open() { SetOpen(true); }
bool Window::IsOpen() const { return open;}
}