18 Commits

Author SHA1 Message Date
c8946673c3 Fix layoutorder on file entries spazzing out in non-testbed apps. 2025-07-08 14:33:35 -05:00
d0a246bffe Update file information display in FileDialog. 2025-07-08 13:31:50 -05:00
e084e1120f Trying to solve for arrow angles. 2025-06-28 00:36:53 -05:00
238a7934b3 Adding Anchors and Decorator Widget types. 2025-06-27 19:55:02 -05:00
558f17fc94 Make ScrollingRect key-binds only work when mouse is inside. (This is subject to change) 2025-06-27 15:30:11 -05:00
4acd91b078 ContextMenu now opens on mouse-release versus initial mouse-press. TODO: Make it an enum configuration. 2025-06-27 15:29:17 -05:00
944bd66b43 Refactor CommandLine to not naiively override Window::Open, Close() 2025-06-27 15:28:06 -05:00
d935608e05 Fixed Window close button simply setting the element to invisible and not properly calling Open()/Close() procedure. 2025-06-27 15:27:29 -05:00
3b973c2c45 Added input-history feature to TextInputForm. Control via up/down arrows. 2025-06-27 15:26:33 -05:00
ce0c69190a Testing more of the filesystem API 2025-06-26 21:45:35 -05:00
dca15511ea Other edits. 2025-06-26 06:03:35 -05:00
beb5c97c13 Add UDim::FromPixels and UDim::FromScale constructors 2025-06-26 06:03:27 -05:00
505c2c70e6 Fixed Separator class spacing behavior 2025-06-26 06:03:04 -05:00
9b07a8ea01 Test of FileDialog.hpp 2025-06-26 06:02:43 -05:00
393ad1b2b3 Somewhat Fixed Checkbox drawing. 2025-06-26 06:02:30 -05:00
67416540d4 Typo 2025-06-26 05:36:27 -05:00
d9d92c2a28 Adjusting demo app to take screenshots. 2025-06-25 17:40:56 -05:00
086a3f226b Writing documentation in widgets.md, will deploy doxygen API reference tomorrow. 2025-06-25 02:46:48 -05:00
31 changed files with 3672 additions and 162 deletions

2782
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

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

View 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 {
};
}

View 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:
};
}

View File

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

View File

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

View File

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

View 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:
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -0,0 +1,3 @@
#include <JUI/Mixins/Focusable.hpp>
bool JUI::Focusable::Focused() const { return focused; }

View File

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

View File

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

View File

@@ -160,7 +160,7 @@ namespace JUI {
BaseBGColor(base);
HoveredBGColor(hover);
PressedBGColor(pressed);
DisabledBGColor(disabled);\
DisabledBGColor(disabled);
UpdateVisualState();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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