30 Commits

Author SHA1 Message Date
c3e372ac29 Update mcolor dependency 2025-04-22 17:23:13 -04:00
fee9f2083c Write TabPageView header 2025-04-18 15:30:15 -04:00
fdaf68b01e Add TabView header 2025-04-18 00:40:52 -04:00
62cfd1bd58 Adjustments to Window and ScrollingRect widget. 2025-04-17 14:13:32 -04:00
b7ab9a8780 Shit 2025-04-12 12:46:38 -05:00
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
36 changed files with 1001 additions and 187 deletions

View File

@@ -26,7 +26,9 @@ if (UNIX)
endif()
if (WIN32)
add_library(JUI STATIC ${JUI_SRC})
add_library(JUI STATIC ${JUI_SRC}
include/JUI/Mixins/DragAndDropReceiver.hpp
include/JUI/Widgets/RadioButtonSet.hpp)
endif()
set_target_properties(JUI PROPERTIES LINKER_LANGUAGE CXX)
@@ -38,7 +40,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-7.2.zip
)
CPMAddPackage(
@@ -58,7 +60,7 @@ CPMAddPackage(
CPMAddPackage(
NAME JGL
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-52.zip
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-55.zip
)
target_include_directories(JUI PUBLIC ${Event_SOURCE_DIR}/include)

View File

@@ -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.

View File

@@ -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,59 @@ 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};

View File

@@ -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;
}

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

@@ -20,6 +20,7 @@ namespace JUI
class Clickable
{
public:
Event<Vector2, MouseButton> OnClickEvent;
Event<Vector2, MouseButton, bool> OnReleaseEvent;
public:

View File

@@ -0,0 +1,8 @@
//
// Created by josh on 4/11/25.
//
#ifndef DRAGANDDROPRECEIVER_HPP
#define DRAGANDDROPRECEIVER_HPP
#endif //DRAGANDDROPRECEIVER_HPP

View File

@@ -0,0 +1,8 @@
//
// Created by josh on 4/11/25.
//
#ifndef DRAGANDDROPSOURCE_HPP
#define DRAGANDDROPSOURCE_HPP
#endif //DRAGANDDROPSOURCE_HPP

View File

@@ -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();

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

@@ -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 {

View File

@@ -0,0 +1,27 @@
#pragma once
#include <JUI/Base/Widget.hpp>
#include <JUI/Widgets/Rect.hpp>
#include <JUI/Widgets/Checkbox.hpp>
#include <JUI/Widgets/TextRect.hpp>
#include <JUI/Widgets/Text.hpp>
#include <JUI/Widgets/ListLayout.hpp>
namespace JUI
{
/// A composite class which combines a Checkbox and TextLabel into one element.
class LabeledCheckbox : public Rect
{
public:
LabeledCheckbox() {
auto* layout = new HorizontalListLayout(this);
}
explicit LabeledCheckbox(Widget* parent);
Checkbox* CheckboxInstance();
TextRect* LabelInstance();
Text* TextInstance();
};
}

View File

@@ -38,6 +38,7 @@ namespace JUI {
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

@@ -1,8 +1,14 @@
//
// Created by dawsh on 11/24/24.
//
#pragma once
#ifndef LISTBOX_HPP
#define LISTBOX_HPP
#endif //LISTBOX_HPP
#include <JUI/Widgets/ScrollingRect.hpp>
namespace JUI {
class ListBox : public ScrollingRect
{
ListBox();
explicit ListBox(Widget* widget);
};
}

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

@@ -0,0 +1,40 @@
#pragma once
#include <JUI/Widgets/Rect.hpp>
#include <JUI/Widgets/RadioButton.hpp>
namespace JUI {
enum class RadioButtonSetMode { Exclusive, Multiple };
/// A rect widget that is specialized to support a set of radio buttons, where one or multiple can be selected at a time.
class RadioButtonSet : public Rect
{
public:
RadioButtonSet()
{
}
explicit RadioButtonSet(Widget* parent) : RadioButtonSet()
{
}
RadioButton* Add(const std::string& label)
{
}
protected:
// TODO: should likely be shared_ptr.
std::unordered_map<std::string, RadioButton*> btns;
};
}

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

@@ -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);

View File

@@ -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();
};

View File

@@ -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

@@ -0,0 +1,61 @@
/// 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
/// @edit 2025-02-14
#pragma once
#include <JUI/Widgets/Rect.hpp>
#include <JUI/Widgets/ListLayout.hpp>
#include <JUI/Widgets/TextButton.hpp>
// TODO: Each "Tab" needs its own content box.
namespace JUI {
class TabPageView : public Rect {
public:
/// Constructs
TabPageView()
{
}
explicit TabPageView(Widget* parent);
/// Adds a tab-page-set to the TabView by creating a default-style TextButton with the specified content.
std::pair<TextButton*, Rect*> AddTabPageSet(const std::string& content);
/// Adds a tab-page-set to the TabView by explicitly specifying the TextButton 'tab' and Rect 'page'.
Widget* AddTabPage(TextButton* tab_element);
int GetActiveTabIndex();
void SetActiveTabIndex(int index);
Rect* GetTabsContainer();
Rect* GetPagesContainer();
HorizontalListLayout* GetTabsListLayout();
TextButton* GetTabByIndex(int index);
Rect* GetPageByIndex(int index);
void NextTab();
void PrevTab();
void FirstTab();
void LastTab();
protected:
std::vector<TextButton*> tabs;
std::vector<Rect*> pages;
Rect* tabs_container;
Rect* page_container;
private:
};
}

View File

@@ -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;

View File

@@ -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,68 @@ 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. v
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);
// Special case to allow drop of "Resizing"
if (btn == MouseButton::Right && pressed == false)
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);
};
}

