Compare commits
18 Commits
Prerelease
...
master
Author | SHA1 | Date | |
---|---|---|---|
c8946673c3 | |||
d0a246bffe | |||
e084e1120f | |||
238a7934b3 | |||
558f17fc94 | |||
4acd91b078 | |||
944bd66b43 | |||
d935608e05 | |||
3b973c2c45 | |||
ce0c69190a | |||
dca15511ea | |||
beb5c97c13 | |||
505c2c70e6 | |||
9b07a8ea01 | |||
393ad1b2b3 | |||
67416540d4 | |||
d9d92c2a28 | |||
086a3f226b |
143
docs/widgets.md
143
docs/widgets.md
@@ -1,12 +1,81 @@
|
||||
<details>
|
||||
<summary>Widget</summary>
|
||||
This is some information about the Widget class.
|
||||
## JUI Widget Directory
|
||||
Updated on 2025-06-25 / Release-6.4 [^1]
|
||||
|
||||
See also:
|
||||
|
||||
[Animation](animation.md) <br/>
|
||||
[Layout](layout.md) <br/>
|
||||
[Overview](overview.md) <br/>
|
||||
[Styling](styling.md) <br/>
|
||||
[Full API Reference](https://doc.redacted.cc/jui) <br/>
|
||||
[Repository](https://git.redacted.cc/josh/ReJUI)
|
||||
|
||||
<details><summary>Widget</summary>
|
||||
|
||||
* The [Widget](doc.redacted.cc/jui/) is the basis of all GUI elements in JUI.
|
||||
* It is an abstract class, and you should not directly instantiate `Widget` for use in your UI.
|
||||
* Instead, create its concrete derived types (`Button`, `Text`, `Slider`) or your own custom derivation.
|
||||
* `class Widget` serves as the starting point for creating custom widgets.
|
||||
* Widgets are organized in a tree-like hierarchy, where each widget can have multiple **children** and a single **parent**.
|
||||
* This structure dictates rendering order, event propagation, and coordinate systems.
|
||||
* Operations like updating, drawing, and event dispatching typically traverse this hierarchy **recursively**.
|
||||
* A Widget's on-screen position is always relative to its parent. This simplifies layout management, as moving a parent automatically moves all its children. Sizing is also handled in relation to the parent.
|
||||
* Widgets can be given a custom string identifier which can be used to search for it in the hierarchy later.
|
||||
* ```Widget::Name("unique_element_4");```
|
||||
|
||||
```cpp
|
||||
// Dispatch a "MouseMovement" event until a widget indicates that it has "observed" the event.
|
||||
for (auto* widget : my_element->GetChildren()) {
|
||||
if (widget->ObserveMouseMovement({420, 420}) return;
|
||||
}
|
||||
|
||||
// Listens for an event of a child widget being added.
|
||||
// These event-hooks are provided to make customization easier without requiring you to derive the class.
|
||||
my_element->ChildAdded += [] (Widget* child) {
|
||||
child->Name("New Child Element!");
|
||||
};
|
||||
```
|
||||
</details>
|
||||
|
||||
<details><summary>Scene</summary>
|
||||
|
||||
|
||||
* The [Scene]() widget acts as the **root container** for all your JUI elements within a window or application.
|
||||
* It's the top-level `Widget` that manages the entire hierarchy of your UI.
|
||||
* You'll typically have one `Scene` per window, serving as the canvas upon which all other widgets are placed and rendered.
|
||||
* Every JUI application or window requires a `Scene` to host its user interface.
|
||||
* All other widgets become descendants of the `Scene`.
|
||||
* A scene is a **mandatory** component for a JUI application. You cannot display widgets without placing them in a `Scene`.
|
||||
* Unlike other `Widget` types, a `Scene` does not have a parent itself. It's the **root** of the hierarchy.
|
||||
* Event Sink - Events from the OS / Framework (mouse clicks, key presses) are first received by the `Scene`, which then initiates the recursive dispatch process down its tree.
|
||||
* Z-Ordering: While individual widgets use Z-Index for sibling ordering, the `Scene` ensures that its entire content is rendered correctly, typically on top of the OpenGL scene if applicable.
|
||||
* A scene's viewport (aka canvas size) can be set to the window viewport, or an arbitrary value.
|
||||
* The scene provides a GlobalUIScale property, that can be used to scale an entire GUI uniformly.
|
||||
|
||||
```cpp
|
||||
auto* scene = new JUI::Scene();
|
||||
|
||||
auto* text = new JUI::Text(scene);
|
||||
text->Content("Hello, World!");
|
||||
|
||||
void app_key_press(Key key) {
|
||||
// Returns whether any widget should have "consumed" the event.
|
||||
bool sunk = scene->ObserveKeyInput(key, true);
|
||||
}
|
||||
|
||||
void app_key_release(Key key) {
|
||||
// Returns whether any widget should have "consumed" the event.
|
||||
bool sunk = scene->ObserveKeyInput(key, false);
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>BindMenu</summary>
|
||||
This is some information about the Widget class.
|
||||
|
||||
### The BindMenu Widget is a work-in-progress. Check back in Prerelease 7.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -16,12 +85,36 @@ This is some information about the Widget class.
|
||||
|
||||
|
||||
<details><summary>Checkbox</summary>
|
||||
This is some information about the Widget class.
|
||||
|
||||
* The `Checkbox` widget provides a simple binary toggle input, allowing users to select or deselect an option.
|
||||
* Typically Consists of a small square box.
|
||||
* When clicked, the box's state flips, indicating whether the corresponding option is enabled or disabled.
|
||||
* Also see LabeledCheckbox for a Checkbox with integrated text.
|
||||
* Usages:
|
||||
* Enabling / Disabling Features
|
||||
* Opt-in / Opt-out Choices:
|
||||
* Multiple Selections - When presented in a group, checkboxes allow users to select zero, one or multiple independent options simultaneously.
|
||||
* Contrast with Radio Buttons, which allow only one selection from a group.
|
||||
* Boolean Settings
|
||||
</details>
|
||||
|
||||
|
||||
<details><summary>Collapsible</summary>
|
||||
This is some information about the Widget class.
|
||||
|
||||
* The `Collapsible` widget provides a way to **show or hide** a section of UI content with a single click, typically on a header or title bar.
|
||||
* It's a space-saving component that allows users to expand details when needed and collapse them to reduce visual clutter.
|
||||
* It's ideal for settings panels, property inspectors, or long lists of categorized information.
|
||||
* Usages:
|
||||
* Settings Panel - Group related settings that can be expanded individually.
|
||||
* Accordions - Create a series of Collapsibles where only one can be open at a time, by combining them with custom logic on the open/close events.
|
||||
* Property Inspectors - Display object properties that can be shown or hidden.
|
||||
* Information Hiding - Presenting advanced options or less frequently used details that don't need to be visible by default.
|
||||
* Debug Overlays - Containing logs or diagnostic tools that can be toggled open.
|
||||
* Notes
|
||||
* Content Container
|
||||
* Toggle Mechanism
|
||||
* Layout Impact
|
||||
* Initial State
|
||||
</details>
|
||||
|
||||
<details><summary>ColorPicker</summary>
|
||||
@@ -116,9 +209,7 @@ This is some information about the Widget class.
|
||||
This is some information about the Widget class.
|
||||
</details>
|
||||
|
||||
<details><summary>Scene</summary>
|
||||
This is some information about the Widget class.
|
||||
</details>
|
||||
|
||||
|
||||
<details><summary>ScrollingRect</summary>
|
||||
This is some information about the Widget class.
|
||||
@@ -129,7 +220,39 @@ This is some information about the Widget class.
|
||||
</details>
|
||||
|
||||
<details><summary>Slider</summary>
|
||||
This is some information about the Widget class.
|
||||
|
||||
* The `Slider` Widget allows users to select a value from a continuous or discrete range by moving a **scrubber** (or thumb, dragger, etc.) along a **track**.
|
||||
* `Slider` is a specialization of the `Rect` widget.
|
||||
* Usages:
|
||||
* Volume Control
|
||||
* Brightess/Contrast
|
||||
* Color Picker (JUI actually has [one](ColorPicker) built-in that uses `Sliders`!)
|
||||
* Progress Indicator (Just kidding, use a `ProgressBar` for this!);
|
||||
* Game Settings
|
||||
* Field-of-view.
|
||||
* Sensitivity
|
||||
* Spell power, physics parameters.
|
||||
* Numerical Input (Range-Bound): An alternative to a text input field when the allowed values are within a known, limited range.
|
||||
* Notes:
|
||||
* Orientation: ===Currently, JUI does not **yet** have vertical sliders.===
|
||||
* Range and Step: You can define a minimum and maximum value for the slider;
|
||||
* As well as a "step" quantity, forcing the slider to snap to discrete increments (ie. multiples of 5, or increments of 0.25)
|
||||
* Event-Driven - The "value changed" event fires when the user moves the scrubber or the value is set programmatically.
|
||||
|
||||
```cpp
|
||||
|
||||
auto* sfx_volume_slider = new JUI::Slider(my_scene);
|
||||
|
||||
sfx_volume_slider->Minimum(0);
|
||||
sfx_volume_slider->Maximum(100);
|
||||
sfx_volume_slider->Interval(1);
|
||||
sfx_volume_slider->CurrentValue(50);
|
||||
sfx_volume_slider->ValueChanged += [](float value) {
|
||||
// set audio engine SFX volume.
|
||||
};
|
||||
|
||||
```
|
||||
* Also see `LabeledSlider` for a slider with text.
|
||||
</details>
|
||||
|
||||
<details><summary>Table</summary>
|
||||
|
@@ -41,6 +41,12 @@ namespace JUI {
|
||||
using namespace J3ML::Math;
|
||||
using namespace J3ML::LinearAlgebra;
|
||||
|
||||
struct PaddedElementStyle {
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct EventArgs {
|
||||
|
||||
};
|
||||
@@ -123,6 +129,8 @@ namespace JUI {
|
||||
[[nodiscard]] virtual Vector2 GetAbsolutePosition() const;
|
||||
[[nodiscard]] virtual float GetAbsoluteRotation() const;
|
||||
|
||||
[[nodiscard]] virtual Vector2 GetAbsoluteCentroid() const;
|
||||
|
||||
/// Returns the parent of this widget, or a nullptr if there is no parent.
|
||||
/// @see GetFamilyTreeRoot().
|
||||
Widget* GetParent() const;
|
||||
|
62
include/JUI/Mixins/Focusable.hpp
Normal file
62
include/JUI/Mixins/Focusable.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/// Josh's User Interface Library
|
||||
/// A C++20 Library for creating, styling, and rendering of a UI/UX widgets.
|
||||
/// Developed and Maintained by Josh O'Leary @ Redacted Software.
|
||||
/// Special Thanks to William Tomasine II and Maxine Hayes.
|
||||
/// (c) 2024 Redacted Software
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file Resizable.hpp
|
||||
/// @desc Resizable Mixin Helper class
|
||||
/// @edit 2024-10-11
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Event.h>
|
||||
#include <J3ML/LinearAlgebra/Vector2.hpp>
|
||||
|
||||
namespace JUI
|
||||
{
|
||||
/// A mixin helper class that enables a widget to be resized by the mouse.
|
||||
class Focusable {
|
||||
public:
|
||||
Event<> OnGrabFocus;
|
||||
Event<> OnDropFocus;
|
||||
|
||||
[[nodiscard]] bool Focused() const;
|
||||
|
||||
[[nodiscard]] bool IsFocused() const { return focused;}
|
||||
[[nodiscard]] bool HasFocus() const { return focused;}
|
||||
virtual void GrabFocus() { SetFocused(true);}
|
||||
virtual void DropFocus(const Vector2& point) {
|
||||
if (do_focus_cooldown_hack && focus_hack_cooldown > time_focused)
|
||||
return;
|
||||
|
||||
SetFocused(false);
|
||||
}
|
||||
virtual void SetFocused(bool value) {
|
||||
focused = value;
|
||||
if (do_focus_cooldown_hack && value) time_focused = 0;
|
||||
}
|
||||
|
||||
virtual void Update(float delta) {
|
||||
if (do_focus_cooldown_hack)
|
||||
time_focused += delta;
|
||||
}
|
||||
|
||||
void DoFocusHackCooldown(bool value) { do_focus_cooldown_hack = value; }
|
||||
bool DoFocusHackCooldown() const { return do_focus_cooldown_hack; }
|
||||
|
||||
float FocusHackCooldown() const { return focus_hack_cooldown; }
|
||||
void FocusHackCooldown(float value) {
|
||||
focus_hack_cooldown = value;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool focused = false;
|
||||
float time_focused = 0;
|
||||
|
||||
bool do_focus_cooldown_hack = false;
|
||||
float focus_hack_cooldown = 0.1f;
|
||||
};
|
||||
}
|
||||
|
14
include/JUI/Style/ButtonStyler.hpp
Normal file
14
include/JUI/Style/ButtonStyler.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace JUI {
|
||||
struct ButtonStyle {
|
||||
RectStyle base_style;
|
||||
RectStyle hovered_style;
|
||||
RectStyle pressed_style;
|
||||
RectStyle disabled_style;
|
||||
};
|
||||
|
||||
class ButtonStyler {
|
||||
|
||||
};
|
||||
}
|
15
include/JUI/Style/CheckboxStyler.hpp
Normal file
15
include/JUI/Style/CheckboxStyler.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
namespace JUI {
|
||||
struct CheckboxStyle {
|
||||
RectStyle unchecked_style;
|
||||
RectStyle checked_color;
|
||||
RectStyle disabled_style;
|
||||
};
|
||||
|
||||
class CheckboxStyler {
|
||||
public:
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
}
|
@@ -5,6 +5,15 @@
|
||||
|
||||
namespace JUI {
|
||||
|
||||
struct RectStyle {
|
||||
float border_width = 1.f;
|
||||
Color4 border_color {192, 192, 192};
|
||||
Color4 bg_color {64, 64, 64};
|
||||
float corner_rounding = 0.f;
|
||||
enum BorderMode border_mode;
|
||||
enum CornerRoundingMode corner_mode;
|
||||
};
|
||||
|
||||
class RectStyler {
|
||||
public:
|
||||
#pragma region Getters
|
||||
@@ -21,11 +30,16 @@ namespace JUI {
|
||||
virtual void BorderWidth(float width) = 0;
|
||||
#pragma endregion
|
||||
|
||||
void Style(const RectStyle& value) {
|
||||
style = value;
|
||||
}
|
||||
protected:
|
||||
float border_width = 1.f;
|
||||
Color4 border_color {192, 192, 192};
|
||||
enum BorderMode border_mode;
|
||||
float corner_rounding = 0.f;
|
||||
|
||||
RectStyle style;
|
||||
private:
|
||||
};
|
||||
}
|
@@ -26,6 +26,9 @@ namespace JUI
|
||||
UDim() : Pixels(0), Scale(0.f) {}
|
||||
UDim(int pixels, float scale) : Pixels(pixels), Scale(scale) {}
|
||||
UDim(const UDim& u) = default;
|
||||
static UDim FromPixels(int pixels);
|
||||
static UDim FromScale(float scale);
|
||||
|
||||
public:
|
||||
UDim operator + (const UDim& rhs) const;
|
||||
|
||||
|
@@ -17,7 +17,6 @@
|
||||
|
||||
namespace JUI
|
||||
{
|
||||
|
||||
using namespace J3ML::LinearAlgebra;
|
||||
|
||||
/// Funky Coordinate system purpose-built for screen positioning
|
||||
@@ -26,6 +25,17 @@ namespace JUI
|
||||
/// @see UDim.hpp
|
||||
class UDim2
|
||||
{
|
||||
public: // Constants
|
||||
static const UDim2 Center;
|
||||
static const UDim2 TopLeft;
|
||||
static const UDim2 BottomRight;
|
||||
static const UDim2 BottomLeft;
|
||||
static const UDim2 TopRight;
|
||||
|
||||
static const UDim2 TopCenter;
|
||||
static const UDim2 LeftCenter;
|
||||
static const UDim2 BottomCenter;
|
||||
static const UDim2 RightCenter;
|
||||
public: // Properties
|
||||
UDim X;
|
||||
UDim Y;
|
||||
|
94
include/JUI/Widgets/Anchor.hpp
Normal file
94
include/JUI/Widgets/Anchor.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include <JUI/Base/Widget.hpp>
|
||||
|
||||
|
||||
namespace JUI {
|
||||
|
||||
/// An invisible, point-like widget which is used to attach decorator-type widgets at a given point.
|
||||
/// For example: The CurveSegment Widget uses two Anchors for it's endpoints.
|
||||
class Anchor : public Widget {
|
||||
public:
|
||||
Anchor() : Widget() {
|
||||
|
||||
}
|
||||
explicit Anchor(Widget* parent) : Anchor() {
|
||||
Parent(parent);
|
||||
};
|
||||
explicit Anchor(Widget* parent, const UDim2& position) : Anchor(parent) {
|
||||
Position(position);
|
||||
Size({0_px, 0_px});
|
||||
}
|
||||
|
||||
void Update(float elapsed) override {
|
||||
|
||||
}
|
||||
|
||||
void Draw() override {
|
||||
Color4 debug_color = Colors::Red;
|
||||
float debug_size = 1;
|
||||
|
||||
J2D::FillCircle(debug_color, GetAbsolutePosition(), debug_size, 5);
|
||||
}
|
||||
|
||||
Vector2 Point() { return GetAbsolutePosition(); }
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
|
||||
class Decorator : public Widget {
|
||||
public:
|
||||
Decorator() : Widget(){
|
||||
|
||||
}
|
||||
explicit Decorator(Widget* parent) : Decorator() {
|
||||
Parent(parent);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class ArrowDecorator : public Decorator {
|
||||
public:
|
||||
explicit ArrowDecorator(Widget* parent, Anchor* a, Anchor* b) : Decorator() {
|
||||
Parent(parent);
|
||||
origin = a;
|
||||
goal = b;
|
||||
}
|
||||
|
||||
|
||||
void Draw() override {
|
||||
|
||||
auto goal_point = goal->Point();
|
||||
auto origin_point = origin->Point();
|
||||
|
||||
using namespace J3ML;
|
||||
|
||||
|
||||
J2D::DrawLine(color, origin->Point(), goal->Point(), line_width);
|
||||
|
||||
|
||||
Vector2 direction = ( origin->Point() - goal->Point()).Normalized();
|
||||
|
||||
Vector2 triangle_base_pt = goal->Point() + (direction*arrowhead_size);
|
||||
|
||||
//J2D::DrawPoint(Colors::Yellow, triangle_base_pt, 4);
|
||||
|
||||
Vector2 tri_left = triangle_base_pt + (direction.Rotated90CW() *arrowhead_size);
|
||||
Vector2 tri_right = triangle_base_pt + (direction.Rotated90CCW()*arrowhead_size);
|
||||
Vector2 tri_top = goal->Point();
|
||||
|
||||
J2D::DrawLine(color, tri_top, tri_left, arrowhead_size);
|
||||
J2D::DrawLine(color, tri_top, tri_right, arrowhead_size);
|
||||
|
||||
}
|
||||
protected:
|
||||
|
||||
float arrowhead_size;
|
||||
float line_width;
|
||||
Color4 color;
|
||||
|
||||
Anchor* origin;
|
||||
Anchor* goal;
|
||||
private:
|
||||
|
||||
};
|
||||
}
|
@@ -32,6 +32,7 @@ namespace JUI {
|
||||
protected:
|
||||
bool checked = false;
|
||||
Color4 check_color = Style::Checkbox::CheckmarkColor;
|
||||
Color4 uncheck_color = Style::BackgroundColor;
|
||||
};
|
||||
|
||||
class Checkbox : public Button, public CheckboxBase {
|
||||
|
@@ -24,10 +24,6 @@ namespace JUI {
|
||||
|
||||
/// 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.
|
||||
@@ -42,18 +38,8 @@ namespace JUI {
|
||||
/// @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);
|
||||
void SetOpen(bool openVal) override;
|
||||
|
||||
/// Passes input events down the widget hierarchy.
|
||||
/// @return True if this widget will "consume" the input.
|
||||
@@ -70,7 +56,6 @@ namespace JUI {
|
||||
int index = 0;
|
||||
int input_form_height = 20;
|
||||
int message_height = 16;
|
||||
bool open = false;
|
||||
std::vector<std::string> msg_history;
|
||||
private:
|
||||
};
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
/// @file FileDialog.hpp
|
||||
/// @desc A dialog window that shows a selection of files on disk, and a form for naming, used for file loading and saving.
|
||||
|
||||
/// @edit 2024-5-29
|
||||
|
||||
#pragma once
|
||||
@@ -17,30 +18,127 @@
|
||||
|
||||
namespace JUI
|
||||
{
|
||||
|
||||
class FileListing : public JUI::Widget { };
|
||||
|
||||
// TODO: Implement SortBy enumeration - Date, Size, Name, Type
|
||||
class FileDialogWindow : public JUI::Window
|
||||
{
|
||||
public:
|
||||
Event<> UserCompleted;
|
||||
Event<> UserConfirmed;
|
||||
Event<> UserCancelled;
|
||||
Event<std::string> FileSelected;
|
||||
FileDialogWindow() : Window() {
|
||||
auto* toolbar = new JUI::UtilityBar(this->Content());
|
||||
|
||||
Title("File Dialog");
|
||||
|
||||
//auto* toolbar = new JUI::UtilityBar(this->Content());
|
||||
|
||||
auto* file_listing = new JUI::ScrollingRect(this->Content());
|
||||
file_listing->Size({100_percent, 100_percent-20_px});
|
||||
|
||||
file_layout = new JUI::VerticalListLayout(file_listing);
|
||||
}
|
||||
|
||||
/// Tree system
|
||||
|
||||
std::string format_perms(const std::filesystem::perms& p) const {
|
||||
using std::filesystem::perms;
|
||||
auto parse = [=](char op, perms perm) {
|
||||
return (perms::none == (perm & p) ? '-' : op);
|
||||
};
|
||||
|
||||
std::string perm_string = "";
|
||||
|
||||
perm_string += parse('r', perms::owner_read);
|
||||
perm_string += parse('w', perms::owner_write);
|
||||
perm_string += parse('x', perms::owner_exec);
|
||||
|
||||
perm_string += parse('r', perms::group_read);
|
||||
perm_string += parse('w', perms::group_write);
|
||||
perm_string += parse('x', perms::group_exec);
|
||||
|
||||
perm_string += parse('r', perms::others_read);
|
||||
perm_string += parse('w', perms::others_write);
|
||||
perm_string += parse('x', perms::others_exec);
|
||||
|
||||
return perm_string;
|
||||
}
|
||||
|
||||
std::string format_size(uintmax_t size) const {
|
||||
bool addSpace = false;
|
||||
int precision = 2;
|
||||
std::vector<std::string> units = {"B", "KB", "GB", "TB", "PB", "EB", "ZB", "YB"};
|
||||
|
||||
if (Math::Abs(size) < 1) return std::to_string(size) + (addSpace ? " " : "") + units[0];
|
||||
|
||||
auto exponent = Math::Min<uintmax_t>(Math::Floor(Math::Log10(size < 0 ? -size : size) / 3), units.size()-1);
|
||||
|
||||
auto n = (size < 0 ? -size : size) / std::pow(1000, exponent);
|
||||
|
||||
|
||||
return (size < 0 ? "-" : "") + std::format("{}", n) + (addSpace ? " ": "") + units[exponent];
|
||||
}
|
||||
|
||||
JUI::Rect* CreateFileEntry(const std::filesystem::directory_entry& entry) {
|
||||
|
||||
|
||||
auto* rect = new JUI::TextButton(file_layout);
|
||||
rect->LayoutOrder(index);
|
||||
rect->Size({100_percent, 20_px});
|
||||
|
||||
std::string entry_type = entry.is_directory() ? "directory" : "file";
|
||||
std::string perms = format_perms(entry.status().permissions());
|
||||
|
||||
auto file_size = (entry.is_directory() ? 0 : entry.file_size());
|
||||
auto file_size_str = format_size(file_size);
|
||||
std::filesystem::file_time_type timestamp = (entry.is_directory() ? std::filesystem::file_time_type{} : entry.last_write_time());
|
||||
|
||||
std::string label = std::format("{} {} {} {} {}", entry.path().filename().string(), file_size_str, entry_type, perms, timestamp);
|
||||
|
||||
rect->Content(label);
|
||||
rect->BaseBGColor(Colors::Gray);
|
||||
|
||||
index++;
|
||||
|
||||
rect->Clicked += [this, entry] (auto ev) mutable {
|
||||
FileSelected(entry.path().string());
|
||||
};
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
FileDialogWindow(const std::filesystem::path& root) : FileDialogWindow() {
|
||||
std::cout << root.relative_path() << std::endl;
|
||||
std::cout << root.root_directory() << std::endl;
|
||||
std::cout << root.filename() << std::endl;
|
||||
std::cout << root.parent_path() << std::endl;
|
||||
std::cout << root.string() << std::endl;
|
||||
std::cout << std::filesystem::current_path() << std::endl;
|
||||
|
||||
Title(std::format("'{}' in '{}' - File Dialog", root.relative_path().string(), std::filesystem::current_path().string()));
|
||||
|
||||
for (const auto& entry: std::filesystem::directory_iterator(root)) {
|
||||
|
||||
CreateFileEntry(entry);
|
||||
}
|
||||
}
|
||||
FileDialogWindow(const std::filesystem::path& root) : FileDialogWindow() {}
|
||||
explicit FileDialogWindow(Widget* parent, const std::filesystem::path& root) : FileDialogWindow(root)
|
||||
{
|
||||
Parent(parent);
|
||||
}
|
||||
|
||||
void SetSelectedFile(const std::string& filename);
|
||||
void SetConfirmOverwrite(bool confirm);
|
||||
void SetBlockingWhileOpen(bool blocking);
|
||||
bool GetBlockingWhileOpen() const;
|
||||
|
||||
|
||||
protected:
|
||||
int directories_indexed = 0;
|
||||
int index = 0;
|
||||
VerticalListLayout* file_layout;
|
||||
std::vector<Rect*> file_entry_widgets;
|
||||
private:
|
||||
};
|
||||
|
||||
|
@@ -10,12 +10,31 @@ namespace JUI {
|
||||
/// Dashed, dotted, and solid lines of varying thickness are supported.
|
||||
class Separator : public Widget {
|
||||
public:
|
||||
Separator();
|
||||
|
||||
explicit Separator(Widget* parent);
|
||||
|
||||
|
||||
|
||||
|
||||
/// Constructs a Separator widget by specifying it's parent. Additional style parameters can also be specified.
|
||||
/// @param parent
|
||||
/// @param orientation
|
||||
/// @param spacing
|
||||
/// @param line_mode
|
||||
/// @param line_color
|
||||
/// @param line_thickness
|
||||
Separator(Widget* parent,
|
||||
const enum Orientation& orientation = Orientation::Horizontal,
|
||||
const UDim& spacing = 5_px,
|
||||
const enum LineFillMode& line_mode = LineFillMode::Solid,
|
||||
const Color4& line_color = Colors::Black,
|
||||
float line_thickness = 1.f
|
||||
);
|
||||
|
||||
enum Orientation Orientation() const;
|
||||
|
||||
/// Sets whether the separator is laid out as a horizontal or vertical line.
|
||||
/// Horizontal orientation draws a horizontal line, for separating elements in a vertical list.
|
||||
/// Vertical orientation draws a vertical line, for separating elements in a horizontal list.
|
||||
void Orient(const enum Orientation& orientation);
|
||||
|
||||
enum LineFillMode LineFillMode() const;
|
||||
@@ -26,18 +45,43 @@ namespace JUI {
|
||||
|
||||
void LineColor(const Color4& color);
|
||||
|
||||
float GetAbsoluteSpacing() const;
|
||||
|
||||
|
||||
void InnerDraw() override;
|
||||
|
||||
float Spacing() const;
|
||||
//float Spacing() const;
|
||||
|
||||
void Spacing(float spacing);
|
||||
//void Spacing(float spacing);
|
||||
|
||||
/// @return The size of the separator object.
|
||||
UDim Spacing() const { return spacing;}
|
||||
/// Sets the size of the separator object.
|
||||
void Spacing(const UDim& value) {
|
||||
spacing = value;
|
||||
UpdateSize();
|
||||
}
|
||||
|
||||
/// @returns The spacing between
|
||||
float LineSpacing() const { return line_spacing;}
|
||||
void LineSpacing(float value) {
|
||||
line_spacing = value;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// The default constructor zero-initializes all members.
|
||||
Separator();
|
||||
|
||||
/// Sets the widgets size based on the spacing and orientation.
|
||||
void UpdateSize();
|
||||
|
||||
enum LineFillMode line_mode = LineFillMode::Solid;
|
||||
enum Orientation orientation = Orientation::Horizontal;
|
||||
Color4 line_color = Colors::Black;
|
||||
float line_thickness = 1.f;
|
||||
float spacing = 2.f;
|
||||
float line_spacing = 2.f;
|
||||
|
||||
UDim spacing = 2_px;
|
||||
private:
|
||||
};
|
||||
|
||||
|
@@ -27,18 +27,32 @@ namespace JUI {
|
||||
|
||||
class TextInputForm : public TextRect, public Clickable {
|
||||
public:
|
||||
#pragma region Events
|
||||
Event<> OnSelect;
|
||||
Event<> OnDeselect;
|
||||
Event<std::string> OnReturn;
|
||||
#pragma endregion
|
||||
public:
|
||||
#pragma region Constructors
|
||||
TextInputForm();
|
||||
explicit TextInputForm(Widget* parent);
|
||||
#pragma endregion
|
||||
public:
|
||||
#pragma region Methods
|
||||
void Update(float elapsed) override;
|
||||
void InnerDraw() override;
|
||||
void Draw() override;
|
||||
/// @return The number of characters the cursor is actually moved to the left by.
|
||||
int MoveCursorLeft(int chars = 1);
|
||||
/// @note The amount of
|
||||
/// @return The number of characters the cursor is actually moved to the right by.
|
||||
int MoveCursorRight(int chars = 1);
|
||||
#pragma region Getters
|
||||
|
||||
void MoveCursorLeft();
|
||||
#pragma endregion
|
||||
#pragma region Setters
|
||||
|
||||
void MoveCursorRight();
|
||||
#pragma endregion
|
||||
|
||||
/// Returns the maximum position of the cursor, which is determined by the input buffer's length.
|
||||
[[nodiscard]] unsigned int CursorMaxPosition() const;
|
||||
@@ -64,6 +78,11 @@ namespace JUI {
|
||||
void PushKeyToCurrentPlaceInInputBuffer(const Key &key);
|
||||
|
||||
bool ObserveKeyInput(Key key, bool pressed) override;
|
||||
|
||||
void ShowNextHistory();
|
||||
|
||||
void ShowPrevHistory();
|
||||
|
||||
bool ObserveMouseInput(MouseButton btn, bool pressed) override;
|
||||
bool ObserveMouseMovement(const Vector2 &latest_known_pos) override;
|
||||
[[nodiscard]] std::string GetAutocompleteText() const;
|
||||
@@ -103,22 +122,50 @@ namespace JUI {
|
||||
void SetSelectionColor(const Color4& color);
|
||||
bool HasSelection() const;
|
||||
std::string GetSelectedText() const;
|
||||
|
||||
[[nodiscard]] bool KeepInputHistory() const;
|
||||
|
||||
void KeepInputHistory(bool value);
|
||||
|
||||
std::string InputHistory(int index) const;
|
||||
|
||||
#pragma endregion
|
||||
protected:
|
||||
#pragma region Properties
|
||||
bool keep_input_history = false;
|
||||
bool clear_text_on_return = true;
|
||||
bool drop_focus_on_return = false;
|
||||
bool focused = false;
|
||||
bool selection_enabled;
|
||||
bool selection_active;
|
||||
int selection_start_index;
|
||||
int selection_end_index;
|
||||
unsigned int cursor_position = 0;
|
||||
std::string input_buffer;
|
||||
|
||||
float cursor_blink_time = 0.f;
|
||||
Color4 autocomplete_color = Style::InputForm::AutocompleteTextColor;
|
||||
std::string autocomplete_text = "Hello World";
|
||||
bool hide_autocomplete_on_select = true;
|
||||
bool autocomplete_text_enabled = true;
|
||||
std::set<std::string> blacklist;
|
||||
bool selection_enabled;
|
||||
#pragma endregion
|
||||
#pragma region Working Variables
|
||||
std::vector<std::string> history;
|
||||
int history_index = -1;
|
||||
bool selection_active;
|
||||
int selection_start_index;
|
||||
int selection_end_index;
|
||||
unsigned int cursor_position = 0;
|
||||
std::string input_buffer;
|
||||
std::string saved_input_buffer;
|
||||
|
||||
|
||||
/// Tracks the time (in seconds) since the TextInputForm was last opened.
|
||||
/// @note This is used to circumvent a behavioral bug caused by how the input code is structured:
|
||||
/// 1. User clicks a button that opens an InputForm.
|
||||
/// 2. Grab InputForm Focus.
|
||||
/// 3. User releases the button.
|
||||
/// 4. The input form interprets this as "I am focused, but something else was just clicked".
|
||||
/// 5. The input form drops its focus instantly, and it appears as if it was never focused.
|
||||
float time_focused = 0.f;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
};
|
||||
}
|
@@ -34,6 +34,9 @@ namespace JUI {
|
||||
/// 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 {
|
||||
public:
|
||||
Event<> OnOpen;
|
||||
Event<> OnClose;
|
||||
|
||||
/// The default constructor sets a default style for this Window.
|
||||
Window();
|
||||
/// Construct a window widget by specifying it's parent.
|
||||
@@ -72,14 +75,18 @@ namespace JUI {
|
||||
/// @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;
|
||||
void MoveUp() {
|
||||
zindex++;
|
||||
}
|
||||
/// Moves this window down in the window-widget stack.
|
||||
void MoveDown() const;
|
||||
void MoveDown() {
|
||||
zindex--;
|
||||
}
|
||||
|
||||
|
||||
void TitlebarHeight(int height);
|
||||
|
||||
@@ -169,8 +176,13 @@ namespace JUI {
|
||||
{
|
||||
// TODO: Consider how this plays with Clickable::OnClick and Clickable::OnRelease
|
||||
|
||||
if (this->visible && this->focused)
|
||||
return Widget::ObserveMouseInput(btn, pressed);
|
||||
if (visible) {
|
||||
if (!pressed)
|
||||
return Widget::ObserveMouseInput(btn, false);
|
||||
|
||||
if (this->focused)
|
||||
return Widget::ObserveMouseInput(btn, pressed);
|
||||
}
|
||||
|
||||
// Special case to allow drop of "Resizing"
|
||||
if (btn == MouseButton::Right && pressed == false)
|
||||
@@ -180,10 +192,12 @@ namespace JUI {
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsOpen() const;
|
||||
void Open();
|
||||
void Close();
|
||||
void SetOpen(bool value);
|
||||
void Toggle();
|
||||
[[nodiscard]] bool IsClosed() const;
|
||||
|
||||
virtual void Open();
|
||||
virtual void Close();
|
||||
virtual void SetOpen(bool value);
|
||||
virtual void Toggle();
|
||||
|
||||
protected:
|
||||
void UpdateInternalWidgetsTitlebarHeight();
|
||||
|
115
main.cpp
115
main.cpp
@@ -34,14 +34,15 @@
|
||||
#include <JUI/Widgets/Collapsible.hpp>
|
||||
#include <JUI/Widgets/CommandLine.hpp>
|
||||
|
||||
#include "JUI/Widgets/Anchor.hpp"
|
||||
#include "JUI/Widgets/ColorPicker.hpp"
|
||||
#include "JUI/Widgets/FileDialog.hpp"
|
||||
#include "JUI/Widgets/FpsGraph.hpp"
|
||||
#include "JUI/Widgets/LabeledSlider.hpp"
|
||||
#include "JUI/Widgets/Link.hpp"
|
||||
|
||||
using namespace JUI;
|
||||
|
||||
|
||||
float ui_scale = 1.f;
|
||||
float accum = 0;
|
||||
int iter = 0;
|
||||
@@ -64,6 +65,10 @@ int count_descendants(JUI::Widget* w) {
|
||||
return w->GetDescendants().size();
|
||||
}
|
||||
|
||||
void CreateFileDialog() {
|
||||
auto* dialog = new JUI::FileDialogWindow(scene, ".");
|
||||
}
|
||||
|
||||
/// Creates the window that provides a "Welcome" dialog to the user.
|
||||
/// Project metadata and copyright information is presented.
|
||||
JUI::Window* CreateInfoboxWindow(JUI::Widget* root) {
|
||||
@@ -156,7 +161,11 @@ JUI::UtilityBar* CreateUtilityBar(JUI::Widget* root) {
|
||||
|
||||
demos->AddButton("Scroll Widget Demo", [&] {});
|
||||
|
||||
demos->AddButton("Command Line", [&]{ console->Toggle();});
|
||||
demos->AddButton("Command Line", [&] {
|
||||
console->Toggle();
|
||||
});
|
||||
|
||||
demos->AddButton("File Dialog", [&] {CreateFileDialog();});
|
||||
|
||||
auto* test_sub = demos->AddSubmenu("Fruit"); {
|
||||
test_sub->AddButton("Apples");
|
||||
@@ -170,6 +179,13 @@ JUI::UtilityBar* CreateUtilityBar(JUI::Widget* root) {
|
||||
berries->AddButton("Blueberries");
|
||||
}
|
||||
}
|
||||
|
||||
auto* ptA = new JUI::Anchor(topbar, {30_px, 40_px});
|
||||
|
||||
auto* ptB = new JUI::Anchor(demos, UDim2::BottomRight);
|
||||
|
||||
auto* line = new JUI::ArrowDecorator(topbar, ptB, ptA);
|
||||
|
||||
}
|
||||
|
||||
auto* edit = topbar->AddButton("Edit");
|
||||
@@ -192,10 +208,8 @@ JUI::UtilityBar* CreateUtilityBar(JUI::Widget* root) {
|
||||
docked = true;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
auto* help = topbar->AddButton("About", [&] {
|
||||
|
||||
});
|
||||
@@ -332,30 +346,85 @@ JUI::Rect* CreateWidgetList(JUI::Widget* root) {
|
||||
auto* checkbox_section = new Collapsible(column_layout);
|
||||
{
|
||||
checkbox_section->Title("Checkbox Styles");
|
||||
checkbox_section->Size({100_percent, 50_px});
|
||||
checkbox_section->Size({100_percent, 60_px});
|
||||
|
||||
auto* checkbox_layout = new VerticalListLayout(checkbox_section->ContentBox());
|
||||
|
||||
auto* set_1 = new Rect(checkbox_layout);
|
||||
{
|
||||
set_1->Size({100_percent, 40_px});
|
||||
{auto* set_1 = new Rect(checkbox_layout);
|
||||
|
||||
set_1->Size({100_percent, 24_px});
|
||||
auto* set_1_layout = new HorizontalListLayout(set_1);
|
||||
auto* label = new TextRect(set_1_layout);
|
||||
label->Content("Standard");
|
||||
label->BorderWidth(0);
|
||||
label->AutoFitSizeToText(true);
|
||||
label->Size({100_px, 20_px});
|
||||
//label->Size({50_px, 20_px});
|
||||
|
||||
auto* separator_1 = new Separator(set_1_layout, Orientation::Vertical, 10_px);
|
||||
separator_1->Visible(false);
|
||||
|
||||
auto* check1 = new Checkbox(set_1_layout);
|
||||
check1->Size({20_px, 20_px});
|
||||
|
||||
auto* separator_2 = new Separator(set_1_layout, Orientation::Vertical, 10_px);
|
||||
separator_2->Visible(false);
|
||||
|
||||
auto* check2 = new Checkbox(set_1_layout);
|
||||
check2->Size({20_px, 20_px});
|
||||
check2->CheckedColor(Colors::Blue);
|
||||
check2->CornerRounding(7);
|
||||
|
||||
auto* separator_3 = new Separator(set_1_layout, Orientation::Vertical, 10_px);
|
||||
separator_3->Visible(false);
|
||||
|
||||
auto* check3 = new Checkbox(set_1_layout);
|
||||
check3->Size({20_px, 20_px});
|
||||
check3->CheckedColor(Colors::Oranges::Coral);
|
||||
check3->CornerRounding(7);
|
||||
|
||||
auto* separator_4 = new Separator(set_1_layout, Orientation::Vertical, 10_px);
|
||||
separator_4->Visible(false);
|
||||
|
||||
auto* check4 = new Checkbox(set_1_layout);
|
||||
check4->Size({20_px, 20_px});
|
||||
check4->DisabledBorderColor(Colors::Black);
|
||||
check4->Disable();
|
||||
|
||||
};
|
||||
{auto* set_2 = new Rect(checkbox_layout);
|
||||
|
||||
set_2->Size({100_percent, 24_px});
|
||||
auto* layout = new HorizontalListLayout(set_2);
|
||||
|
||||
auto* label = new TextRect(layout);
|
||||
label->Content("Customized:");
|
||||
label->BorderWidth(0);
|
||||
label->AutoFitSizeToText(true);
|
||||
|
||||
auto* separator_1 = new Separator(layout, Orientation::Vertical, 20_px);
|
||||
separator_1->Visible(false);
|
||||
|
||||
auto* check1 = new Checkbox(layout);
|
||||
check1->Size({20_px, 20_px});
|
||||
|
||||
auto* separator_2 = new Separator(layout, Orientation::Vertical, 10_px);
|
||||
separator_2->Visible(false);
|
||||
|
||||
auto* check2 = new Checkbox(layout);
|
||||
check2->Size({20_px, 20_px});
|
||||
check2->CheckedColor(Colors::Blue);
|
||||
check2->CornerRounding(7);
|
||||
|
||||
auto* separator_3 = new Separator(layout, Orientation::Vertical, 10_px);
|
||||
separator_3->Visible(false);
|
||||
|
||||
auto* check3 = new Checkbox(layout);
|
||||
check3->Size({20_px, 20_px});
|
||||
check3->CheckedColor(Colors::Oranges::Coral);
|
||||
check3->CornerRounding(10);
|
||||
check3->BorderMode(BorderMode::Inset);
|
||||
check3->BorderWidth(2);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,22 +503,6 @@ JUI::Rect* CreateWidgetList(JUI::Widget* root) {
|
||||
radio_c_btn->Size({20_px, 20_px});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// What the FUCK?
|
||||
|
||||
/*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);*/
|
||||
|
||||
return widget_list;
|
||||
}
|
||||
|
||||
@@ -509,18 +562,6 @@ JUI::Scene* CreateScene() {
|
||||
auto* widget_list = CreateWidgetList(root);
|
||||
|
||||
|
||||
//auto* horizontal = new HorizontalListLayout(root);
|
||||
//horizontal->ZIndex(1);
|
||||
|
||||
|
||||
|
||||
//auto* separator_a = new Rect(radio_btn_set_layout());
|
||||
|
||||
|
||||
|
||||
//nineslice_demo_window->Padding(1_px);
|
||||
// End Window //
|
||||
|
||||
|
||||
|
||||
root->SetViewportSize({800, 600});
|
||||
|
@@ -147,6 +147,10 @@ float Widget::GetAbsoluteRotation() const {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
Vector2 Widget::GetAbsoluteCentroid() const {
|
||||
return GetAbsolutePosition() + (GetAbsoluteSize() / 2.f);
|
||||
}
|
||||
|
||||
std::vector<Widget *> Widget::GetChildren() { return children; }
|
||||
|
||||
std::vector<Widget *> Widget::GetDescendants() {
|
||||
|
3
src/JUI/Mixins/Focusable.cpp
Normal file
3
src/JUI/Mixins/Focusable.cpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#include <JUI/Mixins/Focusable.hpp>
|
||||
|
||||
bool JUI::Focusable::Focused() const { return focused; }
|
@@ -16,6 +16,10 @@ JUI::UDim JUI::UDimLiterals::operator ""_px(unsigned long long px) {
|
||||
return {static_cast<int>(px), 0.f};
|
||||
}
|
||||
|
||||
JUI::UDim JUI::UDim::FromPixels(int pixels) { return {pixels, 0.f}; }
|
||||
|
||||
JUI::UDim JUI::UDim::FromScale(float scale) { return {0, scale}; }
|
||||
|
||||
JUI::UDim JUI::UDim::operator+(const UDim &rhs) const {
|
||||
return {Pixels + rhs.Pixels, Scale + rhs.Scale};
|
||||
}
|
||||
|
@@ -1,5 +1,18 @@
|
||||
#include <JUI/UDim2.hpp>
|
||||
|
||||
const JUI::UDim2 JUI::UDim2::Center {50_percent, 50_percent};
|
||||
|
||||
const JUI::UDim2 JUI::UDim2::TopLeft {0_percent, 0_percent};
|
||||
const JUI::UDim2 JUI::UDim2::BottomRight {100_percent, 100_percent};
|
||||
const JUI::UDim2 JUI::UDim2::BottomLeft {0_percent, 100_percent};
|
||||
const JUI::UDim2 JUI::UDim2::TopRight {100_percent, 0_percent};
|
||||
|
||||
const JUI::UDim2 JUI::UDim2::TopCenter {50_percent, 0_percent};
|
||||
const JUI::UDim2 JUI::UDim2::LeftCenter {0_percent, 50_percent};
|
||||
const JUI::UDim2 JUI::UDim2::BottomCenter {50_percent, 100_percent};
|
||||
|
||||
const JUI::UDim2 JUI::UDim2::RightCenter {100_percent, 50_percent};
|
||||
|
||||
JUI::UDim2::UDim2() {
|
||||
//X = {0, 0.f};
|
||||
//Y = {0, 0.f};
|
||||
|
@@ -160,7 +160,7 @@ namespace JUI {
|
||||
BaseBGColor(base);
|
||||
HoveredBGColor(hover);
|
||||
PressedBGColor(pressed);
|
||||
DisabledBGColor(disabled);\
|
||||
DisabledBGColor(disabled);
|
||||
UpdateVisualState();
|
||||
}
|
||||
}
|
@@ -22,12 +22,14 @@ namespace JUI {
|
||||
|
||||
void Checkbox::InnerDraw() {
|
||||
Rect::InnerDraw();
|
||||
|
||||
// TODO: Renders too large with BorderMode::Inset
|
||||
if (checked) {
|
||||
Vector2 pos_pad = ComputeElementPadding(GetAbsoluteSize(), {PaddingLeft(), PaddingTop()});
|
||||
Vector2 size_pad = ComputeElementPadding(GetAbsoluteSize(), {PaddingLeft()+PaddingRight(), PaddingTop()+PaddingBottom()});
|
||||
Vector2 padded_pos = GetAbsolutePosition() + pos_pad;
|
||||
Vector2 padded_size = GetAbsoluteSize() - size_pad;
|
||||
RectBase::Draw(check_color, check_color, padded_pos, padded_size);
|
||||
RectBase::Draw(check_color, border_color, padded_pos, padded_size);
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,6 +35,7 @@ namespace JUI
|
||||
input_box->TextSize(16);
|
||||
input_box->TextColor(Colors::White);
|
||||
input_box->SetAutocompleteTextColor(Colors::Gray);
|
||||
input_box->KeepInputHistory(true);
|
||||
|
||||
input_box->OnReturn += [this] (const std::string& msg) {
|
||||
OnInputBoxSend(msg);
|
||||
@@ -46,17 +47,10 @@ namespace JUI
|
||||
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;
|
||||
Window::SetOpen(openVal);
|
||||
this->input_box->SetFocused(openVal);
|
||||
}
|
||||
|
||||
void CommandLine::Log(const std::string &message, const Color4 &color) {
|
||||
|
@@ -80,8 +80,8 @@ namespace JUI {
|
||||
auto* btn = AddButton(name);
|
||||
|
||||
// TODO: Is this memory safe at all?
|
||||
btn->OnClickEvent += [this, btn, callback] (auto a, auto b) mutable {
|
||||
if (this->IsVisible() && btn->IsMouseInside())
|
||||
btn->OnReleaseEvent += [this, btn, callback] (auto a, auto b, bool still_focus) mutable {
|
||||
if (still_focus && this->IsVisible() && btn->IsMouseInside())
|
||||
callback();
|
||||
};
|
||||
return btn;
|
||||
@@ -109,8 +109,8 @@ namespace JUI {
|
||||
submenu->Position({100_percent, 0_percent});
|
||||
submenu->Visible(false);
|
||||
|
||||
btn->OnClickEvent += [this, submenu] (auto a, auto b) mutable {
|
||||
if (this->IsVisible()) {
|
||||
btn->OnReleaseEvent += [this, submenu] (auto a, auto b, bool still_there) mutable {
|
||||
if (still_there && this->IsVisible()) {
|
||||
this->Pin();
|
||||
submenu->Visible(true);
|
||||
}
|
||||
|
@@ -119,29 +119,30 @@ bool ScrollingRect::ObserveKeyInput(Key key, bool pressed) {
|
||||
if (Widget::ObserveKeyInput(key, pressed))
|
||||
return true;
|
||||
|
||||
if (IsMouseInside()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@@ -6,12 +6,31 @@ namespace JUI {
|
||||
Name("Separator");
|
||||
}
|
||||
|
||||
Separator::Separator(Widget *parent): Separator() {
|
||||
Parent(parent);
|
||||
void Separator::UpdateSize() {
|
||||
if (orientation == Orientation::Horizontal)
|
||||
Size({100_percent, spacing});
|
||||
|
||||
if (orientation == Orientation::Vertical)
|
||||
Size({spacing, 100_percent});
|
||||
}
|
||||
|
||||
Separator::Separator(Widget *parent, const enum JUI::Orientation &orientation, const UDim &spacing,
|
||||
const enum JUI::LineFillMode &line_mode, const Color4 &line_color, float line_thickness): Separator() {
|
||||
this->orientation = orientation;
|
||||
this->spacing = spacing;
|
||||
this->line_mode = line_mode;
|
||||
this->line_color = line_color;
|
||||
this->line_thickness = line_thickness;
|
||||
UpdateSize();
|
||||
Parent(parent);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Separator::Orient(const enum JUI::Orientation &orientation) {
|
||||
this->orientation = orientation;
|
||||
|
||||
UpdateSize();
|
||||
}
|
||||
|
||||
void Separator::LineFillMode(const enum JUI::LineFillMode &line_mode) {
|
||||
@@ -24,6 +43,24 @@ namespace JUI {
|
||||
line_color = color;
|
||||
}
|
||||
|
||||
float Separator::GetAbsoluteSpacing() const {
|
||||
if (parent == nullptr) {
|
||||
// TODO: This function should in theory not be called in this case.
|
||||
return 0.f;
|
||||
}
|
||||
auto abs_parent_size = parent->GetAbsoluteSize();
|
||||
if (orientation == ::JUI::Orientation::Horizontal) {
|
||||
return spacing.Pixels + (spacing.Scale * abs_parent_size.y);
|
||||
}
|
||||
|
||||
if (orientation == Orientation::Vertical) {
|
||||
return spacing.Pixels + (spacing.Scale * abs_parent_size.x);
|
||||
}
|
||||
|
||||
// This control path should never be reached.
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
void Separator::InnerDraw() {
|
||||
|
||||
Vector2 abs_pos = GetAbsolutePosition();
|
||||
@@ -34,6 +71,8 @@ namespace JUI {
|
||||
|
||||
// TODO: Factor in padding.
|
||||
|
||||
float spacing = GetAbsoluteSpacing();
|
||||
|
||||
if (orientation == Orientation::Horizontal) {
|
||||
start = abs_pos + Vector2(0, abs_size.y / 2.f);
|
||||
end = start + Vector2(abs_size.x, 0);
|
||||
@@ -44,22 +83,18 @@ namespace JUI {
|
||||
end = start + Vector2(0, abs_size.y);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (line_mode == LineFillMode::Dotted)
|
||||
J2D::DrawDottedLine(line_color, start, end, spacing, line_thickness);
|
||||
J2D::DrawDottedLine(line_color, start, end, line_spacing, line_thickness);
|
||||
|
||||
if (line_mode == LineFillMode::Dashed)
|
||||
J2D::DrawDashedLine(line_color, start, end ,spacing, line_thickness);
|
||||
J2D::DrawDashedLine(line_color, start, end, line_spacing, line_spacing, line_thickness);
|
||||
|
||||
if (line_mode == LineFillMode::Solid)
|
||||
JGL::J2D::DrawLine(line_color, start, end, line_thickness);
|
||||
}
|
||||
|
||||
float Separator::Spacing() const { return spacing; }
|
||||
|
||||
void Separator::Spacing(float spacing) {
|
||||
this->spacing = spacing;
|
||||
}
|
||||
|
||||
enum Orientation Separator::Orientation() const { return orientation;}
|
||||
|
||||
enum LineFillMode Separator::LineFillMode() const { return line_mode;}
|
||||
|
@@ -31,20 +31,18 @@ namespace JUI {
|
||||
|
||||
bool TextInputForm::ObserveMouseInput(MouseButton btn, bool pressed) {
|
||||
|
||||
if (pressed && btn == MouseButton::Left) {
|
||||
if (!IsMouseInside() && focused && time_focused > 1.f) {
|
||||
OnDeselect.Invoke();
|
||||
focused = false;
|
||||
}
|
||||
|
||||
if (IsMouseInside()) {
|
||||
if (!focused) {
|
||||
OnSelect.Invoke();
|
||||
}
|
||||
focused = true;
|
||||
return true;
|
||||
} else {
|
||||
if (focused)
|
||||
OnDeselect.Invoke();
|
||||
focused = false;
|
||||
|
||||
if (IsMouseInside() && pressed && btn == MouseButton::Left) {
|
||||
if (!focused) {
|
||||
OnSelect.Invoke();
|
||||
}
|
||||
|
||||
focused = true;
|
||||
return true;
|
||||
}
|
||||
return Widget::ObserveMouseInput(btn, pressed);
|
||||
}
|
||||
@@ -87,6 +85,7 @@ namespace JUI {
|
||||
|
||||
// TODO: Make cursor actually blink
|
||||
if (focused) {
|
||||
time_focused += elapsed;
|
||||
cursor_blink_time += elapsed;
|
||||
|
||||
if (cursor_blink_time > 1.f)
|
||||
@@ -115,12 +114,27 @@ namespace JUI {
|
||||
return s;
|
||||
}
|
||||
|
||||
void TextInputForm::MoveCursorLeft() {
|
||||
if (cursor_position > 0)
|
||||
cursor_position--;
|
||||
int TextInputForm::MoveCursorLeft(int chars) {
|
||||
if (cursor_position <= 0) return 0; // TODO: This may be redundant?
|
||||
|
||||
int available_moves = Math::Min<int>(chars, cursor_position);
|
||||
|
||||
cursor_position -= available_moves;
|
||||
|
||||
return available_moves;
|
||||
}
|
||||
|
||||
void TextInputForm::MoveCursorRight() {
|
||||
int TextInputForm::MoveCursorRight(int chars) {
|
||||
if (cursor_position >= CursorMaxPosition()) return 0;
|
||||
|
||||
int chars_until_end = CursorMaxPosition() - cursor_position;
|
||||
|
||||
int available_moves = Math::Min<int>(chars, chars_until_end);
|
||||
|
||||
cursor_position += available_moves;
|
||||
|
||||
return available_moves;
|
||||
|
||||
if (cursor_position < CursorMaxPosition()-1)
|
||||
cursor_position++;
|
||||
}
|
||||
@@ -137,6 +151,15 @@ namespace JUI {
|
||||
void TextInputForm::SendInput(bool clear_input) {
|
||||
OnReturn.Invoke(input_buffer);
|
||||
|
||||
if (keep_input_history) {
|
||||
if (history.size() == 0 || history[0] != input_buffer) {
|
||||
history.insert(history.begin(), input_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
history_index = -1;
|
||||
saved_input_buffer = "";
|
||||
|
||||
if (clear_input) {
|
||||
input_buffer = "";
|
||||
cursor_position = 0;
|
||||
@@ -159,11 +182,15 @@ namespace JUI {
|
||||
if (cursor_position > 0) {
|
||||
input_buffer = input_buffer.erase(cursor_position-1, 1);
|
||||
cursor_position--;
|
||||
|
||||
saved_input_buffer = input_buffer;
|
||||
}
|
||||
}
|
||||
void TextInputForm::PushStringToCurrentPlaceInInputBuffer(const std::string& snippet) {
|
||||
input_buffer = input_buffer.insert(cursor_position, snippet);
|
||||
cursor_position += snippet.length();
|
||||
|
||||
saved_input_buffer = input_buffer;
|
||||
}
|
||||
void TextInputForm::PushKeyToCurrentPlaceInInputBuffer(const Key& key) {
|
||||
std::string insertion = lowercase(key.Mnemonic);
|
||||
@@ -194,12 +221,30 @@ namespace JUI {
|
||||
}
|
||||
|
||||
if (key == Keys::LeftArrow) {
|
||||
MoveCursorLeft();
|
||||
int qty = 1;
|
||||
// TODO: Move Left until next space.
|
||||
if (InputService::IsKeyDown(Keys::LeftShift)) qty = 5;
|
||||
MoveCursorLeft(qty);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == Keys::RightArrow) {
|
||||
MoveCursorRight();
|
||||
int qty = 1;
|
||||
// TODO: Move Right until next space.
|
||||
if (InputService::IsKeyDown(Keys::LeftShift)) qty = 5;
|
||||
MoveCursorRight(qty);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == Keys::DownArrow) {
|
||||
if (keep_input_history)
|
||||
ShowNextHistory();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == Keys::UpArrow) {
|
||||
if (keep_input_history)
|
||||
ShowPrevHistory();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -218,11 +263,45 @@ namespace JUI {
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextInputForm::ShowNextHistory() {
|
||||
if (keep_input_history) {
|
||||
|
||||
if (history_index > -1)
|
||||
history_index--;
|
||||
|
||||
std::string result = "";
|
||||
if (history_index == -1)
|
||||
SetInputBuffer(saved_input_buffer);
|
||||
else
|
||||
SetInputBuffer(history[history_index]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void TextInputForm::ShowPrevHistory() {
|
||||
if (keep_input_history) {
|
||||
if (history_index == -1 && !history.empty() || history.size()-1 > history_index) {
|
||||
history_index++;
|
||||
}
|
||||
|
||||
std::string result = "";
|
||||
if (history_index == -1)
|
||||
SetInputBuffer(saved_input_buffer);
|
||||
else
|
||||
SetInputBuffer(history[history_index]);
|
||||
}
|
||||
}
|
||||
|
||||
bool TextInputForm::HasFocus() const { return focused; }
|
||||
|
||||
void TextInputForm::SetFocused(bool focused) { this->focused = focused;}
|
||||
void TextInputForm::SetFocused(bool focused) {
|
||||
this->focused = focused;
|
||||
if (focused) time_focused = 0;
|
||||
}
|
||||
|
||||
void TextInputForm::GrabFocus() { SetFocused(true); }
|
||||
void TextInputForm::GrabFocus() {
|
||||
SetFocused(true);
|
||||
}
|
||||
|
||||
void TextInputForm::DropFocus() { SetFocused(false); }
|
||||
|
||||
@@ -250,4 +329,13 @@ namespace JUI {
|
||||
blacklist.insert(value);
|
||||
}
|
||||
|
||||
bool TextInputForm::KeepInputHistory() const { return keep_input_history; }
|
||||
|
||||
void TextInputForm::KeepInputHistory(bool value) {
|
||||
keep_input_history = value;
|
||||
}
|
||||
|
||||
std::string TextInputForm::InputHistory(int index) const {
|
||||
return history[index];
|
||||
}
|
||||
}
|
||||
|
@@ -30,9 +30,9 @@ namespace JUI {
|
||||
Vector2 size = this->font.MeasureString(this->content, this->text_size);
|
||||
//size += pad_shrinks_size_by*2.f;
|
||||
|
||||
if (abs_size.x < size.x)
|
||||
//if (abs_size.x < size.x)
|
||||
this->size.X.Pixels = size.x;
|
||||
if (abs_size.y < size.y)
|
||||
//if (abs_size.y < size.y)
|
||||
this->size.Y.Pixels = size.y;
|
||||
|
||||
}
|
||||
|
@@ -6,14 +6,13 @@ void JUI::Tooltip::Update(float delta) {
|
||||
{
|
||||
auto coords = InputService::GetMousePosition();
|
||||
|
||||
|
||||
auto rel_pos = parent->GetAbsolutePosition();
|
||||
|
||||
auto rel_udim = UDim2::FromPixels(rel_pos.x, rel_pos.y);
|
||||
|
||||
auto anchor = AnchorPoint();
|
||||
|
||||
Position(UDim2::FromPixels(coords.x - rel_pos.x, coords.y - rel_pos.y));
|
||||
Position(UDim2::FromPixels(coords.x - rel_pos.x, coords.y - rel_pos.y - GetAbsoluteSize().y));
|
||||
Visible(true);
|
||||
} else
|
||||
Visible(false);
|
||||
|
@@ -102,7 +102,7 @@ namespace JUI {
|
||||
exit_btn->PressedImageColor(Colors::Reds::DarkRed);
|
||||
|
||||
exit_btn->OnReleaseEvent += [&] (...) {
|
||||
this->Visible(false);
|
||||
this->Close();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -315,8 +315,18 @@ namespace JUI {
|
||||
}
|
||||
|
||||
void Window::SetOpen(bool value) {
|
||||
bool value_changed = value != open;
|
||||
open = value;
|
||||
Visible(open);
|
||||
|
||||
Visible(value);
|
||||
|
||||
if (value_changed) {
|
||||
if (value)
|
||||
OnOpen.Invoke();
|
||||
else
|
||||
OnClose.Invoke();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Window::Toggle() {
|
||||
@@ -346,4 +356,6 @@ namespace JUI {
|
||||
}
|
||||
|
||||
bool Window::IsOpen() const { return open;}
|
||||
}
|
||||
|
||||
bool Window::IsClosed() const { return !open;}
|
||||
}
|
||||
|
Reference in New Issue
Block a user