Compare commits
25 Commits
Prerelease
...
Prerelease
Author | SHA1 | Date | |
---|---|---|---|
8d90f990f0 | |||
5ccbb84e55 | |||
74ae05321b | |||
906bde163a | |||
d0fcf9cce2 | |||
2e0ce74753 | |||
47a7b19648 | |||
80c28921b1 | |||
6166984daa | |||
8589431cc7 | |||
1092ef2b38 | |||
1a11fa824b | |||
1bd11c5235 | |||
956183bade | |||
86d01099e2 | |||
90272e35f4 | |||
e2528f54fb | |||
fffcc7f12d | |||
b3f65b3396 | |||
4193a2a693 | |||
ee0e92d826 | |||
86aa41a3db | |||
3486ee03d7 | |||
34257eb256 | |||
c4f88c9021 |
@@ -38,7 +38,7 @@ CPMAddPackage(
|
||||
|
||||
CPMAddPackage(
|
||||
NAME mcolor
|
||||
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-6.zip
|
||||
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-6.2.zip
|
||||
)
|
||||
|
||||
CPMAddPackage(
|
||||
|
@@ -17,8 +17,7 @@
|
||||
|
||||
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 object is complex, stateful, and manages resources. Do not use this as a general-purpose texture data type.
|
||||
|
@@ -34,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++".
|
||||
@@ -44,6 +43,7 @@ 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;
|
||||
@@ -61,10 +61,9 @@ namespace JUI {
|
||||
Event<Widget *> ChildRemoved;
|
||||
/// This event triggers right before this widget gets deallocated.
|
||||
Event<Widget *> Destroying;
|
||||
#pragma endregion
|
||||
public:
|
||||
Tween* TweenPosition(const UDim2& goal, TweenInfo params = {});
|
||||
Tween* TweenSize(const UDim2& goal, TweenInfo params = {});
|
||||
|
||||
#pragma region Hierarchy
|
||||
/// Adds a given widget to this widget's list of children.
|
||||
/// @return The widget in question.
|
||||
Widget* Add(Widget* newChild);
|
||||
@@ -95,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;
|
||||
@@ -125,17 +142,13 @@ 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;
|
||||
|
||||
///
|
||||
@@ -144,6 +157,8 @@ namespace JUI {
|
||||
///
|
||||
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.
|
||||
@@ -245,6 +260,7 @@ namespace JUI {
|
||||
|
||||
#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().
|
||||
@@ -264,9 +280,7 @@ 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;
|
||||
@@ -274,6 +288,7 @@ namespace JUI {
|
||||
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.
|
||||
@@ -284,49 +299,58 @@ namespace JUI {
|
||||
|
||||
void SetViewportSize(const Vector2& vps);
|
||||
|
||||
float ComputeElementPadding(float size, const UDim& padding) {
|
||||
return padding.Pixels + (padding.Scale * size);
|
||||
}
|
||||
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;
|
||||
Vector2 last_known_mouse_pos = {0,0};
|
||||
|
@@ -56,7 +56,8 @@ namespace JUI {
|
||||
const Vector2 DefaultMinimumSize = {200, 200};
|
||||
const int TitlebarHeight = 20;
|
||||
const Color4 OutlineColor = {92, 92, 192};
|
||||
const Color4 ViewportBackgroundColor = Colors::White;
|
||||
const Color4 UnfocusedOutlineColor = Colors::Gray;
|
||||
const Color4 ViewportBackgroundColor = Colors::DarkGray;
|
||||
const int OutlineWidth = 2;
|
||||
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -85,13 +85,14 @@ namespace JUI
|
||||
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, TweenInfo info) {
|
||||
Tween(TweenTickFunc tick_func, const TweenInfo& info) {
|
||||
this->tick_func = tick_func;
|
||||
|
||||
this->time = info.time;
|
||||
@@ -99,6 +100,7 @@ namespace JUI
|
||||
this->repeat_count = info.repeats;
|
||||
this->reverses = info.reverse;
|
||||
this->easing_func = info.easing;
|
||||
this->overwritable = info.overwritable;
|
||||
}
|
||||
Event<> Completed;
|
||||
|
||||
@@ -120,6 +122,7 @@ namespace JUI
|
||||
bool alive = true;
|
||||
bool paused = false;
|
||||
bool completed = false;
|
||||
bool overwritable = true;
|
||||
|
||||
|
||||
void Cancel();
|
||||
|
25
include/JUI/Widgets/BindMenu.h
Normal file
25
include/JUI/Widgets/BindMenu.h
Normal 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);
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
@@ -19,8 +19,7 @@
|
||||
#include <JUI/Base/ImageBase.hpp>
|
||||
#include <JUI/Widgets/ImageButton.hpp>
|
||||
|
||||
namespace JUI
|
||||
{
|
||||
namespace JUI {
|
||||
// TODO: Find a nice way to implement a checkmark.
|
||||
|
||||
class CheckboxBase {
|
||||
|
10
include/JUI/Widgets/CheckboxLabel.hpp
Normal file
10
include/JUI/Widgets/CheckboxLabel.hpp
Normal 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
|
||||
{
|
||||
|
||||
};
|
||||
}
|
@@ -38,6 +38,7 @@ namespace JUI {
|
||||
bool collapsed = false;
|
||||
UDim2 saved_size;
|
||||
UDim header_height = Style::Collapsible::HeaderHeight;
|
||||
Tween* resize_anim = nullptr;
|
||||
};
|
||||
|
||||
|
||||
|
78
include/JUI/Widgets/CommandLine.hpp
Normal file
78
include/JUI/Widgets/CommandLine.hpp
Normal 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:
|
||||
};
|
||||
|
||||
}
|
@@ -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;
|
||||
};
|
||||
}
|
@@ -31,11 +31,11 @@
|
||||
& &
|
||||
*/
|
||||
|
||||
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;
|
||||
|
@@ -32,11 +32,7 @@ namespace JUI
|
||||
|
||||
[[nodiscard]] Vector2 GetAbsoluteSize() const override;
|
||||
|
||||
void ObserveMouseMovement(const J3ML::LinearAlgebra::Vector2 &latest_known_pos) override
|
||||
{
|
||||
Vector2 new_pos = latest_known_pos / ui_scale;
|
||||
Widget::ObserveMouseMovement(new_pos);
|
||||
}
|
||||
bool ObserveMouseMovement(const Vector2 &latest_known_pos) override;
|
||||
|
||||
protected:
|
||||
Vector2 ui_scale = Vector2(1, 1);
|
||||
|
@@ -19,25 +19,18 @@ namespace JUI {
|
||||
// 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; }
|
||||
@@ -48,29 +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();
|
||||
};
|
@@ -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:
|
||||
|
@@ -63,9 +63,9 @@ namespace JUI {
|
||||
|
||||
void PushKeyToCurrentPlaceInInputBuffer(const Key &key);
|
||||
|
||||
void ObserveKeyInput(Key key, bool pressed) override;
|
||||
void ObserveMouseInput(MouseButton btn, bool pressed) override;
|
||||
void ObserveMouseMovement(const Vector2 &latest_known_pos) override;
|
||||
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;
|
||||
|
@@ -25,16 +25,14 @@
|
||||
#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();
|
||||
@@ -57,6 +55,19 @@ namespace JUI
|
||||
// TODO: Decide if this will auto-scale with the titlebar's text height, or the other way around.
|
||||
[[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.
|
||||
@@ -66,6 +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.
|
||||
@@ -86,9 +98,7 @@ namespace JUI
|
||||
|
||||
void SetResizable(bool value);
|
||||
|
||||
void CornerRounding(float radius) override {
|
||||
RectBase::CornerRounding(radius);
|
||||
}
|
||||
void CornerRounding(float radius) override;
|
||||
|
||||
/// Returns a pointer to the Text Widget that is used to render the title bar's text.
|
||||
Text* TitleInstance();
|
||||
@@ -116,24 +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:
|
||||
void UpdateInternalWidgetsTitlebarHeight();
|
||||
protected:
|
||||
|
||||
JUI::Rect* Topbar;
|
||||
JUI::Rect* Viewport;
|
||||
JUI::Text* TitleLabel;
|
||||
JUI::ImageButton* exit_btn;
|
||||
JUI::ImageButton* fs_btn;
|
||||
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 = 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);
|
||||
};
|
||||
}
|
||||
|
40
main.cpp
40
main.cpp
@@ -28,6 +28,7 @@
|
||||
#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;
|
||||
@@ -35,6 +36,7 @@ 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();
|
||||
@@ -45,7 +47,6 @@ JUI::Scene* CreateScene() {
|
||||
|
||||
auto *root = new Scene();
|
||||
|
||||
|
||||
/*root->DescendantAdded += [&] (auto* node) {
|
||||
widget_count->SetContent("Widgets: " + count_descendants(root));
|
||||
};
|
||||
@@ -62,6 +63,11 @@ JUI::Scene* CreateScene() {
|
||||
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);
|
||||
auto* file = topbar->AddButton("Demos");
|
||||
@@ -82,7 +88,14 @@ 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->AddItem("");
|
||||
|
||||
|
||||
auto* open_console = ctx_menu->AddItem("Command Line");
|
||||
open_console->OnClickEvent += [&] (Vector2 pos, JUI::MouseButton btn)mutable {
|
||||
console->Open();
|
||||
};
|
||||
|
||||
ctx_menu->ZIndex(3);
|
||||
};
|
||||
|
||||
@@ -297,8 +310,7 @@ public:
|
||||
|
||||
scene->Update(elapsed);
|
||||
|
||||
if (accum > 1.f)
|
||||
{
|
||||
if (accum > 1.f) {
|
||||
iter--;
|
||||
accum = 0.f;
|
||||
auto* text = new JUI::TextRect(list);
|
||||
@@ -306,7 +318,8 @@ public:
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -349,9 +362,14 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -361,6 +379,11 @@ public:
|
||||
|
||||
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});
|
||||
}
|
||||
};
|
||||
@@ -409,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'
|
||||
|
@@ -270,9 +270,14 @@ void Widget::UpdateTweens(float elapsed) {
|
||||
t->Update(elapsed);
|
||||
|
||||
// TODO: Remove tweens if "dead"
|
||||
std::ranges::remove_if(tweens.begin(), tweens.end(), [](Tween* t){
|
||||
return t->HasCompleted();
|
||||
});
|
||||
|
||||
tweens.erase(std::remove_if(tweens.begin(), tweens.end(), [](Tween* t){
|
||||
if (t->HasCompleted())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}), tweens.end());
|
||||
}
|
||||
|
||||
void Widget::Update(float delta) {
|
||||
@@ -317,26 +322,43 @@ bool Widget::IsMouseInside() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Widget::ObserveMouseInput(MouseButton btn, bool pressed) {
|
||||
bool Widget::ObserveMouseInput(MouseButton btn, bool pressed) {
|
||||
mbtn = btn;
|
||||
mb_state = pressed;
|
||||
|
||||
for (Widget* child : children)
|
||||
child->ObserveMouseInput(btn, pressed);
|
||||
for (Widget* child : children) {
|
||||
if (child->ObserveMouseInput(btn, pressed))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Widget::ObserveMouseMovement(const Vector2 &latest_known_pos) {
|
||||
bool Widget::ObserveMouseMovement(const Vector2 &latest_known_pos) {
|
||||
last_known_mouse_pos = latest_known_pos;
|
||||
|
||||
for (Widget* child: children)
|
||||
child->ObserveMouseMovement(latest_known_pos);
|
||||
if (child->ObserveMouseMovement(latest_known_pos))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Widget::ObserveKeyInput(Key key, bool pressed) {
|
||||
bool Widget::ObserveKeyInput(Key key, bool pressed) {
|
||||
for (Widget* child : children)
|
||||
child->ObserveKeyInput(key, pressed);
|
||||
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;
|
||||
}
|
||||
|
||||
bool Widget::IsAncestorOf(Widget *descendant) const {
|
||||
if (descendant == nullptr)
|
||||
@@ -587,12 +609,24 @@ Tween* Widget::TweenMarginBottom(const JUI::UDim& goal, JUI::TweenInfo info) {
|
||||
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(start);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -34,17 +34,31 @@ namespace JUI {
|
||||
}
|
||||
|
||||
void Collapsible::Collapse() {
|
||||
//Size(saved_size);
|
||||
lil_arrow->SetContent("\\/");
|
||||
content_box->Visible(false);
|
||||
saved_size = Size();
|
||||
TweenSize(UDim2(saved_size.X, header_height));
|
||||
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);
|
||||
TweenSize(saved_size);
|
||||
if (resize_anim != nullptr && !resize_anim->HasCompleted())
|
||||
resize_anim->Cancel();
|
||||
|
||||
|
||||
resize_anim = TweenSize(saved_size, {
|
||||
.time = 0.25f
|
||||
});
|
||||
collapsed = false;
|
||||
}
|
||||
|
||||
|
87
src/JUI/Widgets/CommandLine.cpp
Normal file
87
src/JUI/Widgets/CommandLine.cpp
Normal 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("");
|
||||
}
|
||||
}
|
@@ -18,6 +18,8 @@ namespace JUI
|
||||
|
||||
void VerticalListLayout::ApplyLayout() {
|
||||
|
||||
content_height = 0;
|
||||
|
||||
std::sort(children.begin(), children.end(), layoutOrderSort);
|
||||
|
||||
if (layout == LayoutOrder::V::TOP)
|
||||
@@ -28,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)
|
||||
@@ -41,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +73,10 @@ namespace JUI
|
||||
|
||||
LayoutOrder::V VerticalListLayout::LayoutOrder() const { return layout;}
|
||||
|
||||
float VerticalListLayout::CurrentContentHeight() const {
|
||||
return content_height;
|
||||
}
|
||||
|
||||
HorizontalListLayout::HorizontalListLayout() : ListLayout() {}
|
||||
|
||||
|
||||
@@ -73,10 +95,17 @@ namespace JUI
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,4 +135,6 @@ namespace JUI
|
||||
LayoutOrder::H HorizontalListLayout::LayoutOrder() const {
|
||||
return layout;
|
||||
}
|
||||
|
||||
float HorizontalListLayout::CurrentContentWidth() const { return content_width;}
|
||||
}
|
@@ -26,6 +26,11 @@ namespace JUI {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -7,28 +7,55 @@ void ScrollingRect::RecomputeRenderTarget() {
|
||||
InnerDraw();
|
||||
}
|
||||
|
||||
|
||||
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::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) {
|
||||
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);
|
||||
DrawVerticalScrollbar();
|
||||
}
|
||||
//J2D::DrawString(Colors::Black, std::format("scroll {}, canvas {}", scroll, canvas_height), GetAbsolutePosition().x, GetAbsolutePosition().y, 1, 12);
|
||||
}
|
||||
|
||||
Vector2 ScrollingRect::CanvasPosition() const {
|
||||
@@ -48,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};
|
||||
@@ -79,3 +107,79 @@ ScrollingRect::ScrollingRect(Widget *parent) : Rect(parent), canvas(new RenderTa
|
||||
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;
|
||||
}
|
||||
|
@@ -119,4 +119,10 @@ namespace JUI
|
||||
Slider::Slider() {
|
||||
Name("Slider");
|
||||
}
|
||||
|
||||
void Slider::SetDragging(bool value) {
|
||||
dragging = value;
|
||||
}
|
||||
|
||||
bool Slider::Dragging() const { return dragging; }
|
||||
}
|
@@ -9,8 +9,8 @@ namespace JUI {
|
||||
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;}
|
||||
@@ -29,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()
|
||||
@@ -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, content, text_size, autocomplete_color);
|
||||
TextBase::Draw(pos, size, content, text_size, text_color);
|
||||
|
||||
//TextRect::InnerDraw();
|
||||
}
|
||||
@@ -182,23 +184,38 @@ namespace JUI {
|
||||
PushStringToCurrentPlaceInInputBuffer(insertion);
|
||||
}
|
||||
|
||||
void TextInputForm::ObserveKeyInput(Key key, bool pressed) {
|
||||
bool TextInputForm::ObserveKeyInput(Key key, bool pressed) {
|
||||
Widget::ObserveKeyInput(key, pressed);
|
||||
if (!pressed || !focused) return;
|
||||
if (key == Keys::Return || key == Keys::NumPadReturn)
|
||||
return SendInput(clear_text_on_return);
|
||||
if (key == Keys::LeftArrow) return MoveCursorLeft();
|
||||
if (key == Keys::RightArrow) return MoveCursorRight();
|
||||
if (InputService::IsKeyDown(Keys::LeftControl)) {
|
||||
if (key == Keys::C) return Copy();
|
||||
if (key == Keys::V) return Paste();
|
||||
if (key == Keys::X) return Cut();
|
||||
if (!pressed || !focused) return false;
|
||||
|
||||
if (key == Keys::Return || key == Keys::NumPadReturn) {
|
||||
SendInput(clear_text_on_return);
|
||||
return true;
|
||||
}
|
||||
if (IsNonAlphanumerosymbolic(key)) return;
|
||||
if (blacklist.contains(key.Mnemonic)) return;
|
||||
if (key == Keys::Backspace) return Backspace();
|
||||
|
||||
if (key == Keys::LeftArrow) {
|
||||
MoveCursorLeft();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == Keys::RightArrow) {
|
||||
MoveCursorRight();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (InputService::IsKeyDown(Keys::LeftControl)) {
|
||||
if (key == Keys::C) { Copy(); return true; }
|
||||
if (key == Keys::V) { Paste(); return true; }
|
||||
if (key == Keys::X) { Cut(); return true; }
|
||||
}
|
||||
|
||||
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; }
|
||||
|
@@ -91,7 +91,6 @@ namespace JUI {
|
||||
exit_btn->OnReleaseEvent += [&] (auto... _) {
|
||||
this->Visible(false);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Window::Window(Widget* parent) : Window() {
|
||||
@@ -154,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) {
|
||||
@@ -183,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);
|
||||
@@ -279,4 +283,23 @@ namespace JUI {
|
||||
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;}
|
||||
}
|
Reference in New Issue
Block a user