View File

@@ -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);
};
@@ -202,10 +215,47 @@ JUI::Scene* CreateScene() {
input_form->SetContent("");
input_form->SetTextSize(14);
auto* collapsible = new Collapsible(column_layout);
collapsible->Size({100_percent, 200_px});
auto* radio_btn_set = new Rect(collapsible->ContentBox());
radio_btn_set->Size({100_percent, 20_px});
auto* radio_btn_set_layout = new HorizontalListLayout(radio_btn_set);
radio_btn_set_layout->Padding(2_px);
auto* radio_a_btn = new Checkbox(radio_btn_set_layout);
radio_a_btn->Size({20_px, 20_px});
auto* radio_a_label = new TextRect(radio_btn_set_layout);
radio_a_label->BorderWidth(0);
radio_a_label->Size({20_px, 20_px});
radio_a_label->AutoFitSizeToText(true);
radio_a_label->SetContent("A ");
radio_a_label->SetTextSize(12);
auto* radio_b_btn = new Checkbox(radio_btn_set_layout);
radio_b_btn->Size({20_px, 20_px});
auto* radio_b_label = new TextRect(radio_btn_set_layout);
radio_b_label->BorderWidth(0);
radio_b_label->Size({20_px, 20_px});
radio_b_label->AutoFitSizeToText(true);
radio_b_label->SetContent("B ");
radio_b_label->SetTextSize(12);
auto* radio_c_btn = new Checkbox(radio_btn_set_layout);
radio_c_btn->Size({20_px, 20_px});
auto* radio_c_label = new TextRect(radio_btn_set_layout);
radio_c_label->BorderWidth(0);
radio_c_label->Size({20_px, 20_px});
radio_c_label->AutoFitSizeToText(true);
radio_c_label->SetContent("C ");
radio_c_label->SetTextSize(12);
//auto* separator_a = new Rect(radio_btn_set_layout());
auto* other_window = new JUI::Window(root);
other_window->Position({10_percent, 10_percent});
other_window->Size({30_percent, 25_percent});
@@ -297,8 +347,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 +355,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 +399,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,11 +416,15 @@ 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});
}
};
void inspect_widget(JUI::Widget* w, int depth = 1) {
std::cout << std::setw(depth*4);
std::cout << w->Name() << std::endl;
@@ -377,8 +436,6 @@ void inspect_widget(JUI::Widget* w, int depth = 1) {
}
}
int main() {
using namespace ReWindow;
// TODO: Find out new jlog api for silencing specific loggers.
@@ -409,10 +466,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

@@ -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);
}

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

@@ -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;
}

View File

@@ -0,0 +1,89 @@
#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);
message_log_box->canvas_height += entry->GetAbsoluteSize().y;
}
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

@@ -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;}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -119,4 +119,10 @@ namespace JUI
Slider::Slider() {
Name("Slider");
}
void Slider::SetDragging(bool value) {
dragging = value;
}
bool Slider::Dragging() const { return dragging; }
}

View File

@@ -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; }

View File

@@ -16,6 +16,7 @@ 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),
@@ -27,7 +28,7 @@ namespace JUI {
};
Vector2 size = this->set_font.MeasureString(this->content, this->text_size);
size += pad_shrinks_size_by*2.f;
//size += pad_shrinks_size_by*2.f;
if (abs_size.x < size.x)
this->size.X.Pixels = size.x;

View File

@@ -91,7 +91,6 @@ namespace JUI {
exit_btn->OnReleaseEvent += [&] (auto... _) {
this->Visible(false);
};
}
Window::Window(Widget* parent) : Window() {
@@ -145,20 +144,35 @@ namespace JUI {
void Window::OnRelease(const J3ML::LinearAlgebra::Vector2 &mouse_pos, const JUI::MouseButton &btn,
bool still_hovering) {
// TODO: This ONLY invokes if the mouse is still indeed **hovering**
// Some controls would like to listen in cases where it isn't.
Clickable::OnRelease(mouse_pos, btn, still_hovering);
clicked = false;
if (dragging)
this->SetDrag(false);
if (resizing)
if (resizing) {
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 +197,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);
@@ -198,7 +211,8 @@ namespace JUI {
OnClick(last_known_mouse_pos, mbtn);
}
if (IsClicked() && !mb_state)
if (prev_mb_state && !mb_state)
{
OnRelease(last_known_mouse_pos, mbtn, IsHovered());
}
@@ -211,6 +225,9 @@ namespace JUI {
return;
RectBase::Draw(this->GetAbsolutePosition(), this->GetAbsoluteSize());
Widget::Draw();
J2D::DrawString(Colors::Black, std::format("hover: {}", hovered), this->GetAbsolutePosition().x, this->GetAbsolutePosition().y, 1.f, 10);
J2D::DrawString(Colors::Black, std::format("mbutton: {}", mb_state), this->GetAbsolutePosition().x, this->GetAbsolutePosition().y+10, 1.f, 10);
}
void Window::SetTitle(const std::string &title) {
@@ -279,4 +296,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;}
}