63 Commits

Author SHA1 Message Date
ee0e92d826 Specify default state of 'Dragging' for Slider. Add Slider::Dragging and Slider::SetDragging 2025-03-19 01:26:25 -04:00
86aa41a3db Added Widget::ObserveMouseWheel virtual method 2025-03-08 16:02:47 -05:00
3486ee03d7 yes 2025-03-02 05:24:51 -05:00
34257eb256 Merge remote-tracking branch 'origin/master' 2025-02-24 14:33:56 -05:00
c4f88c9021 Fix incorrect draw color on TextInputForm rendering the input buffer 2025-02-24 14:33:49 -05:00
052157f484 Windows check 2025-02-19 20:38:04 -06:00
bd02c91c45 Lit & Based 2025-02-19 21:21:18 -05:00
73a461ab56 Add widget counter 2025-02-19 16:54:44 -06:00
efb89dbcee Implement more Style 2025-02-19 16:54:36 -06:00
e607db2ae2 Edits 2025-02-19 16:54:22 -06:00
c7d214c851 Widget::ComputeElementPadding 2025-02-19 16:54:12 -06:00
116c2e2e2f etc 2025-02-18 23:13:50 -05:00
73ef647156 TextInputForm cleanup 2025-02-18 16:21:02 -06:00
bdbfe61b87 TweenAnchorPoint stub 2025-02-18 14:48:39 -05:00
f105053fb2 Edits 2025-02-17 22:35:08 -05:00
f772369686 Refactoring, bug fixes, and implementing styling data. 2025-02-14 15:29:00 -05:00
9cf78742ae Add TextInputForm::ClearTextOnReturn and ::DropFocusOnReturn 2025-02-13 22:41:33 -05:00
3aa9f419a2 Add Scene::GlobalUIScale 2025-02-13 22:41:16 -05:00
c338cc66a4 Update to latest JGL and mcolor 2025-02-11 19:29:51 -05:00
64ee666a83 Update main.cpp demo 2025-02-11 18:35:44 -05:00
13cbaa733c Added ImageButton::HoveredImageColor and other state variants 2025-02-11 18:35:31 -05:00
ad2e3e9a45 Added ImageRect::FitImageToBounds 2025-02-11 18:35:10 -05:00
24d5065f01 Changes to RectBase 2025-02-11 18:34:53 -05:00
bfd898b4f6 Change Window default style, implement Window::TitlebarHeight 2025-02-11 18:33:30 -05:00
2579b587c4 Added ImageBase::Draw overload that takes size of parent object to compute scale for image. 2025-02-11 18:32:49 -05:00
883eabb5e3 Awesome collaborative changes. 2025-02-10 21:07:31 -05:00
24e38b9ec3 Implement Widget tweening and layout order. 2025-02-10 16:42:33 -05:00
1b2fdecc18 Implement sorting of children for ListLayouts. 2025-02-10 16:42:14 -05:00
6e58e30022 Fully implement Tween class 2025-02-10 16:41:56 -05:00
030da85166 Implement UDim::Equals and UDim::Lerp 2025-02-10 16:41:44 -05:00
bd19b29a27 Implement UDim2::Equals and UDim2::Lerp 2025-02-10 16:41:39 -05:00
251daa4070 Work-In-Progress tween code. 2025-02-10 14:45:59 -05:00
fc65e9d229 ImageButton & change window exit to red circle. 2025-02-08 16:04:37 -05:00
2aaba5c1f7 Implement Tween class interface. 2025-02-08 15:35:01 -05:00
97edc67fad Tweening animation test 1. 2025-02-08 14:44:43 -05:00
1053a32317 Renamed several functions, testing tween support. 2025-02-08 04:07:00 -05:00
9a70839d2e Add JUI::Window:AbsoluteViewportPosition and AbsoluteViewportSize 2025-02-08 03:24:39 -05:00
578d15200c Add JUI::Window:LayoutControls Left & Right 2025-02-07 04:09:51 -05:00
15a2bfe667 Merge pull request 'test-jgl-changes' (#57) from test-jgl-changes into master
Reviewed-on: #57
2025-02-07 03:48:34 -05:00
d204d991c1 More Scroller Testing 2025-02-07 03:47:44 -05:00
29f5125ccc Test MSAA 2025-02-07 02:42:30 -05:00
c44e5c830c Logger Window Test!! 2025-02-07 02:40:13 -05:00
c78f9a045c JGL::RenderTarget::Resize appears to have an issue. 2025-02-07 01:56:25 -05:00
7f2477677a Refactoring entire render logic to support ScrollingRect, got some results, but still having issues. 2025-02-07 01:48:22 -05:00
fe454209f7 Other small edits 2025-02-04 19:59:03 -05:00
5673ce3b9e Changes to WIP ScrollingRect 2025-02-04 19:53:43 -05:00
02cd200114 Changes to WIP ScrollingRect 2025-02-04 19:53:40 -05:00
32d9615001 Change behavior of tooltip and UtilityBar 2025-02-04 19:51:42 -05:00
5efbe3fea3 Migrate to latest JGL 2025-02-04 19:51:19 -05:00
1520db8dcd Merge remote-tracking branch 'origin/master' 2025-02-04 19:50:59 -05:00
663355726b Refactor RectBase to allow blit to RenderTarget 2025-02-04 19:43:57 -05:00
83ffecb91d Hoverable class no longer drives tooltips. Rather, a tooltip widget can be parented to any widget. 2025-02-04 19:43:36 -05:00
135a1564f2 Remove Canvas class 2025-02-04 19:41:21 -05:00
d3f29e2072 Fix update chain in Button class 2025-02-04 19:41:11 -05:00
5f7a2326bd CHange showcase icon. 2025-02-04 16:19:33 -06:00
8acf32f140 Update README.md 2025-02-04 17:09:18 -05:00
d89d74052c Update README.md 2025-02-04 17:08:49 -05:00
2e65380ae6 Update README.md 2025-02-04 17:07:45 -05:00
5bf6e2cabc Add 9slice sample image. 2025-02-03 00:15:15 -05:00
593ba22626 Implemented NineSliceRect widget. 2025-02-03 00:13:56 -05:00
27cde1f37c Implemented NineSliceRect widget. 2025-02-02 23:59:15 -05:00
103813e9c1 Fix bug in slider. 2025-01-28 18:59:20 -05:00
0ef7ec2a7f Migrate to latest ReWindow and JGL 2025-01-28 18:35:34 -05:00
60 changed files with 2449 additions and 1120 deletions

View File

@@ -38,7 +38,7 @@ CPMAddPackage(
CPMAddPackage(
NAME mcolor
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-5.zip
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-6.2.zip
)
CPMAddPackage(
@@ -53,12 +53,12 @@ CPMAddPackage(
CPMAddPackage(
NAME ReWindow
URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-31.zip
URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-32.zip
)
CPMAddPackage(
NAME JGL
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-47.zip
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-52.zip
)
target_include_directories(JUI PUBLIC ${Event_SOURCE_DIR}/include)
@@ -70,7 +70,7 @@ target_include_directories(JUI PUBLIC ${ReWindow_SOURCE_DIR}/include)
install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME})
install(FILES ${JUI_HEADERS} DESTINATION include/${PROJECT_NAME})
target_link_libraries(JUI PUBLIC Event J3ML jlog ReWindowLibrary JGL)
target_link_libraries(JUI PUBLIC Event J3ML jlog ReWindow JGL)
add_executable(RedactedJUIDemo main.cpp)
target_link_libraries(RedactedJUIDemo PUBLIC JUI)

View File

@@ -2,33 +2,58 @@
![Static Badge](https://img.shields.io/badge/Lit-Based-%20)
Fourth Time's The Charm! (tm)
Fourth Time's The Charm!
JUI is a C++20 Library for building interactive menus in OpenGL / Redacted3D.
#### JUI is a C++20 Library for building interactive menus in OpenGL / Redacted3D.
It is expressly built with our Redacted3D engine in mind, but steps have been taken to support OpenGL generally.
![JUI First Showcase](showcase.png)
## Abstract
JUI provides a set of objects that we term Widgets. Widgets can be styled and laid out on-screen in relation to each other. Each widget has a single parent, and a list of child elements. Your root widget should be a Scene object.
Provided widgets include Scene, Rect, Text, TextRect, Button, TextButton, TextInputForms, Slider, Image, ImageRect, RadioButton
JUI provides a set of objects that we term Widgets, which can be styled and laid out on-screen in relation to each other. Each widget has a single parent, and a list of child elements. Your root widget should be a Scene object.
## Features
### Why use this instead of imgui?
## Usage
* Comprehensive list of common UI widgets:
* ![Scene](https://git.redacted.cc/josh/ReJUI/wiki/Scene)
* ![Rect](https://git.redacted.cc/josh/ReJUI/wiki/Rect)
* ![Text](https://git.redacted.cc/josh/ReJUI/wiki/Text)
* ![TextRect](https://git.redacted.cc/josh/ReJUI/wiki/TextRect)
* ![Button](https://git.redacted.cc/josh/ReJUI/wiki/Button)
* ![TextButton](https://git.redacted.cc/josh/ReJUI/wiki/TextButton)
* ![TextInputForm](https://git.redacted.cc/josh/ReJUI/wiki/TextInputForm)
* ![Slider](https://git.redacted.cc/josh/ReJUI/wiki/Slider)
* ![Image](https://git.redacted.cc/josh/ReJUI/wiki/Image)
* ![ImageRect](https://git.redacted.cc/josh/ReJUI/wiki/ImageRect)
* ![RadioButton](https://git.redacted.cc/josh/ReJUI/wiki/RadioButton)
* ![Window](https://git.redacted.cc/josh/ReJUI/wiki/Window)
* ![Checkbox](https://git.redacted.cc/josh/ReJUI/wiki/Checkbox)
* ![NineSliceRect](https://git.redacted.cc/josh/ReJUI/wiki/NineSliceRect)
* Vertical and Horizontal ListLayout, GridLayout
* Separator
* ScrollRect
* Extendable - Widgets can be extended via class derivation, and even combined to create complex behavior.
* Low-overhead stateful GUI elements.
* Easy integration with your project. Simply provide update, draw, and user-input callbacks to your scene.
## Examples
![JUI First Showcase](showcase.png)
Browse the src/Demos directories for examples of building and interacting with things in JUI.
Browse the Repository's ![Wiki](https://git.redacted.cc/josh/ReJUI/wiki), linked above, for further explainations and sample code on each widget.
## Dependencies
ReJUI shares dependencies with it's rendering layer, ![JGL](https://git.redacted.cc/josh/JGL).
Currently, the package is also integrated with ![J3ML](https://git.redacted.cc/josh/J3ML) and ![ReWindow](https://git.redacted.cc/Redacted/ReWindow).
`Fedora/RHEL: dnf install cmake make gcc-g++ libX11 libX11-devel mesa-libGL-devel vulkan-loader-devel`
`Ubuntu/Debian: apt-get install cmake make gcc g++ libx11-6 libx11-dev libgl-dev libvulkan-dev libxrandr-dev`
## Documentation
Documentation is automatically generated from latest commit and is hosted at https://doc.redacted.cc/jui .
@@ -37,17 +62,6 @@ Documentation is automatically generated from latest commit and is hosted at htt
Contributions to JUI are welcome! Feel free to file bug reports or feature requests by creating an Issue. Pull requests are also very welcome!
## History
JUI started out as my menu toolkit for the LOVE2D framework many years ago. Between then and now I had re-implemented it twice, once for MonoGame Framework, and again in C++ for SDL2. Legacy versions are listed below.
JUI v1 - LOVE2D / Lua
JUI v2 - MonoGame / C#
JUI v3 - SDL2 / C++
## License
JUI is expressly released without a license, under no restrictions. We dedicate all of our works to the public domain for the (hopeful) betterment of humanity.

BIN
assets/9slice.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

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

View File

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

View File

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

View File

@@ -19,41 +19,14 @@
#include <Color3.hpp>
#include <Color4.hpp>
#include <JGL/JGL.h>
#include <rewindow/types/key.h>
#include <ReWindow/types/Key.h>
#include <JUI/Tween.hpp>
#include <JUI/JUI.hpp>
#include "JUI/DefaultStyle.hpp"
using namespace JGL;
namespace JUI
{
class ITween {};
template <typename T>
class Tween : public ITween
{
public:
std::function<T(void)> getter;
std::function<void(T)> setter;
float lifetime = 5;
float progress = 0;
T goal;
void Update(float dt)
{
}
};
/// Support Lerp/Tween of the following types:
/// int, float, UDim, UDim2, Color4, Vector2,
/// An enumeration for mouse buttons, used by JUI to decouple from external systems.
/// Some boilerplate is required in order to get input mechanisms up and running. See the demo files for reference.
enum class MouseButton {
Left = 1,
Middle = 2,
Right = 3
};
namespace JUI {
using namespace J3ML::Math;
using namespace J3ML::LinearAlgebra;
@@ -77,9 +50,9 @@ namespace JUI
/// An event that triggers when the widget loses focus.
Event<> Unfocused;
/// This event triggers when a new widget is added to this widget, or any descendant of this widget.
Event<Widget *> DescendantAdded;
Event<Widget *> DescendantAdded; // TODO: Debug
/// This event triggers when a widget is removed from this widget's children, or any of it's childrens' children, and so forth.
Event<Widget *> DescendantRemoved;
Event<Widget *> DescendantRemoved; // TODO: Debug
/// This event triggers when the hierarchy this widget is contained within changes.
Event<Widget *, Widget*> AncestryChanged;
/// This event triggers when a widget is added to this widget's list of children.
@@ -89,24 +62,8 @@ namespace JUI
/// This event triggers right before this widget gets deallocated.
Event<Widget *> Destroying;
public:
template <typename T>
void TweenTo(std::function<T()> getter, std::function<void(T)> setter, T goal) { }
/*template <>
void TweenTo(std::function<float()> getter, std::function<void(float)> setter, float goal) { }
template <>
void TweenTo(std::function<UDim()> getter, std::function<void(UDim)> setter, UDim goal) { }
template <>
void TweenTo(std::function<UDim2()> getter, std::function<void(UDim2)> setter, UDim2 goal) { }*/
void TweenFromTo(std::function<UDim2(void)> getter, std::function<void(UDim2)> setter, UDim2 start, UDim2 goal) {
}
Tween* TweenPosition(const UDim2& goal, TweenInfo params = {});
Tween* TweenSize(const UDim2& goal, TweenInfo params = {});
/// Adds a given widget to this widget's list of children.
/// @return The widget in question.
@@ -178,8 +135,16 @@ namespace JUI
bool IsAncestorOf(Widget *descendant);
/// Determines the origin point of a Widget, relative to it's absolute size.
/// TODO: Better explain what this allows for.
[[nodiscard]] Vector2 AnchorPoint() const;
///
void AnchorPoint(const Vector2 &point);
///
Tween* TweenAnchorPoint(const Vector2& goal, TweenInfo info = {});
#pragma region Padding
/// Returns the padding factor on the left of this widget.
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
/// @see PaddingLeft(), class UDim.
@@ -213,6 +178,7 @@ namespace JUI
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
/// @see PaddingLeft(), class UDim.
void PaddingBottom(const UDim &pad_bottom);
/// Sets the padding factor on the four respective sides of this widget.
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
/// @param left
@@ -225,6 +191,17 @@ namespace JUI
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
void Padding(const UDim& padding);
Tween* TweenPaddingLeft(const UDim &goal, TweenInfo info = {});
Tween* TweenPaddingRight(const UDim &goal, TweenInfo info = {});
Tween* TweenPaddingTop(const UDim &goal, TweenInfo info = {});
Tween* TweenPaddingBottom(const UDim &goal, TweenInfo info = {});
Tween* TweenPadding(const UDim& goalLeft, const UDim& goalTop, const UDim& goalRight, const UDim& goalBottom, TweenInfo info = {});
Tween* TweenPadding(const UDim& goal, TweenInfo info = {});
#pragma endregion
#pragma region Margin
/// Returns the margin factor on the left of this widget.
[[nodiscard]] UDim MarginLeft() const;
/// Returns the margin factor on the top of this widget.
@@ -258,6 +235,16 @@ namespace JUI
/// @see Margin(const UDim&, const UDim&, const UDim&, const UDim&).
void Margin(const UDim& margin);
Tween* TweenMarginLeft(const UDim &goal, TweenInfo info = {});
Tween* TweenMarginTop(const UDim &goal, TweenInfo info = {});
Tween* TweenMarginBottom(const UDim &goal, TweenInfo info = {});
Tween* TweenMarginRight(const UDim &goal, TweenInfo info = {});
Tween* TweenMargin(const UDim& goalLeft, const UDim& goalTop, const UDim& goalRight, const UDim& goalBottom, TweenInfo info = {});
Tween* TweenMargin(const UDim& goal, TweenInfo info = {});
#pragma endregion
/// Returns this widgets mnemonic name.
/// Widgets can optionally be assigned a name that can be used to retrieve it from a widget tree node.
/// @see Name().
@@ -284,24 +271,36 @@ namespace JUI
/// Returns whether or not the mouse is inside this widget's approximate bounding-box.
bool IsMouseInside() const;
void AnchorPoint(const Vector2 &point);
int LayoutOrder() const { return layout_order; }
void LayoutOrder(int value) { layout_order = value;}
/// Returns the complete bounding box around this instance that will be rendered onto.
/// This differs from AbsolutePosition and AbsoluteSize in that they are computed for positioning elements relative to each other.
/// Only this method represents the full bounding box of the widget.
[[nodiscard]] virtual AABB2D GetActualRenderBounds() const;
AABB2D AbsoluteBounds() const;
void SetViewportSize(const Vector2& vps);
float ComputeElementPadding(float size, const UDim& padding) {
return padding.Pixels + (padding.Scale * size);
}
Vector2 ComputeElementPadding(const Vector2& size, const UDim2& padding) const;
public:
// TODO: Consider calling J2D::Begin here.
virtual void PreDraw() {}
// TODO: Consider calling J2D::End here.
virtual void PostDraw() {}
// TODO: Consider calling J2D::End here.
virtual void InnerDraw() {}
/// Renders the widget to the current OpenGL Context using JGL.
/// The user should call this on their Scene instances only. JUI will handle the rest.
virtual void Draw();
@@ -319,11 +318,19 @@ namespace JUI
/// See ReWindowIntegrationDemo for an example.
virtual void ObserveMouseInput(MouseButton btn, bool pressed);
/// Informs a widget that the mouse wheel has been moved.
/// This is designed in such a way that the end-user can plug this into their existing code.
/// The user should call this on their Scene instances only. JUI will handle the rest.
/// See ReWindowIntegrationDemo for an example.
virtual void ObserveMouseWheel(int mwheel);
/// Informs a widget that a key has been pressed or released.
/// This is designed in such a way that the end-user can plug this into their existing code.
/// The user should call this on their Scene instances only. JUI will handle the rest.
/// See ReWindowIntegrationDemo for an example.
virtual void ObserveKeyInput(Key key, bool pressed);
protected:
void DrawChildWidgets();
void UpdateChildWidgets(float delta);
@@ -331,33 +338,31 @@ namespace JUI
MouseButton mbtn;
bool mb_state = false;
bool prev_mb_state = false;
//int last_known_mouse_button;
//bool last_known_mouse_button_state;
Vector2 last_known_mouse_pos = {0,0};
UDim2 position = {0_px, 0_px};
UDim2 size = {50_px, 50_px};
Widget* parent = nullptr;
std::vector<Widget*> children;
std::vector<Tween*> tweens;
float rotation = 0;
std::string name;
bool selected = false;
UDim pad_left = 0_px;
UDim pad_right = 0_px;
UDim pad_top = 0_px;
UDim pad_bottom = 0_px;
UDim margin_left = 0_px;
UDim margin_right = 0_px;
UDim margin_top = 0_px;
UDim margin_bottom = 0_px;
UDim pad_left = Style::BasePadding;
UDim pad_right = Style::BasePadding;
UDim pad_top = Style::BasePadding;
UDim pad_bottom = Style::BasePadding;
UDim margin_left = Style::BasePadding;
UDim margin_right = Style::BasePadding;
UDim margin_top = Style::BasePadding;
UDim margin_bottom = Style::BasePadding;
Vector2 anchor_point = {0.f, 0.f};
bool visible = true;
Widget* next = nullptr;
Widget* prev = nullptr;
int zindex = 0;
int layout_order = 0;
Vector2 viewport_size{0,0};
/// Returns the amount of pixels this widget will be padded by from the top-left.
/// Generally, the widget will be shrunk and moved over by this amount, relative to the parent.
Vector2 GetAbsolutePaddingTopLeft() const;
@@ -365,11 +370,8 @@ namespace JUI
/// Returns the amount of pixels this widget will be padded by from bottom-right.
/// Generally, the widget will be shrunk by twice this amount, relative to the parent.
Vector2 GetAbsolutePaddingBottomRight() const;
Vector2 GetAbsoluteMarginTopLeft();
Vector2 GetAbsoluteMarginBottomRight();
void UpdateTweens(float elapsed);
};
}

View File

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

View File

@@ -5,4 +5,12 @@
namespace JUI
{
extern jlog::GenericLogger UILogs;
/// An enumeration for mouse buttons, used by JUI to decouple from external systems.
/// Some boilerplate is required in order to get input mechanisms up and running. See the demo files for reference.
enum class MouseButton {
Left = 1,
Middle = 2,
Right = 3
};
}

View File

@@ -31,11 +31,11 @@ namespace JUI
virtual void OnExit(const Vector2& MousePos);
void Update(const Vector2& m_pos, float delta);
virtual void SetTooltip(const std::string& content, float delay) {}
//virtual void SetTooltip(const std::string& content, float delay) {}
protected:
Tooltip* tooltip = nullptr;
float tooltip_threshold = 0.f;
float tooltip_limit = 0.125f;
//Tooltip* tooltip = nullptr;
//float tooltip_threshold = 0.f;
//float tooltip_limit = 0.125f;
bool hovered = false;
bool hover_debounce = false;
};

134
include/JUI/Tween.hpp Normal file
View File

@@ -0,0 +1,134 @@
#pragma once
#include <functional>
#include "Event.h"
namespace JUI
{
using EasingFunc = std::function<float(float)>;
using TweenTickFunc = std::function<void(float, float)>;
namespace EasingFunctions
{
float EaseInOutLinear(float t);
/// Speed is determined by a sine wave for gentle easing motion.
float EaseInSine(float t);
/// Speed is determined by a sine wave for gentle easing motion.
float EaseOutSine(float t);
/// Speed is determined by a sine wave for gentle easing motion.
float EaseInOutSine(float t);
/// Similar to Sine but with a slightly sharper curve based on quadratic interpolation.
float EaseInQuad(float t);
/// Similar to Sine but with a slightly sharper curve based on quadratic interpolation.
float EaseOutQuad(float t);
/// Similar to Sine but with a slightly sharper curve based on quadratic interpolation.
float EaseInOutQuad(float t);
/// Similar to Quad but with a slightly sharper curve based on cubic interpolation.
float EaseInCubic(float t);
/// Similar to Quad but with a slightly sharper curve based on cubic interpolation.
float EaseOutCubic(float t);
/// Similar to Quad but with a slightly sharper curve based on cubic interpolation.
float EaseInOutCubic(float t);
/// Similar to Cubic but with an even sharper curve based on quartic interpolation.
float EaseInQuart(float t);
/// Similar to Cubic but with an even sharper curve based on quartic interpolation.
float EaseOutQuart(float t);
/// Similar to Cubic but with an even sharper curve based on quartic interpolation.
float EaseInOutQuart(float t);
/// Similar to Quart but with an even sharper curve based on quintic interpolation.
float EaseInQuint(float t);
/// Similar to Quart but with an even sharper curve based on quintic interpolation.
float EaseOutQuint(float t);
/// Similar to Quart but with an even sharper curve based on quintic interpolation.
float EaseInOutQuint(float t);
/// The sharpest curve based on exponential interpolation.
float EaseInExpo(float t);
/// The sharpest curve based on exponential interpolation.
float EaseOutExpo(float t);
/// The sharpest curve based on exponential interpolation.
float EaseInOutExpo(float t);
/// Follows a circular arc, such that acceleration is more sudden and deceleration more gradual versus Quint or Exponential.
float EaseInCirc(float t);
/// Follows a circular arc, such that acceleration is more sudden and deceleration more gradual versus Quint or Exponential.
float EaseOutCirc(float t);
/// Follows a circular arc, such that acceleration is more sudden and deceleration more gradual versus Quint or Exponential.
float EaseInOutCirc(float t);
/// Slightly overshoots the target, then backs into place.
float EaseInBack(float t);
/// Slightly overshoots the target, then backs into place.
float EaseOutBack(float t);
/// Slightly overshoots the target, then backs into place.
float EaseInOutBack(float t);
float EaseInElastic(float t);
float EaseOutElastic(float t);
float EaseInOutElastic(float t);
float EaseInBounce(float t);
float EaseOutBounce(float t);
float EaseInOutBounce(float t);
}
struct TweenInfo {
float time = 1.f;
float delay = 0.f;
int repeats = 0;
bool reverse = false;
EasingFunc easing = &EasingFunctions::EaseInOutLinear;
};
/// A class that represents an animation-in-action.
class Tween {
public:
Tween(TweenTickFunc tick_func);
Tween(TweenTickFunc tick_func, TweenInfo info) {
this->tick_func = tick_func;
this->time = info.time;
this->delay_time = info.delay;
this->repeat_count = info.repeats;
this->reverses = info.reverse;
this->easing_func = info.easing;
}
Event<> Completed;
void Update(float elapsed);
std::function<float(float)> easing_func = &EasingFunctions::EaseInOutLinear;
std::function<void(float, float)> tick_func;
/// Duration of the tween, in seconds.
float time = 5;
/// Time of delay until the tween begins, in seconds.
float delay_time = 0;
/// Number of times the tween repeats. -1 indicates indefinite repetition.
int repeat_count = 0; // TODO: Implement
/// Whether or not the tween interpolates in reverse once the initial tween completes.
bool reverses = false; // TODO: Implement
float progress = 0;
bool alive = true;
bool paused = false;
bool completed = false;
void Cancel();
void ForceFinish();
void Pause() { paused = true; }
void Resume() { paused = false; }
void Start();
bool Paused() const;
bool HasCompleted() const;
};
}

View File

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

View File

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

View File

@@ -105,17 +105,18 @@ namespace JUI
void OnRelease(const Vector2& mouse_pos, const MouseButton& bnt, bool still_hovering) override;
void OnHover(const J3ML::LinearAlgebra::Vector2 &MousePos) override;
void OnExit(const J3ML::LinearAlgebra::Vector2 &MousePos) override;
void SetTooltip(const std::string &content, float delay = 0.2f) override;
//void SetTooltip(const std::string &content, float delay = 0.2f) override;
protected:
bool disabled = false;
Color4 hover_bg = Colors::Blues::SkyBlue;
Color4 hover_border = Colors::Blues::DarkSlateBlue;
Color4 pressed_bg = Colors::Blues::DarkSlateBlue;
Color4 pressed_border = Colors::Blues::SkyBlue;;
Color4 disabled_bg = Colors::Gray;
Color4 disabled_border = Colors::Gray;
Color4 base_bg = Colors::White;
Color4 base_border = Colors::Black;
Color4 hover_bg = Style::Button::HoverBackgroundColor;
Color4 hover_border = Style::Button::HoverBorderColor;
Color4 pressed_bg = Style::Button::PressBackgroundColor;
Color4 pressed_border = Style::Button::PressBorderColor;
Color4 disabled_bg = Style::Button::DisableBackgroundColor;
Color4 disabled_border = Style::Button::DisableBorderColor;
Color4 base_bg = Style::Button::BaseBackgroundColor;
Color4 base_border = Style::Button::BaseBorderColor;
void UpdateVisualState();
};

View File

@@ -1,24 +0,0 @@
/// 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 Canvas.hpp
/// @desc A 2D drawable canvas widget.
/// @edit 2024-11-27
#pragma once
#include <JUI/Widgets/Rect.hpp>
namespace JUI
{
class Canvas : public Rect
{
public:
protected:
private:
};
}

View File

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

View File

@@ -1,5 +1,44 @@
/// Josh's User Interface Library
/// A C++20 Library for creating, styling, and rendering of a UI/UX widgets.
/// Developed and Maintained by Josh O'Leary @ Redacted Software.
/// Special Thanks to William Tomasine II and Maxine Hayes.
/// (c) 2025 Redacted Software
/// This work is dedicated to the public domain.
/// @file Collapsible.cpp
/// @desc A rectangular widget with a clickable header that enables hiding and showing the contents.
/// @edit 2025-02-14
#pragma once
namespace JUI
{
#include <JUI/Widgets/Rect.hpp>
#include <JUI/Widgets/TextButton.hpp>
namespace JUI {
class Collapsible : public Rect {
public:
Collapsible();
explicit Collapsible(Widget* parent);
TextButton* Header();
Rect* ContentBox();
void Collapse();
void Expand();
[[nodiscard]] bool Collapsed() const;
UDim HeaderHeight() const;
void HeaderHeight(const UDim& value);
void Title(const std::string& value);
std::string Title() const;
protected:
TextButton* header = nullptr;
Rect* content_box = nullptr;
TextRect* lil_arrow = nullptr;
bool collapsed = false;
UDim2 saved_size;
UDim header_height = Style::Collapsible::HeaderHeight;
};
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,83 @@
/// 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 NineSlice.hpp
/// @desc A widget that implements 9-slice scaling on an image.
/// @edit 2025-2-2
/// https://en.wikipedia.org/wiki/9-slice_scaling
#pragma once
#include <JUI/Base/Widget.hpp>
#include <JUI/Widgets/Rect.hpp>
#include <JUI/Base/ImageBase.hpp>
namespace JUI
{
class NineSliceRect : public Rect, public ImageBase {
public:
NineSliceRect();
explicit NineSliceRect(Widget* parent);
NineSliceRect(Widget* parent, JGL::Texture* texture);
/// Returns the bounds of the 'Top-Left' slice of the 9-slice.
[[nodiscard]] AABB2D TopLeftQuad() const;
/// Returns the bounds of the 'Top-Right' slice of the 9-slice.
[[nodiscard]] AABB2D TopRightQuad() const;
/// Returns the bounds of the 'Bottom-Left' slice of the 9-slice.
[[nodiscard]] AABB2D BottomLeftQuad() const;
/// Returns the bounds of the 'Bottom-Right' slice of the 9-slice.
[[nodiscard]] AABB2D BottomRightQuad() const;
/// Returns the bounds of the 'Top' slice of the 9-slice.
[[nodiscard]] AABB2D TopQuad() const;
/// Returns the bounds of the 'Left' slice of the 9-slice.
[[nodiscard]] AABB2D LeftQuad() const;
/// Returns the bounds of the 'Right' slice of the 9-slice.
[[nodiscard]] AABB2D RightQuad() const;
/// Returns the bounds of the 'Bottom' slice of the 9-slice.
[[nodiscard]] AABB2D BottomQuad() const;
/// Returns the bounds of the 'Center' slice of the 9-slice.
[[nodiscard]] AABB2D CenterQuad() const;
/// Sets the bounds of the quadrant for the 'Top-Left' slice of the 9-slice.
void TopLeftQuad(const AABB2D& quad);
/// Sets the bounds of the quadrant for the 'Top-Right' slice of the 9-slice.
void TopRightQuad(const AABB2D& quad);
/// Sets the bounds of the quadrant for the 'Bottom-Left' slice of the 9-slice.
void BottomLeftQuad(const AABB2D& quad);
/// Sets the bounds of the quadrant for the 'Bottom-Right' slice of the 9-slice.
void BottomRightQuad(const AABB2D& quad);
/// Sets the bounds of the quadrant for the 'Top' slice of the 9-slice.
void TopQuad(const AABB2D& quad);
/// Sets the bounds of the quadrant for the 'Right' slice of the 9-slice.
void RightQuad(const AABB2D& quad);
/// Sets the bounds of the quadrant for the 'Bottom' slice of the 9-slice.
void BottomQuad(const AABB2D& quad);
/// Sets the bounds of the quadrant for the 'Left' slice of the 9-slice.
void LeftQuad(const AABB2D& quad);
/// Sets the bounds of the quadrant for the 'Center' slice of the 9-slice.
void CenterQuad(const AABB2D& quad);
void Draw() override;
protected:
AABB2D top_left_quad;
AABB2D top_right_quad;
AABB2D bottom_left_quad;
AABB2D bottom_right_quad;
AABB2D top_quad;
AABB2D bottom_quad;
AABB2D right_quad;
AABB2D left_quad;
AABB2D center_quad;
};
}

View File

@@ -1,19 +0,0 @@
#pragma once
#include <JUI/Base/Widget.hpp>
#include <JUI/Widgets/Rect.hpp>
namespace JUI
{
class NineSliceImage
{
};
class NineSliceRect : public Rect, public NineSliceImage
{
};
}

View File

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

View File

@@ -15,22 +15,31 @@ namespace JUI {
class ScrollingRect;
}
// TODO: Fix direction of ScrollBar itself
// TODO: MouseWheel scroll, and ScrollBar clickable
// TODO: Clamp range of ScrollBar
/// A Rectangle Widget which has a larger renderable area than the visible area.
/// This allows user-controlled scrolling of the viewable content.
class JUI::ScrollingRect : public Rect {
protected:
bool vertical_scrollbar_enabled = true;
bool horizontal_scrollbar_enabled = true;
bool scrollbar_visible = true;
float scrollbar_width = 12;
Color4 scrollbar_color = Colors::Whites::Azure;
float scroll = 0;
Vector2i canvas_size {200, 200};
JGL::RenderTarget* canvas = nullptr;
protected:
/* This isn't public because nothing should ever
* have to do this from the outside. -Redacted */
void RecomputeRenderTarget();
public:
JGL::RenderTarget* GetCanvas() { return canvas; }
[[nodiscard]] Vector2i CanvasSize() const { return canvas_size; }
float scroll_size = 0;
JGL::RenderTarget* GetCanvas();
[[nodiscard]] Vector2i CanvasSize() const;
[[nodiscard]] float ScrollPos() const { return scroll; }
[[nodiscard]] Vector2 CanvasPosition() const;
// TODO scrolling in either direction. Assuming vertical scroll for now.
@@ -41,8 +50,27 @@ public:
void CanvasSize(const Vector2i& new_size);
void InnerDraw() override;
void Draw() override;
void Update(float delta) override {
//scroll += delta*5;
//canvas->Resize(Vector2i(GetAbsoluteSize().x, GetAbsoluteSize().y));
Rect::Update(delta);
}
void ObserveKeyInput(Key key, bool pressed) override
{
if (key == Keys::UpArrow && pressed)
{
scroll -= 10;
}
if (key == Keys::DownArrow && pressed)
{
scroll += 10;
}
}
public:
~ScrollingRect() override { delete canvas; }
ScrollingRect() : canvas(new RenderTarget(canvas_size, {0, 0, 0, 0})) {};
explicit ScrollingRect(Widget* parent) : Rect(parent), canvas(new RenderTarget(canvas_size, {0, 0, 0, 0})) {};
~ScrollingRect() override;
ScrollingRect();
explicit ScrollingRect(Widget* parent);;
};

View File

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

View File

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

View File

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

View File

@@ -2,22 +2,40 @@
#include <JUI/Widgets/TextRect.hpp>
#include "ReWindow/InputService.h"
namespace JUI
{
class Tooltip : public TextRect
{
public:
Tooltip() : TextRect()
{
Tooltip() : TextRect() {
Name("Tooltip");
}
explicit Tooltip(Widget* parent) : Tooltip()
{
this->Parent(parent);
this->AutoFitSizeToText(true);
this->ZIndex(10);
this->Visible(false);
this->Size({100_px, 20_px});
}
void Update(float delta) override
{
if (parent && parent->IsMouseInside() || IsMouseInside())
{
auto coords = InputService::GetMousePosition();
Position(UDim2::FromPixels(coords.x, coords.y));
Visible(true);
} else
Visible(false);
TextRect::Update(delta);
}
~Tooltip() override {};
protected:

View File

@@ -6,10 +6,18 @@ namespace JUI
class ContextMenu : public Rect {
public:
ContextMenu() : Rect() {
Name("ContextMenu");
this->BGColor(Colors::White);
this->Margin(2_px);
this->Size({200, 200, 0, 0});
layout = new VerticalListLayout(this);
MouseExit += [this] (Vector2 _)
{
this->Visible(false);
this->Parent(nullptr);
// TODO: Collect
};
}
explicit ContextMenu(Widget* parent) : ContextMenu()
@@ -52,7 +60,7 @@ namespace JUI
this->Position({0,0,0,0});
this->BGColor(Colors::White);
this->BorderColor(Colors::Blues::CornflowerBlue);
this->SetBorderWidth(2);
this->BorderWidth(2);
this->Margin(2_px);
this->BorderMode(BorderMode::Outline);
layout = new HorizontalListLayout(this);
@@ -83,7 +91,7 @@ namespace JUI
btn->SetTextSize(14);
btn->SetTextColor(Colors::Black);
btn->Size({static_cast<int>(str_width.x)+16, 0, 0, 1});
btn->SetBorderWidth(0.f);
btn->BorderWidth(0.f);
btn->SetContent(name);
return btn;
}

View File

@@ -23,12 +23,12 @@
#include <JUI/Mixins/Hoverable.hpp>
#include <JUI/Mixins/Clickable.hpp>
#include <JUI/Widgets/TextButton.hpp>
#include <JUI/Widgets/ImageButton.hpp>
namespace JUI
{
using J3ML::LinearAlgebra::Vector2;
class DockingStation {};
/// A container widget class, with title bar and buttons,
@@ -39,7 +39,7 @@ namespace JUI
/// The default constructor sets a default style for this Window.
Window();
/// Construct a window widget by specifying it's parent.
Window(Widget* parent);
explicit Window(Widget* parent);
/// Returns the current size (in x,y pixels) of the Window widget.
[[nodiscard]] Vector2 CurrentSize() const;
@@ -53,10 +53,11 @@ namespace JUI
/// Sets the maximum size (in x,y pixels) that the Window widget is allowed to be.
void MaxSize(const Vector2& constraint);
/// Returns the height (in pixels) of the Window's titlebar.
// TODO: Decide if this will auto-scale with the titlebar's text height, or the other way around.
int TitlebarHeight() const;
[[nodiscard]] int TitlebarHeight() const;
void TitlebarHeight(int height);
/// Returns the text displayed as the Window's title.
[[nodiscard]] std::string Title() const;
@@ -65,7 +66,6 @@ namespace JUI
/// @see class Draggable.
bool IsDraggable() const;
void SetDraggable(bool value);
/// Returns whether this Window is able to be 'Docked' into another widget.
@@ -74,29 +74,40 @@ namespace JUI
void SetDockable(bool value);
/// Align topbar buttons to the left.
void LayoutControlsLeft();
/// Align topbar buttons to the right.
void LayoutControlsRight();
/// Returns whether resizing the window via right-click is enabled.
/// @see class Resizable
bool IsResizable() const;
void SetResizable(bool value);
void CornerRounding(float radius) override {
RectBase::CornerRounding(radius);
}
/// Returns a pointer to the Text Widget that is used to render the title bar's text.
Text* GetTitleInstance();
Text* TitleInstance();
/// Returns a pointer to the Rect Widget that is used to layout the title bar contents.
Rect* GetTopbarInstance();
Rect* TopbarInstance();
/// Returns a pointer to the Rect Widget that is used to layout the contents of the window.
Rect* GetViewportInstance();
Rect* ViewportInstance();
/// Returns a pointer to the Exit Button Widget.
TextButton* GetExitButtonInstance();
ImageButton* ExitButtonInstance();
Vector2 AbsoluteViewportPosition() const;
Vector2 AbsoluteViewportSize() const;
AABB2D AbsoluteViewportBounds() const;
/// Sets the font used by the title-bar text on this Window.
void SetTitleFont(const Font& f);
/// Toggles whether this window is actively being dragged by the mouse.
/// @see class Draggable.
void SetDrag(bool d) override;
/// @see class Widget.
void Update(float delta) override;
/// @see class Widget.
@@ -106,17 +117,20 @@ namespace JUI
/// @see class Clickable.
void OnRelease(const Vector2& m_pos, const MouseButton& m_btn, bool still_hovering) override;
protected:
void UpdateInternalWidgetsTitlebarHeight();
protected:
JUI::Rect* Topbar;
JUI::Rect* Viewport;
JUI::Text* TitleLabel;
JUI::TextButton* exit_btn;
JUI::TextButton* fs_btn;
JUI::ImageButton* exit_btn;
JUI::ImageButton* fs_btn;
std::string title = "JUI Window";
bool resizable = true;
//bool resizing = false;
bool draggable = true;
bool dockable = false;
int titlebar_height = 20;
int titlebar_height = Style::Window::TitlebarHeight;
int title_font_size = 16;
Vector2 max_size;
Vector2 min_size; //= {30, 30};

384
main.cpp
View File

@@ -24,37 +24,66 @@
#include <JUI/Widgets/UtilityBar.hpp>
#include <JUI/Widgets/Checkbox.hpp>
#include <JUI/Widgets/TextInputForm.hpp>
#include <rewindow/types/window.h>
#include <ReWindow/types/Window.h>
#include <ReWindow/Logger.h>
#include "JUI/Widgets/NineSlice.hpp"
#include <JUI/Widgets/Collapsible.hpp>
JUI::Scene* scene;
JGL::Texture* sample_texture;
JGL::Texture* slicer;
JUI::VerticalListLayout* list;
JUI::ScrollingRect* scroller;
JUI::TextRect* widget_count;
int count_descendants(JUI::Widget* w) {
return w->GetDescendants().size();
}
JUI::Scene* CreateScene() {
using namespace JUI;
Scene *root = new Scene();
auto *root = new Scene();
/*root->DescendantAdded += [&] (auto* node) {
widget_count->SetContent("Widgets: " + count_descendants(root));
};
root->DescendantRemoved += [&] (auto* node) {
widget_count->SetContent("Widgets: " + count_descendants(root));
};*/
auto* nineslice_demo_window = new JUI::Window(root);
nineslice_demo_window->Name("NineSlice Demo Window");
nineslice_demo_window->CornerRounding(10);
nineslice_demo_window->Size({50_percent, 50_percent});
nineslice_demo_window->SetTitle("9-Slice Demo");
nineslice_demo_window->Visible(false);
nineslice_demo_window->TitlebarHeight(12);
auto* topbar = new UtilityBar(root);
topbar->ZIndex(3);
auto* file = topbar->AddButton("File");
file->SetTooltip("This Dick Nigga", 0.5f);
auto* file = topbar->AddButton("Demos");
file->OnClickEvent += [&, root, file] (Vector2 pos, JUI::MouseButton btn)
auto* file_tt = new JUI::Tooltip(file);
file_tt->SetContent("Tooltip");
file->OnClickEvent += [&, root, nineslice_demo_window] (Vector2 pos, JUI::MouseButton btn)
{
auto* ctx_menu = new ContextMenu(root);
ctx_menu->Position(UDim2(0,20,0,0));
ctx_menu->AddItem("A");
ctx_menu->AddItem("B");
ctx_menu->AddItem("c");
ctx_menu->ZIndex(3);
auto* open_nineslice = ctx_menu->AddItem("9-Slice Widget Demo");
ctx_menu->MouseExit += [ctx_menu] (Vector2 _)
{
ctx_menu->Visible(false);
ctx_menu->Parent(nullptr);
// TODO: Collect
open_nineslice->OnClickEvent += [&, nineslice_demo_window] (Vector2 pos, JUI::MouseButton btn) {
nineslice_demo_window->Visible(true);
};
auto* open_scroll = ctx_menu->AddItem("Scroll Widget Demo");
open_scroll->OnClickEvent += [&] (Vector2 pos, JUI::MouseButton btn) {};
ctx_menu->AddItem("");
ctx_menu->ZIndex(3);
};
topbar->AddButton("Edit");
@@ -62,181 +91,163 @@ JUI::Scene* CreateScene() {
topbar->AddButton("Help");
widget_count = new JUI::TextRect(topbar);
widget_count->Size(UDim2(100,20,0,0));
widget_count->AnchorPoint({1, 0});
widget_count->Position({100_percent, 0_percent});
widget_count->BGColor(Colors::Transparent);
widget_count->SetTextColor(Colors::Black);
widget_count->BorderColor(Colors::Transparent);
widget_count->BorderWidth(0);
widget_count->Center();
widget_count->AlignRight();
//auto* horizontal = new HorizontalListLayout(root);
//horizontal->ZIndex(1);
auto* sizer_1 = new Rect(root);
//sizer_1->ZIndex(4);
sizer_1->Size({0,-24,0.2f, 1.f});
sizer_1->Position({0, 24, 0, 0});
sizer_1->BGColor(Colors::Grays::Gainsboro);
auto* column_rect = new Rect(root);
//column_rect->ZIndex(4);
column_rect->Size({0, -24, 0.2f, 1.f});
column_rect->Position({0, 24, 0, 0});
column_rect->BGColor(Colors::Grays::Gainsboro);
auto* s1_vert = new VerticalListLayout(sizer_1);
auto* column_layout = new VerticalListLayout(column_rect);
auto* button = new TextButton(s1_vert);
auto* btn_container1 = new Rect(column_layout);
btn_container1->Size({100_percent, 24_px});
auto* btn_h_layout1 = new HorizontalListLayout(btn_container1);
btn_h_layout1->Padding({2_px});
auto* button = new TextButton(btn_h_layout1);
//button->Position({5, 105, 0, 0});
button->Size({0, 35, 1, 0});
button->Size({0, 20, 0.32f, 0});
button->SetTextColor(Colors::Black);
button->SetContent("Button");
button->AlignLeft();
button->Padding(5_px);
//button->Padding(5_px);
auto* button2 = new TextButton(s1_vert);
auto* tt2 = new JUI::Tooltip(button);
tt2->SetContent("Test 123");
auto* button2 = new TextButton(btn_h_layout1);
//button2->Position({5, 105, 0, 0});
button2->Size({0, 35, 1, 0});
button2->Size({0, 20, 0.32f, 0});
button2->SetTextColor(Colors::Black);
button2->SetContent("Button");
button2->AlignCenterHorizontally();
auto* button3 = new TextButton(s1_vert);
auto* button3 = new TextButton(btn_h_layout1);
//button2->Position({5, 105, 0, 0});
button3->Size({0, 35, 1, 0});
button3->Size({0, 20, 0.32f, 0});
button3->SetTextColor(Colors::Black);
button3->SetContent("Button");
button3->AlignRight();
auto* checkbox_container = new Rect(s1_vert);
checkbox_container->Size({0, 35, 1, 0});
auto* btn_container2 = new Rect(column_layout);
btn_container2->Size({100_percent, 24_px});
btn_container2->BGColor(Colors::DarkGray);
auto* btn_h_layout2 = new HorizontalListLayout(btn_container2);
btn_h_layout2->Padding({2_px});
auto* button4 = new TextButton(btn_h_layout2);
//button->Position({5, 105, 0, 0});
button4->Size({0, 20, 0.32f, 0});
button4->SetTextColor(Colors::Black);
button4->SetContent("Button");
button4->AlignLeft();
//button4->CornerRounding(4);
auto* button5 = new TextButton(btn_h_layout2);
//button2->Position({5, 105, 0, 0});
button5->Size({0, 20, 0.32f, 0});
button5->SetTextColor(Colors::Black);
button5->SetContent("Button");
button5->AlignCenterHorizontally();
//button5->CornerRounding(4);
auto* button6 = new TextButton(btn_h_layout2);
//button2->Position({5, 105, 0, 0});
button6->Size({0, 20, 0.32f, 0});
button6->SetTextColor(Colors::Black);
button6->SetContent("Button");
button6->AlignRight();
//button6->CornerRounding(4);
auto* checkbox_container = new Rect(column_layout);
checkbox_container->Size({0, 24, 1, 0});
auto* checkbox_horiz = new HorizontalListLayout(checkbox_container);
checkbox_horiz->Padding(2_px);
auto* label = new TextRect(checkbox_horiz);
label->SetContent("Checkboxes");
label->BorderWidth(0);
label->AutoFitSizeToText(true);
auto* check1 = new Checkbox(checkbox_horiz);
check1->Size({20_px, 20_px});
auto* check2 = new Checkbox(checkbox_horiz);
check2->Size({20_px, 20_px});
check2->CheckedColor(Colors::Blue);
check2->CornerRounding(7);
auto* check3 = new Checkbox(checkbox_horiz);
check3->Size({20_px, 20_px});
check3->CheckedColor(Colors::Oranges::Coral);
check3->CornerRounding(7);
auto* input_form = new TextInputForm(s1_vert);
input_form->Size({0,30, 1, 0});
auto* input_form = new TextInputForm(column_layout);
input_form->Size({0,24, 1, 0});
input_form->SetContent("");
input_form->SetTextSize(14);
//auto* main_wnd = new Window(root);
//auto* left = new HorizontalListLayout();
auto* collapsible = new Collapsible(column_layout);
collapsible->Size({100_percent, 200_px});
auto* other_window = new JUI::Window(root);
other_window->Position({10_percent, 10_percent});
other_window->Size({30_percent, 25_percent});
other_window->SetTitle("Another Window");
//auto* btn = new TextButton(root);
Tween* t = other_window->TweenPosition({50_percent, 50_percent}, {.time = 5});
t->Completed += [] () { std::cout << "Tween type test!!" << std::endl; };
/*
auto* JUI = new TextRect(root);
JUI->SetClipsDescendants(true);
JUI->SetFont(FreeSans);
JUI->SetTextSize(48);
JUI->SetTextColor({32, 48, 192});
JUI->SetContent("Josh User Interface");
JUI->AlignBottom();
JUI->AlignCenterHorizontally();
JUI->AnchorPoint(Vector2(0.5f, 0.5f));
JUI->Position({50_percent, 50_percent});
//JUI->Size({30_percent, 10_percent});
JUI->FitText(true);
JUI->Padding(20_px);
JUI->BGColor({64, 64, 64, 128});
JUI->SetBorderWidth(2);
JUI->BorderColor({255, 255, 255, 128});
auto* Redacted = new TextRect(root);
Redacted->SetFont(FreeSans);
Redacted->SetTextSize(32);
Redacted->SetTextColor({255, 255, 255});
Redacted->SetContent("Redacted Software Group");
Redacted->Position({50_percent, 60_percent});
//Redacted->Size({30_percent, 10_percent});
Redacted->AlignCenterHorizontally();
Redacted->AlignTop();
Redacted->AnchorPoint(Vector2(0.5f, .5f));
Redacted->FitText(true);
Redacted->Padding(10_px);
Redacted->BGColor({32, 48, 192});
Redacted->SetBorderWidth(1);
Redacted->BorderColor({64, 64, 64});
scroller = new JUI::ScrollingRect(other_window->ViewportInstance());
scroller->Size({100_percent, 100_percent});
scroller->BGColor(Colors::Reds::LightCoral);
list = new JUI::VerticalListLayout(scroller);
list->LayoutOrder(JUI::LayoutOrder::V::BOTTOM);
//auto* scrollRect = new ScrollingRect(root);
// Rect //
Rect *rect_element = new Rect(root);
rect_element->Name("JimBob");
//Rect* element = new Rect(root);
//rect_element->Name("JimBob");
//element->BGColor({0,255,0});
rect_element->BGColor({0, 64, 0});
rect_element->Size({0, 0, 0.1f, 0.2f});
rect_element->SetClipsDescendants(true);
rect_element->BorderColor({255, 255, 255});
rect_element->SetBorderWidth(2.f);
rect_element->MouseEnter += [rect_element](auto coords) {
rect_element->BGColor({0, 128, 0});
};
rect_element->MouseExit += [rect_element](auto coords) {
rect_element->BGColor({0, 64, 0});
};
// End Rect //
Slider* slide = new Slider(root);
slide->Size({200, 40, 0, 0});
slide->BGColor({255, 255, 0});
slide->Position({0, 0, 0.125, 0.125});
// Button //
Button *button_element = new Button(root);
button_element->Name("BobJim");
button_element->BaseBGColor({64, 0, 64});
button_element->PressedBGColor({0, 64, 64});
button_element->Size({0, 0, 0.1f, 0.1f});
button_element->Position({0,0,0.25f,0.25f});
//button_element->SetClipsDescendants(true);
button_element->BaseBorderColor({255, 255, 255});
button_element->SetBorderWidth(2.f);
auto bpos = rect_element->Size();
//exit(1);
//button_element->Position(
// {bpos.X.Pixels, bpos.Y.Pixels, bpos.X.Scale - 0.2f, 0}); // I don't know how to use sx and sy - maxine
//button_element->OnToggleOnEvent += [rect_element] () {
// rect_element->BGColor({64, 0, 0});
//};
//button_element->OnToggleOffEvent += [rect_element] () {
// rect_element->BGColor({0, 64, 0});
//};
//button_element->OnToggleEvent += [button_element] () {
// Color4 incbg = button_element->BGColor();
// Once an overflow occurs it will reset anyway
// Thanks computer science
// if (incbg.b < 255)
// incbg.b += 10;
// button_element->BGColor(incbg);
//};
Text* btntext = new Text(button_element);
btntext->SetContent("I AM BUTTON");
btntext->SetFont(FreeSans);
btntext->SetTextSize(8);
btntext->SetTextColor({255, 0, 0});
// End Button //
*/
// Window //
auto* win_element = new JUI::Window(root);
win_element->CornerRounding(5);
win_element->Size({50_percent, 50_percent});
win_element->SetTitle("Window Widget");
//win_element->Padding(1_px);
//nineslice_demo_window->Padding(1_px);
// End Window //
auto darkie = new JUI::Image(win_element->GetViewportInstance(), sample_texture);
auto* nineslice = new JUI::NineSliceRect(nineslice_demo_window);
nineslice->Content(slicer);
nineslice->Size({100_percent, 100_percent});
nineslice->BGColor(Colors::Transparent);
nineslice->TopLeftQuad({{0,0},{96,96}});
nineslice->TopRightQuad({{384-96,0},{96,96}});
nineslice->BottomLeftQuad({ {0, 378-96}, {96, 96}});
nineslice->BottomRightQuad({ {384-96, 378-96}, {96, 96}});
nineslice->TopQuad({ {96, 0}, {192, 96} });
nineslice->BottomQuad({ {96, 378-96}, {192, 96} });
nineslice->RightQuad({{384-(96), 96}, {96, 378-(96*2)}});
nineslice->LeftQuad({{0, 96}, {96, 378-(96*2)}});
nineslice->CenterQuad({{96, 96}, {384-(96*2), 378-(96*2)}});
auto darkie = new JUI::Image(nineslice_demo_window->ViewportInstance(), sample_texture);
darkie->FitImageToParent(true);
darkie->Color({255,255,255,128});
auto list = new VerticalListLayout(win_element->GetViewportInstance());
auto list = new VerticalListLayout(nineslice_demo_window->ViewportInstance());
list->Padding(10_px);
TextRect* a = new TextRect(list);
a->SetTextSize(16);
a->Size({0, 20, 1, 0});
@@ -247,7 +258,7 @@ JUI::Scene* CreateScene() {
TextRect* b = new TextRect(list);
b->SetTextSize(16);
b->SetContent("You can drag it around via left-click, and resize via right-click.");
b->SetContent("You can drag it around via left-click, and resize via right-click. Isn't that cool??");
//b->FitText(true);
b->Size({0, 20, 1, 0});
b->Center();
@@ -257,12 +268,18 @@ JUI::Scene* CreateScene() {
return root;
}
class JUIDevelopmentTestWindow : public ReWindow::RWindow {
float scale = 1.f;
float accum = 0;
int iter = 0;
class JUIDevelopmentTestWindow : public ReWindow::OpenGLWindow {
public:
void initGL() {
bool result = JGL::Init(GetSize(), 0.f, 0.f);
JGL::Update(GetSize());
auto size = GetSize();
auto vec_size = Vector2i(size.x, size.y);
bool result = JGL::Init(vec_size, 0.f, 0.f);
JGL::Update(vec_size);
glClearColor(0.f, 0.f, 0.f, 0.f);
// TODO: Delete when we update to the next release of JGL
@@ -271,7 +288,28 @@ public:
void Update(float elapsed)
{
widget_count->SetContent(std::format("Widgets: {}", count_descendants(scene)));
using namespace JUI::UDimLiterals;
accum += elapsed;
scene->Update(elapsed);
if (accum > 1.f)
{
iter--;
accum = 0.f;
auto* text = new JUI::TextRect(list);
text->Size({50_percent, 20_px});
text->ZIndex(iter);
text->LayoutOrder(-iter);
text->SetContent(std::format("{} Sampled Delta: {}ms", -iter, Math::Floor(elapsed*1000.f)));
scroller->scroll_size += 20;
}
}
void Draw()
@@ -279,17 +317,19 @@ public:
scene->Draw();
}
JUIDevelopmentTestWindow(const std::string& title, int w, int h) : ReWindow::RWindow(title, w, h) {}
JUIDevelopmentTestWindow(const std::string& title, int w, int h) : ReWindow::OpenGLWindow(title, w, h, 2, 1) {}
void OnRefresh(float elapsed) override {
Update(elapsed);
JGL::Update(GetSize());
scene->SetViewportSize(GetSize());
auto size = GetSize();
Vector2i vSize = Vector2i(size.x, size.y);
JGL::Update(vSize);
scene->SetViewportSize(Vector2(vSize));
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
Draw();
this->GLSwapBuffers();
this->SwapBuffers();
}
@@ -315,27 +355,38 @@ public:
}
void OnKeyDown(const ReWindow::KeyDownEvent &) override
{
void OnKeyDown(const ReWindow::KeyDownEvent &) override {
}
//bool OnResizeRequest(const ReWindow::WindowResizeRequestEvent &e) override {}
JUIDevelopmentTestWindow() : ReWindow::RWindow() {}
void OnMouseWheel(const ReWindow::MouseWheelEvent &w) override {
scale += w.WheelMovement * 0.125f;
scene->GlobalUIScale({scale, scale});
}
};
#include <rewindow/logger/logger.h>
int main()
{
void inspect_widget(JUI::Widget* w, int depth = 1) {
std::cout << std::setw(depth*4);
std::cout << w->Name() << std::endl;
std::cout << std::setw(0);
depth++;
for (auto* child : w->GetChildren())
{
inspect_widget(child, depth);
}
}
int main() {
using namespace ReWindow;
// TODO: Find out new jlog api for silencing specific loggers.
ReWindow::Logger::Debug.EnableConsole(false);
auto* window = new JUIDevelopmentTestWindow("Test Window", 800, 600);
window->SetRenderer(RenderingAPI::OPENGL);
//window->SetRenderer(RenderingAPI::OPENGL);
window->Open();
window->initGL();
window->SetFullscreen(false);
@@ -345,19 +396,22 @@ int main()
JGL::Update({800, 600});
sample_texture = new JGL::Texture("assets/ld.png");
slicer = new JGL::Texture("assets/9slice.png");
scene = CreateScene();
inspect_widget(scene);
window->OnResizeRequestEvent += [&] (ReWindow::WindowResizeRequestEvent e){
Vector2 size = e.Size;//window->getLastKnownResize();
scene->SetViewportSize(size);
Vector2i size = Vector2i(e.Size.x, e.Size.y);//window->getLastKnownResize();
scene->SetViewportSize(Vector2(size));
std::cout << size.x << "," << size.y << std::endl;
JGL::Update(size);
};
window->OnMouseMoveEvent += [&] (MouseMoveEvent e)
{
scene->ObserveMouseMovement(e.Position);
scene->ObserveMouseMovement(Vector2(e.Position.x, e.Position.y));
};
window->OnMouseButtonUpEvent += [&] (MouseButtonUpEvent e) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

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

View File

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

View File

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

View File

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

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

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

View File

@@ -7,12 +7,12 @@ void JUI::Hoverable::OnHover(const Vector2 &MousePos) {
void JUI::Hoverable::OnExit(const Vector2 &MousePos) {
OnExitEvent.Invoke(MousePos);
tooltip_threshold = 0;
//tooltip_threshold = 0;
}
void JUI::Hoverable::Update(const Vector2 &m_pos, float delta) {
if (tooltip != nullptr)
/*if (tooltip != nullptr)
{
if (IsHovered()) {
tooltip->Visible(true);
@@ -20,7 +20,7 @@ void JUI::Hoverable::Update(const Vector2 &m_pos, float delta) {
} else {
tooltip->Visible(false);
}
}
}*/
if (IsHovered() && !hover_debounce) {

202
src/JUI/Tween.cpp Normal file
View File

@@ -0,0 +1,202 @@
#include <JUI/Tween.hpp>
#include <J3ML/J3ML.hpp>
namespace JUI
{
using namespace J3ML;
float EasingFunctions::EaseInSine(float t) { return Math::Sin(Math::PiOverTwo * t); }
float EasingFunctions::EaseOutSine(float t) { return 1 + Math::Sin(Math::PiOverTwo * (--t)); }
float EasingFunctions::EaseInOutSine(float t) { return 0.5f * (1 + Math::Sin(Math::Pi * (t - 0.5f))); }
float EasingFunctions::EaseInQuad(float t) { return t * t;}
float EasingFunctions::EaseOutQuad(float t) { return t * (2.f - t);}
float EasingFunctions::EaseInOutQuad(float t) { return t < 0.5f ? 2.f * t * t : t * (4.f - 2.f * t) - 1; }
float EasingFunctions::EaseInCubic(float t) { return t * t * t;}
float EasingFunctions::EaseOutCubic(float t) { return 1.f + (--t) * t * t; }
float EasingFunctions::EaseInOutCubic(float t) { return t < 0.5f ? 4.f * t * t * t : 1 + (--t) * (2 * (--t)) * (2 * t); }
float EasingFunctions::EaseInQuart(float t) {
t *= t;
return t * t;
}
float EasingFunctions::EaseOutQuart(float t) {
t = (--t) * t;
return 1.f - t * t;
}
float EasingFunctions::EaseInOutQuart(float t) {
if (t < 0.5f) {
t *= t;
return 8 * t * t;
} else {
t = (--t) * t;
return 1 - 8 * t * t;
}
}
float EasingFunctions::EaseInQuint(float t) {
float t2 = t * t;
return t * t2 * t2;
}
float EasingFunctions::EaseOutQuint(float t) {
float t2 = (--t) * t;
return 1 + t * t2 * t2;
}
float EasingFunctions::EaseInOutQuint(float t) {
float t2;
if ( t < 0.5f) {
t2 = t * t;
return 16 * t * t2 * t2;
} else {
t2 = (--t) * t;
return 1 + 16 * t * t2 * t2;
}
}
float EasingFunctions::EaseInExpo(float t) {
return (Math::Pow(2, 8*t) - 1) / 255;
}
float EasingFunctions::EaseOutExpo(float t) {
return 1 - Math::Pow(2, -8*t);
}
float EasingFunctions::EaseInOutExpo(float t) {
if (t < 0.5f) {
return (Math::Pow(2, 16 * t) - 1) / 510;
} else {
return 1 - 0.5f * Math::Pow(2, -16 * (t - 0.5f));
}
}
float EasingFunctions::EaseInCirc(float t) {
return 1 - Math::Sqrt(1 - t);
}
float EasingFunctions::EaseOutCirc(float t) {
return Math::Sqrt(t);
}
float EasingFunctions::EaseInOutCirc(float t) {
if (t < 0.5f) {
return (1 - Math::Sqrt(1 - 2 * t)) * 0.5f;
} else {
return (1 + Math::Sqrt(2 * t - 1)) * 0.5f;
}
}
float EasingFunctions::EaseInBack(float t) { return t * t * (2.70158f * t - 1.70158f); }
float EasingFunctions::EaseOutBack(float t) { return 1 + (--t) * t * (2.70158 * t + 1.70158); }
float EasingFunctions::EaseInOutBack(float t) {
if (t < 0.5f) {
return t * t * (7 * t - 2.5f) * 2.f;
} else {
return 1 + (--t) * t * 2 * (7 * t + 2.5f);
}
}
float EasingFunctions::EaseInElastic(float t) {
float t2 = t * t;
return t2 * t2 * Math::Sin(t * Math::Pi * 4.5f);
}
float EasingFunctions::EaseOutElastic(float t) {
float t2 = (t - 1) * (t - 1);
return 1 - t2 * t2 * Math::Cos(t * Math::Pi * 4.5f);
}
float EasingFunctions::EaseInOutElastic(float t) {
float t2;
if (t < 0.45f) {
t2 = t * t;
return 8 * t2 * t2 * Math::Sin(t * Math::Pi * 9);
} else if (t < 0.55f) {
return 0.5f + 0.75f * Math::Sin(t * Math::Pi * 4);
} else {
t2 = (t - 1) * (t - 1);
return 1 - 8 * t2 * t2 * Math::Sin(t * Math::Pi * 9);
}
}
float EasingFunctions::EaseInBounce(float t) {
return Math::Pow(2, 6 * (t - 1)) * Math::Abs(Math::Sin(t * Math::Pi * 3.5f));
}
float EasingFunctions::EaseOutBounce(float t) {
return 1 - Math::Pow(2, -6 * t ) * Math::Abs(Math::Cos(t * Math::Pi * 3.5f));
}
float EasingFunctions::EaseInOutBounce(float t) {
if (t < 0.5f) {
return 8 * Math::Pow(2, 8 * (t - 1)) * Math::Abs(Math::Sin(t * Math::Pi));
} else {
return 1 - 8 * Math::Pow(2, -8 * t) * Math::Abs(Math::Sin(t * Math::Pi * 7));
}
}
float EasingFunctions::EaseInOutLinear(float t) { return t;}
void Tween::Update(float elapsed) {
if (time <= 0)
{
// TODO: Error in this case.
completed = true;
}
if (delay_time > 0)
{
delay_time -= elapsed;
return;
}
progress += (elapsed / time);
if (progress >= 1.f)
{
progress = 1.f;
if (!completed)
{
completed = true;
Completed.Invoke();
}
return;
}
float modified_progress = easing_func(progress);
tick_func(elapsed, modified_progress);
}
void Tween::ForceFinish() {
progress = 1.f;
}
void Tween::Cancel() {
completed = true;
}
bool Tween::Paused() const { return paused; }
bool Tween::HasCompleted() const { return completed; }
void Tween::Start() { progress = 0; Resume(); }
Tween::Tween(TweenTickFunc tick_func) {
this->tick_func = tick_func;
}
}

View File

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

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
#include <JUI/Widgets/Canvas.hpp>

View File

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

View File

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

View File

@@ -2,9 +2,8 @@
namespace JUI
{
Image::Image() : Widget(), ImageBase()
{
Image::Image() : Widget(), ImageBase() {
Name("Image");
}
Image::Image(Widget* parent) : Image()
@@ -31,7 +30,7 @@ namespace JUI
if (fit_image_to_parent)
{
auto old_scale = scale;
scale = GetAbsoluteSize() / texture->GetDimensions();
scale = GetAbsoluteSize() / Vector2(texture->GetDimensions());
ImageBase::Draw(GetAbsolutePosition(), GetAbsoluteSize());
scale = old_scale;
} else

View File

@@ -0,0 +1,59 @@
#include <JUI/Widgets/ImageButton.hpp>
void JUI::ImageButton::Update(float delta) {
Button::Update(delta);
UpdateImageVisualState();
}
void JUI::ImageButton::Draw() {
Button::Draw();
ImageBase::Draw(GetAbsolutePosition()+GetAbsolutePaddingTopLeft(), GetAbsoluteSize()-GetAbsolutePaddingBottomRight());
}
JUI::ImageButton::ImageButton() : ImageBase(), Button() {
Name("ImageButton");
}
JUI::ImageButton::ImageButton(JUI::Widget *parent) : ImageButton() { Parent(parent); }
void JUI::ImageButton::HoveredImageColor(const Color4 &value) {
hover_img_color = value;
}
void JUI::ImageButton::BaseImageColor(const Color4 &value) {
base_img_color = value;
}
void JUI::ImageButton::PressedImageColor(const Color4 &value) {
pressed_img_color = value;
}
void JUI::ImageButton::DisabledImageColor(const Color4 &value) {
disabled_img_color = value;
}
void JUI::ImageButton::UpdateImageVisualState() {
if (Disabled())
ImageBase::Color(DisabledImageColor());
else if (IsClicked())
ImageBase::Color(PressedImageColor());
else if (IsHovered())
ImageBase::Color(HoveredImageColor());
else
ImageBase::Color(BaseImageColor());
}
Color4 JUI::ImageButton::HoveredImageColor() const {return hover_img_color;}
Color4 JUI::ImageButton::BaseImageColor() const { return base_img_color; }
Color4 JUI::ImageButton::PressedImageColor() const { return pressed_img_color; }
Color4 JUI::ImageButton::DisabledImageColor() const { return disabled_img_color; }
void JUI::ImageButton::ImageColors(const Color4 &hover, const Color4 &base, const Color4 &press, const Color4 &disabled) {
HoveredImageColor(hover);
BaseImageColor(base);
PressedImageColor(press);
DisabledImageColor(disabled);
}

View File

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

View File

@@ -2,17 +2,24 @@
namespace JUI
{
ListLayout::ListLayout() : LayoutContainer() {}
ListLayout::ListLayout() : LayoutContainer() {
Name("ListLayout");
}
ListLayout::ListLayout(Widget* parent) : ListLayout() {}
VerticalListLayout::VerticalListLayout() : ListLayout() {}
VerticalListLayout::VerticalListLayout(Widget* parent) : VerticalListLayout()
{
VerticalListLayout::VerticalListLayout(Widget* parent) : VerticalListLayout() {
this->Parent(parent);
}
struct {
bool operator()(Widget* a, Widget* b) const { return a->LayoutOrder() > b->LayoutOrder();}
} layoutOrderSort;
void VerticalListLayout::ApplyLayout() {
std::sort(children.begin(), children.end(), layoutOrderSort);
if (layout == LayoutOrder::V::TOP)
{
int consumed_height = 0;
@@ -58,8 +65,11 @@ namespace JUI
void HorizontalListLayout::ApplyLayout() {
// TODO: Implement widget.LayoutOrder and sort by that number.
std::sort(children.begin(), children.end(), layoutOrderSort);
if (layout == LayoutOrder::H::LEFT)
{
int consumed_width = 0;
for (auto &child_widget : children)
{

View File

@@ -0,0 +1,108 @@
#include <JUI/Widgets/NineSlice.hpp>
#include <JGL/JGL.h>
namespace JUI {
AABB2D JUI::NineSliceRect::TopLeftQuad() const { return top_left_quad; }
AABB2D NineSliceRect::TopRightQuad() const { return top_right_quad; }
AABB2D NineSliceRect::BottomLeftQuad() const { return bottom_left_quad; }
AABB2D NineSliceRect::BottomRightQuad() const { return bottom_right_quad; }
AABB2D NineSliceRect::TopQuad() const { return top_quad; }
AABB2D NineSliceRect::LeftQuad() const { return left_quad; }
AABB2D NineSliceRect::RightQuad() const { return right_quad; }
AABB2D NineSliceRect::BottomQuad() const { return bottom_quad; }
AABB2D NineSliceRect::CenterQuad() const { return center_quad; }
void NineSliceRect::TopLeftQuad(const AABB2D &quad) { top_left_quad = quad; }
void NineSliceRect::TopRightQuad(const AABB2D &quad) { top_right_quad = quad; }
void NineSliceRect::BottomLeftQuad(const AABB2D &quad) { bottom_left_quad = quad; }
void NineSliceRect::BottomRightQuad(const AABB2D &quad) { bottom_right_quad = quad; }
void NineSliceRect::TopQuad(const AABB2D &quad) { top_quad = quad;}
void NineSliceRect::RightQuad(const AABB2D &quad) { right_quad = quad;}
void NineSliceRect::BottomQuad(const AABB2D &quad) { bottom_quad = quad;}
void NineSliceRect::LeftQuad(const AABB2D &quad) { left_quad = quad;}
void NineSliceRect::CenterQuad(const AABB2D &quad) { center_quad = quad;}
void NineSliceRect::Draw() {
Rect::Draw();
Vector2 abs_pos = GetAbsolutePosition();
Vector2 abs_size = GetAbsoluteSize();
// Draw Top-Left Quad.
Vector2 tl_computed_pos = abs_pos;
auto tl_quad = TopLeftQuad();
JGL::J2D::DrawPartialSprite(texture, tl_computed_pos, tl_quad.minPoint, tl_quad.maxPoint);
// Draw Top-Right Quad.
auto tr_quad = TopRightQuad();
Vector2 tr_computed_pos = abs_pos + Vector2(abs_size.x - tr_quad.maxPoint.x, 0);
JGL::J2D::DrawPartialSprite(texture, tr_computed_pos, tr_quad.minPoint, tr_quad.maxPoint);
// Draw Bottom Left Quad
auto bl_quad = BottomLeftQuad();
Vector2 bl_computed_pos = abs_pos + Vector2(0, abs_size.y - bl_quad.maxPoint.y);
JGL::J2D::DrawPartialSprite(texture, bl_computed_pos, bl_quad.minPoint, bl_quad.maxPoint);
// Draw Bottom Right Quad
auto br_quad = BottomRightQuad();
Vector2 br_computed_pos = abs_pos + abs_size - br_quad.maxPoint;
JGL::J2D::DrawPartialSprite(texture, br_computed_pos, br_quad.minPoint, br_quad.maxPoint);
// Draw Top-Quad.
Vector2 t_computed_pos = abs_pos + Vector2(tl_quad.maxPoint.x, 0);
auto t_quad = TopQuad();
float abs_width_minus_corners = abs_size.x - (tl_quad.maxPoint.x + tr_quad.maxPoint.x);
float t_scaling = abs_width_minus_corners / t_quad.maxPoint.x;
JGL::J2D::DrawPartialSprite(texture, t_computed_pos, t_quad.minPoint, t_quad.maxPoint, 0, {0,0}, {t_scaling, 1});
// Draw Bottom Quad
auto b_quad = BottomQuad();
Vector2 b_computed_pos = abs_pos + Vector2(tl_quad.maxPoint.x, abs_size.y - b_quad.maxPoint.y);
float b_scaling = abs_width_minus_corners / b_quad.maxPoint.x;
JGL::J2D::DrawPartialSprite(texture, b_computed_pos, b_quad.minPoint, b_quad.maxPoint, 0, {0,0}, {b_scaling, 1});
// Draw Left Quad
Vector2 l_computed_pos = abs_pos + Vector2(0, tl_quad.maxPoint.y);
auto l_quad = LeftQuad();
float abs_height_minus_corners = abs_size.y - (tl_quad.maxPoint.y + tr_quad.maxPoint.y);
float l_scaling = abs_height_minus_corners / l_quad.maxPoint.y;
JGL::J2D::DrawPartialSprite(texture, l_computed_pos, l_quad.minPoint, l_quad.maxPoint, 0, {0, 0}, {1, l_scaling});
// Draw Right Quad
auto r_quad = RightQuad();
Vector2 r_computed_pos = abs_pos + Vector2(abs_size.x - tr_quad.maxPoint.x, tr_quad.maxPoint.y);
float r_scaling = abs_height_minus_corners / r_quad.maxPoint.y;
JGL::J2D::DrawPartialSprite(texture, r_computed_pos, r_quad.minPoint, r_quad.maxPoint, 0, {0,0}, {1, r_scaling});
// Draw Center Quad
auto c_quad = CenterQuad();
Vector2 c_computed_pos = abs_pos + tl_quad.maxPoint;
Vector2 c_scaling = Vector2(abs_width_minus_corners, abs_height_minus_corners) / c_quad.maxPoint;
JGL::J2D::DrawPartialSprite(texture, c_computed_pos, c_quad.minPoint, c_quad.maxPoint, 0, {0,0}, c_scaling);
}
NineSliceRect::NineSliceRect() : Rect(), ImageBase() {
Name("NineSliceRect");
}
NineSliceRect::NineSliceRect(Widget *parent) : Rect(parent), ImageBase() {}
NineSliceRect::NineSliceRect(Widget *parent, JGL::Texture *texture) : Rect(parent), ImageBase(texture) {}
}

View File

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

View File

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

View File

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

View File

@@ -4,22 +4,78 @@
using namespace JUI;
void ScrollingRect::RecomputeRenderTarget() {
Rect::PreDraw();
InnerDraw();
Rect::PostDraw();
}
void ScrollingRect::Draw() {
J2D::Begin();
J2D::DrawRenderTarget(canvas, CanvasPosition());
// TODO sub_texture_size would be the size of the visible area.
J2D::DrawPartialRenderTarget(canvas, GetAbsolutePosition(), {0, scroll}, {});
J2D::End();
// TODO: Only recompute when something has changed.
// Q: How do we know when something has changed?
RecomputeRenderTarget();
J2D::DrawPartialRenderTarget(canvas, GetAbsolutePosition(), {0, 0}, Vector2(GetAbsoluteSize().x, GetAbsoluteSize().y));
bool canvas_larger_than_widget = scroll_size > GetAbsoluteSize().y;
float ratio = scroll_size / GetAbsoluteSize().y;
if (vertical_scrollbar_enabled && canvas_larger_than_widget) {
Vector2 vscroll_pos = GetAbsolutePosition() + Vector2(GetAbsoluteSize().x-scrollbar_width, 0);
Vector2 vscroll_size = {scrollbar_width, GetAbsoluteSize().y-scrollbar_width};
J2D::FillRect(Colors::LightGray, vscroll_pos, vscroll_size);
J2D::FillRoundedRect(scrollbar_color, {vscroll_pos.x, vscroll_pos.y + scroll}, {vscroll_size.x, (vscroll_size.y / ratio)}, 4);
// Draw little square box at the point where the vertical and horizontal scrollbars would meet.
Vector2 lil_box_size = {scrollbar_width, scrollbar_width};
Vector2 lil_box_pos = GetAbsolutePosition() + GetAbsoluteSize() - lil_box_size;
J2D::FillRect(Colors::DarkGray, lil_box_pos, lil_box_size);
}
}
Vector2 ScrollingRect::CanvasPosition() const {
return {GetAbsolutePosition().x, GetAbsolutePosition().y + scroll};
return {GetAbsolutePosition().x, GetAbsolutePosition().y - scroll};
}
// Wouldn't inner draw actually render everything onto the RenderTarget?
void ScrollingRect::InnerDraw() {}
void ScrollingRect::InnerDraw() {
J2D::Begin(canvas, true);
auto saved_pos = position;
auto saved_size = size;
// Kind of a hack to get the child widgets onto the rendertarget at the correct position.
// We basically set the position for just long enough to lie to the child widgets.
// The amount we offset by is the absolute position.
// If you work the formulas out, the child widgets will believe their parent's AbsolutePosition is {0,0}.
Position(UDim2::FromPixels(-GetAbsolutePosition().x, -GetAbsolutePosition().y + scroll));
Size({200_percent, 500_percent});
Rect::InnerDraw();
DrawChildWidgets();
J2D::DrawPoint(Colors::Blue, {1,1}, 2);
J2D::DrawPoint(Colors::Blue, Vector2(GetAbsoluteSize()), 2);
// Set our position back once we're done.
Size(saved_size);
Position(saved_pos);
J2D::End();
}
const Vector2i default_initialize_canvas_size {1024, 4096};
ScrollingRect::~ScrollingRect() { delete canvas; }
ScrollingRect::ScrollingRect() : canvas(new RenderTarget(default_initialize_canvas_size)) {
Name("ScrollingRect");
//bool success = canvas->SetMSAAEnabled(JGL::SampleRate::X8);
}
ScrollingRect::ScrollingRect(Widget *parent) : Rect(parent), canvas(new RenderTarget(default_initialize_canvas_size)) {
//bool success = canvas->SetMSAAEnabled(JGL::MSAA_SAMPLE_RATE::MSAA_8X);
}
JGL::RenderTarget *ScrollingRect::GetCanvas() { return canvas; }
Vector2i ScrollingRect::CanvasSize() const { return canvas->GetDimensions(); }

View File

@@ -4,7 +4,7 @@
namespace JUI
{
Slider::Slider(JUI::Widget *parent)
Slider::Slider(JUI::Widget *parent) : Slider()
{
this->Parent(parent);
}
@@ -85,9 +85,9 @@ namespace JUI
,GetAbsolutePosition().y
};
/// TODO: Implement internal padding on scrubber element?
J2D::Begin();
JGL::J2D::FillRect(Colors::Gray, pos, {scrubber_width, GetAbsoluteSize().y});
J2D::End();
//J2D::Begin();
JGL::J2D::FillRect(scrubber_color, pos, {scrubber_width, GetAbsoluteSize().y});
//J2D::End();
}
float Slider::Minimum() const { return minimum; }
@@ -115,4 +115,14 @@ namespace JUI
void Slider::ScrubberColor(const Color4 &color) { scrubber_color = color;}
void Slider::ScrubberWidth(float width) { scrubber_width = width;}
Slider::Slider() {
Name("Slider");
}
void Slider::SetDragging(bool value) {
dragging = value;
}
bool Slider::Dragging() const { return dragging; }
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,15 +3,15 @@
#include <cstdlib>
namespace JUI
{
namespace JUI {
Window::Window() : Widget(), Clickable(), Hoverable(), RectBase(), Draggable(), Resizable(), Dockable() {
Name("Window");
this->Position({200, 200, 0, 0});
this->Size({400, 200, 0, 0});
min_size = {400, 200};
this->BGColor({64,64,64,255});
this->BorderColor({92,92,192});
this->SetBorderWidth(2);
this->BGColor(Style::Window::OutlineColor);
this->BorderColor(Style::Window::OutlineColor);
this->BorderWidth(Style::Window::OutlineWidth);
this->BorderMode(BorderMode::Middle);
// TODO: Move out of Event callback
@@ -23,13 +23,12 @@ namespace JUI
Viewport = new Rect(this);
Viewport->Name("Viewport");
Viewport->BGColor({64,64,64, 0});
Viewport->Size({0, -20, 1, 1});
Viewport->Position({0, 20, 0, 0});
Viewport->BGColor(Style::Window::ViewportBackgroundColor);
Viewport->Size({0, -titlebar_height, 1, 1});
Viewport->Position({0, titlebar_height, 0, 0});
// TODO: Viewport->AnchorPoint({0.f, 0.f});
Viewport->BorderColor({128, 128, 128, 255});
Viewport->SetBorderWidth(0);
Viewport->BorderWidth(0);
//Viewport->Padding(1_px);
// TODO: Viewport->SetBorderRadius(0);
@@ -37,40 +36,59 @@ namespace JUI
Topbar = new Rect(this);
Topbar->Position({0_px, 0_px});
Topbar->Size({100_percent, UDim(titlebar_height, 0)});
Topbar->BGColor({92,92,192, 255});
//Topbar->BGColor({92,92,192, 255});
Topbar->BGColor(Colors::Transparent);
Topbar->BorderColor({128, 128, 128, 0});
Topbar->SetBorderWidth(0);
Topbar->BorderWidth(0);
TitleLabel = new Text(Topbar);
TitleLabel->Center();
TitleLabel->SetContent(title);
TitleLabel->SetTextSize(title_font_size);
// TODO: Pull out this circle image generation code, make a Circle widget.
// TODO: auto* list = new HorizontalListLayout(Topbar);
// TODO: exit_btn
exit_btn = new TextButton(Topbar);
auto* exb_tex = new Texture( Vector2i(titlebar_height, titlebar_height));
auto* exb_rt = new RenderTarget(exb_tex);
if (exb_rt->SetMSAAEnabled(JGL::SampleRate::X8)) {/* using msaa to make the circle nicer. */}
std::array<Color4, 2> exb_circle_colors {
Colors::White,//Color4(168, 28, 28, 255),
Colors::White//Color4(212, 25, 25, 255)
};
std::array<Vector2, 2> exb_circle_positions {
Vector2(exb_rt->GetDimensions()) / 2,
Vector2(exb_rt->GetDimensions()) / 2
};
std::array<float, 2> exb_circle_radii {
(float) (exb_rt->GetDimensions().x * 0.33),
(float) (exb_rt->GetDimensions().x * 0.28)
};
J2D::Begin(exb_rt, true);
J2D::BatchFillCircle(exb_circle_colors.data(), exb_circle_positions.data(), exb_circle_radii.data(), 24, 2);
J2D::End();
delete exb_rt;
exit_btn = new ImageButton(Topbar);
exit_btn->Content(exb_tex);
//exit_btn->AnchorPoint({1.f, 0.f});
//exit_btn->Size({30_px, 100_percent});
exit_btn->Size({titlebar_height, titlebar_height, 0, 0});
//exit_btn->BorderColor({128, 128, 128, 255});
//exit_btn->SetBaseColor({192, 64, 64, 255});
//exit_btn->SetHoverColor({255, 0, 0, 255});
//exit_btn->BGColor({192, 64, 64, 255});
exit_btn->SetContent("X");
exit_btn->BaseBGColor({92,92,192, 255});
exit_btn->BGColor(exit_btn->BaseBGColor());
exit_btn->BorderColors({0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0});
exit_btn->BorderColor({0,0,0,0});
exit_btn->SetBorderWidth(0.f);
//exit_btn_text->SetFont(TitleLabel->GetFont());
exit_btn->SetTextSize(title_font_size);
exit_btn->SetTextColor({255, 0, 0});
exit_btn->Center();
exit_btn->BGColor(Colors::Transparent);
exit_btn->BGColors(Colors::Transparent, Colors::Transparent, Colors::Transparent, Colors::Transparent);
exit_btn->BorderWidth(0.f);
exit_btn->BaseImageColor(Colors::Reds::LightCoral);
exit_btn->HoveredImageColor(Colors::Red);
exit_btn->PressedImageColor(Colors::Reds::DarkRed);
exit_btn->OnClickEvent += [&] (auto... _) {
exit_btn->OnReleaseEvent += [&] (auto... _) {
this->Visible(false);
};
@@ -94,11 +112,11 @@ namespace JUI
bool Window::IsResizable() const { return resizable; }
Text *Window::GetTitleInstance() { return TitleLabel; }
Text *Window::TitleInstance() { return TitleLabel; }
Rect *Window::GetTopbarInstance() { return Topbar; }
Rect *Window::TopbarInstance() { return Topbar; }
Rect *Window::GetViewportInstance() { return Viewport; }
Rect *Window::ViewportInstance() { return Viewport; }
void Window::SetDrag(bool d) {
Draggable::SetDrag(d);
@@ -107,7 +125,7 @@ namespace JUI
void Window::SetTitleFont(const Font& f) {
TitleLabel->SetFont(f);
exit_btn->SetFont(f);
//exit_btn->SetFont(f);
}
void Window::OnClick(const Vector2& mouse_pos, const MouseButton& btn)
@@ -115,10 +133,10 @@ namespace JUI
Clickable::OnClick(mouse_pos, btn);
clicked = true;
if (draggable && btn == MouseButton::Left && GetTitleInstance()->IsMouseInside())
if (draggable && btn == MouseButton::Left && TitleInstance()->IsMouseInside())
this->SetDrag(true);
if (resizable && btn == MouseButton::Right && GetTitleInstance()->IsMouseInside()) {
if (resizable && btn == MouseButton::Right && TitleInstance()->IsMouseInside()) {
this->StartResizing(last_known_mouse_pos);
this->SetResize(true);
size_when_restart_began = Size();
@@ -196,7 +214,7 @@ namespace JUI
}
void Window::SetTitle(const std::string &title) {
GetTitleInstance()->SetContent(title);
TitleInstance()->SetContent(title);
}
void Window::MinSize(const Vector2 &constraint) {
@@ -219,7 +237,46 @@ namespace JUI
resizable = value;
}
TextButton *Window::GetExitButtonInstance() {
ImageButton* Window::ExitButtonInstance() {
return exit_btn;
}
void Window::LayoutControlsLeft() {
exit_btn->AnchorPoint({0.f, 0.f});
exit_btn->Position({0_px, 0_px});
}
void Window::LayoutControlsRight() {
exit_btn->AnchorPoint({1.f, 0.f});
exit_btn->Position({100_percent, 0_px});
}
Vector2 Window::AbsoluteViewportPosition() const {
return Viewport->GetAbsolutePosition();
}
Vector2 Window::AbsoluteViewportSize() const {
return Viewport->GetAbsoluteSize();
}
AABB2D Window::AbsoluteViewportBounds() const {
return {AbsoluteViewportPosition(), AbsoluteViewportSize()};
}
void Window::UpdateInternalWidgetsTitlebarHeight() {
Viewport->Size({0, -titlebar_height, 1, 1});
Viewport->Position({0, titlebar_height, 0, 0});
Topbar->Size({100_percent, UDim(titlebar_height, 0)});
exit_btn->Size({titlebar_height, titlebar_height, 0, 0});
}
void Window::TitlebarHeight(int height) {
titlebar_height = height;
UpdateInternalWidgetsTitlebarHeight();
}
int Window::TitlebarHeight() const {
return titlebar_height;
}
}