113 Commits

Author SHA1 Message Date
8d90f990f0 Fix ScrollingRect behavior with arrow keys, and add PageUp and PageDown key support. 2025-04-10 13:25:11 -05:00
5ccbb84e55 Add Window::Open, Close, SetOpen, IsOpen, and Toggle 2025-04-10 13:38:16 -04:00
74ae05321b add ObserveMouseInput override 2025-04-08 15:27:25 -04:00
906bde163a Other small fixes. 2025-04-07 00:09:36 -04:00
d0fcf9cce2 Refactor tweening some. 2025-04-07 00:09:17 -04:00
2e0ce74753 Implement debounce for collapsible. 2025-04-07 00:09:05 -04:00
47a7b19648 Continued. 2025-04-05 14:51:23 -04:00
80c28921b1 Refactored ScrollingRect to properly clamp the scrollbar size and position. 2025-04-05 14:51:13 -04:00
6166984daa Add Window::DragTo() 2025-04-05 14:49:28 -04:00
8589431cc7 Moving functions to cpp file 2025-04-05 14:49:06 -04:00
1092ef2b38 Add VerticalListLayout::CurrentContentHeight() and HorizontalListLayout::CurrentContentWidth() 2025-04-05 14:48:27 -04:00
1a11fa824b Implement window focusing. 2025-04-04 14:58:00 -04:00
1bd11c5235 Implement CommandLine Widget 2025-04-04 13:54:29 -04:00
956183bade Writing Logger 2025-04-03 23:36:10 -04:00
86d01099e2 Additions 2025-04-03 13:34:51 -04:00
90272e35f4 Editing demo program to have context menu that opens the console. 2025-04-02 15:26:23 -05:00
e2528f54fb Add BindMenu widget header 2025-04-02 15:25:59 -05:00
fffcc7f12d Add CommandLine window widget. 2025-04-02 15:25:41 -05:00
b3f65b3396 Adding new widgets 2025-04-02 13:19:06 -04:00
4193a2a693 Implementing input consumption refactor. 2025-04-01 18:56:43 -04:00
ee0e92d826 Specify default state of 'Dragging' for Slider. Add Slider::Dragging and Slider::SetDragging 2025-03-19 01:26:25 -04:00
86aa41a3db Added Widget::ObserveMouseWheel virtual method 2025-03-08 16:02:47 -05:00
3486ee03d7 yes 2025-03-02 05:24:51 -05:00
34257eb256 Merge remote-tracking branch 'origin/master' 2025-02-24 14:33:56 -05:00
c4f88c9021 Fix incorrect draw color on TextInputForm rendering the input buffer 2025-02-24 14:33:49 -05:00
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
0cd3fbfc17 Bugfixes for Slider and Window widget. 2025-01-28 18:11:35 -05:00
e7e469864f Cleanup & Stater
Make it so when you modify any element of the text that would necessitate redrawing the text we do.
2025-01-21 11:07:22 -05:00
0f00cfa48d Merge branch 'render_targets_for_text' 2025-01-20 20:49:42 -05:00
c9a9c6aeb5 Fixed.
Update to latest JGL & check if bounds is zero because that breaks *everything* to do with RenderTargets.
2025-01-20 20:49:21 -05:00
355e9f975e Edits to ScrollingRect. 2025-01-19 13:54:20 -05:00
5293a845ad ???? 2025-01-19 01:58:59 -05:00
a31ee731d2 Merge remote-tracking branch 'origin/master' 2025-01-16 00:07:26 -05:00
389f266722 Fixed some functions, will check into the rest in the morning. 2025-01-16 00:07:07 -05:00
Redacted
e32bce6b50 Update CMakeLists.txt 2025-01-15 23:02:08 -05:00
93632faaed Text Input Form Autocomplete text. 2025-01-09 13:19:20 -05:00
e11b8c8c50 Text Input Form OnReturn event. 2025-01-08 15:35:14 -05:00
2e0d75391e Text Input Form proper cursor behavior. 2025-01-08 13:53:11 -05:00
bc8ba3ed02 Implementing Text Input Form and it's features. 2025-01-07 19:09:37 -05:00
9867f49ebf Fix issue with default-initialization of fonts as garbage data in JUI. 2024-12-09 18:07:32 -05:00
c5766aa358 Merge remote-tracking branch 'origin/master' 2024-12-09 17:40:23 -05:00
3d22d683bc Migrate to latest JGL. 2024-12-09 17:40:16 -05:00
432a5990ce Roll back to code from prelease-41. Fix segfault later 2024-12-09 13:12:35 -05:00
2a7b562b8f Remove NonFree fonts 2024-12-04 15:31:47 -05:00
936c1902d0 Set sane default members on Hoverable 2024-12-04 15:05:07 -05:00
e8eb018959 Partially Completed Edits xddd 2024-12-04 14:41:53 -05:00
465d7052b7 pushing 2024-12-04 14:30:48 -05:00
ff84118b10 Fuck 2024-11-25 20:10:45 -05:00
9b3bf93ea1 Current State 2024-11-25 17:51:41 -05:00
0ffb750f82 Oops 2024-11-25 16:35:13 -06:00
423dc188cd Incremental Improvements 2024-11-22 16:45:14 -05:00
3a062a2709 Implement ZIndex draw sorting. 2024-11-22 16:02:55 -05:00
146b8190b0 Another Segfault 2024-11-21 13:40:01 -05:00
1cb0c1299a Segmentation fault on file->OnClickEvent callback 2024-11-21 12:40:48 -05:00
3ad9c615b6 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	include/JUI/Widgets/Button.hpp
2024-11-19 19:15:33 -05:00
19e066c835 Several minor changes 2024-11-19 19:15:18 -05:00
77 changed files with 3909 additions and 1185 deletions

View File

@@ -31,6 +31,16 @@ endif()
set_target_properties(JUI PROPERTIES LINKER_LANGUAGE CXX)
CPMAddPackage(
NAME jlog
URL https://git.redacted.cc/josh/jlog/archive/Prerelease-17.zip
)
CPMAddPackage(
NAME mcolor
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-6.2.zip
)
CPMAddPackage(
NAME Event
URL https://git.redacted.cc/josh/Event/archive/Release-12.zip
@@ -38,22 +48,17 @@ CPMAddPackage(
CPMAddPackage(
NAME J3ML
URL https://git.redacted.cc/josh/j3ml/archive/Release-3.4.zip
)
CPMAddPackage(
NAME jlog
URL https://git.redacted.cc/josh/jlog/archive/Prerelease-16.zip
URL https://git.redacted.cc/josh/j3ml/archive/3.4.5.zip
)
CPMAddPackage(
NAME ReWindow
URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-21.zip
URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-32.zip
)
CPMAddPackage(
NAME JGL
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-38.zip
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-52.zip
)
target_include_directories(JUI PUBLIC ${Event_SOURCE_DIR}/include)
@@ -65,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

Binary file not shown.

View File

@@ -17,11 +17,11 @@
using J3ML::LinearAlgebra::Vector2;
namespace JUI
{
namespace JUI {
/// The ImageBase class is an object that handles managing and rendering a texture reference.
/// This class is used as a mixin on widgets that must render images. i.e. Image class
/// This 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:
@@ -47,6 +47,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

@@ -9,7 +9,6 @@
/// @desc Base class for container objects. Container objects lay out widgets in a certain order.
/// @edit 2024-08-02
#pragma once
#include <JUI/Base/Widget.hpp>

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);
@@ -65,14 +66,15 @@ namespace JUI
void SetBorderStyling(const Color4& color, float width);
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

@@ -1,23 +1,27 @@
/// 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 TextBase.hpp
/// @desc Demo Program Entry Point
/// @edit 2024-11-16
#pragma once
#include <J3ML/LinearAlgebra.hpp>
#include <Color4.hpp>
#include <JGL/JGL.h>
#include <JGL/types/Font.h>
using J3ML::LinearAlgebra::Vector2;
namespace JUI
{
/// Enumerations for alignment of text within a desired "Box".
namespace TextAlign {
enum class H { Left, Center, Right }; // Horizontal
enum class V { Top, Center, Bottom }; // Vertical
}
namespace JUI {
class TextBase;
/// How should a text widget behave when it's text runs out of room?
enum class TextOverflowMode
{
enum class TextOverflowMode {
WRAP, /// Wraps at the nearest 'word', or otherwise appropriate stop in text.
WRAP_ANYWHERE, /// Wraps the text anywhere.
TRUNCATE, /// Cuts the text off and suffixes '...' to indicate the lack of the full intended message.
@@ -25,93 +29,104 @@ namespace JUI
TRUNCATE_NO_DOTS, /// Cuts the text off at the first appropriate break in text.
TRUNCATE_ANYWHERE_NO_DOTS /// Cuts the text off at any point in the string.
};
}
/// TextBase class, implements core mechanics of drawing text in JUI, and is used by Text Widget class.
class TextBase {
public:
TextBase();
/// Enumerations for alignment of text within a desired "Box".
namespace JUI::TextAlign {
// Horizontal
enum class H { Left, Center, Right };
// Vertical
enum class V { Top, Center, Bottom };
}
[[nodiscard]] std::string GetContent() const;
[[nodiscard]] Color4 GetTextColor() const;
[[nodiscard]] Color4 GetOutlineColor() const;
[[nodiscard]] TextAlign::H GetHorizontalTextAlign() const;
[[nodiscard]] TextAlign::V GetVerticalTextAlign() const;
/// TextBase class, implements core mechanics of drawing text in JUI, and is used by Text Widget class.
class JUI::TextBase {
private:
bool state_redraw = true;
protected:
RenderTarget* text_canvas;
std::string content = "Sample Text";
Color4 text_color = {255,255,255};
float text_outline = 1.f;
Color4 outline_color = {255,255,255};
bool word_wrap = false;
TextAlign::H h_align = TextAlign::H::Left;
TextAlign::V v_align = TextAlign::V::Top;
TextOverflowMode overflow_mode;
JGL::Font set_font = JGL::Fonts::Jupiteroid;
u32 text_size = 12;
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, unsigned int size, const Color4& color);
TextOverflowMode GetOverflowMode() const;
/// Renders the aligned text string within a bounding-box specified by abs_pos (top-left corner), and abs_size.
/// @see Widget::Draw(), Text::Draw().
void Draw(const Vector2& abs_pos, const Vector2& abs_size);
public:
[[nodiscard]] std::string GetContent() const;
[[nodiscard]] Color4 GetTextColor() const;
[[nodiscard]] Color4 GetOutlineColor() const;
[[nodiscard]] TextAlign::H GetHorizontalTextAlign() const;
[[nodiscard]] TextAlign::V GetVerticalTextAlign() const;
TextOverflowMode GetOverflowMode() const;
[[nodiscard]] Vector2 GetTextBounds();
[[nodiscard]] Vector2 GetTextPosition() const;
[[nodiscard]] JGL::Font GetFont() const;
[[nodiscard]] u32 GetTextSize() const;
public:
void Update(float delta);
void SetWordWrap(bool wrap);
void SetOverflowMode(const TextOverflowMode& mode);
void SetTextSize(u32 size);
void SetFont(const JGL::Font& font);
void SetContent(const std::string& content);
void SetTextColor(const Color4& color);
void SetOutlineColor(const Color4& color);
/// Returns the
[[nodiscard]] Vector2 GetTextBounds() const;
[[nodiscard]] Vector2 GetTextPosition() const;
[[nodiscard]] JGL::Font GetFont() const;
[[nodiscard]] u32 GetTextSize() const;
/// Set the horizontal alignment of this text widget in relation to it's parent,
/// while keeping the text retained inside the parent widget's bounds.
/// @see TextAlign::H
void SetHorizontalTextAlign(const TextAlign::H& align);
void SetOverflowMode(const TextOverflowMode& mode);
/// Set the vertical alignment of this text widget in relation to it's parent,
/// while keeping the text retained inside the parent widget's bounds.
/// @see TextAlign::V
void SetVerticalTextAlign(const TextAlign::V& align);
void SetTextSize(u32 size);
/// Sets the horizontal and vertical alignment of this text widget.
/// @see TextAlign::H and TextAlign::V.
void SetTextAlign(const TextAlign::H& h_align, const TextAlign::V& v_align);
void SetFont(const JGL::Font& font);
/// Aligns the text of this widget to the left-hand-side of the parent's bounding box.
/// @see SetHorizontalTextAlign, TextAlign::H
void AlignLeft();
void SetContent(const std::string& content);
/// Aligns the text of this widget to the right-hand-side of the parent's bounding box.
/// @see SetHorizontalTextAlign, TextAlign::H
void AlignRight();
void SetTextColor(const Color4& color);
/// Aligns the text of this widget to the top of the parent's bounding box.
/// @see SetVerticalTextAlign, TextAlign::V
void AlignTop();
void SetOutlineColor(const Color4& color);
/// Aligns the text of this widget to the bottom of the parent's bounding box.
/// @see SetVerticalTextAlign, TextAlign::V
void AlignBottom();
/// Set the horizontal alignment of this text widget in relation to it's parent,
/// while keeping the text retained inside the parent widget's bounds.
/// @see TextAlign::H
void SetHorizontalTextAlign(const TextAlign::H& align);
/// Set the vertical alignment of this text widget in relation to it's parent,
/// while keeping the text retained inside the parent widget's bounds.
/// @see TextAlign::V
void SetVerticalTextAlign(const TextAlign::V& align);
/// Centers the text of this widget in relation to the parent's bounding box.
/// @see SetHorizontalTextAlign, SetVerticalTextAlign, TextAlign enums
void AlignCenterBoth();
/// Sets the horizontal and vertical alignment of this text widget.
/// @see TextAlign::H and TextAlign::V.
void SetTextAlign(const TextAlign::H& h_align, const TextAlign::V& v_align);
/// Alias for AlignCenterBoth().
void Center();
/// Aligns the text of this widget to the left-hand-side of the parent's bounding box.
/// @see SetHorizontalTextAlign, TextAlign::H
void AlignLeft();
/// Aligns the text of this widget to the right-hand-side of the parent's bounding box.
/// @see SetHorizontalTextAlign, TextAlign::H
void AlignRight();
/// Aligns the text of this widget to the top of the parent's bounding box.
/// @see SetVerticalTextAlign, TextAlign::V
void AlignTop();
/// Aligns the text of this widget to the bottom of the parent's bounding box.
/// @see SetVerticalTextAlign, TextAlign::V
void AlignBottom();
/// Centers the text of this widget in relation to the parent's bounding box.
/// @see SetHorizontalTextAlign, SetVerticalTextAlign, TextAlign enums
void AlignCenterBoth();
/// Alias for AlignCenterBoth().
void Center();
/// Aligns the text of this widget to the horizontal center of the parent's bounding box.
void AlignCenterHorizontally();
/// Aligns the text of this widget to the vertical center of the parent's bounding box.
void AlignCenterVertically();
/// Aligns the text of this widget to the horizontal center of the parent's bounding box.
void AlignCenterHorizontally();
void Update(float delta);;
/// Renders the aligned text string within a bounding-box specified by abs_pos (top-left corner), and abs_size.
/// @see Widget::Draw(), Text::Draw().
void Draw(const Vector2& abs_pos, const Vector2& abs_size);
void SetWordWrap(bool wrap);
protected:
std::string content = "Sample Text";
Color4 text_color = {255,255,255,255};
float text_outline;
Color4 outline_color = {255,255,255,255};
bool word_wrap = false;
TextAlign::H h_align = TextAlign::H::Left;
TextAlign::V v_align = TextAlign::V::Top;
TextOverflowMode overflow_mode;
JGL::Font set_font;
u32 text_size;
};
}
/// Aligns the text of this widget to the vertical center of the parent's bounding box.
void AlignCenterVertically();
public:
~TextBase() { delete text_canvas; };
TextBase() : set_font(JGL::Fonts::Jupiteroid), state_redraw(true), text_canvas(new RenderTarget({1, 1}, {0, 0, 0, 0})) {};
};

View File

@@ -19,19 +19,14 @@
#include <Color3.hpp>
#include <Color4.hpp>
#include <JGL/JGL.h>
#include <ReWindow/types/Key.h>
#include <JUI/Tween.hpp>
#include <JUI/JUI.hpp>
#include "JUI/DefaultStyle.hpp"
using namespace JGL;
namespace JUI
{
/// 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;
@@ -39,8 +34,7 @@ namespace JUI
/// Widget is the base class for all JUI elements and represents any object that can be in the tree hierarchy.
/// Some widgets are expressly used for layout, and therefore do not strictly speaking 'Appear' on screen.
/// Widgets exist in a tree hierarchy where each object has one parent, and an arbitrary number of children. No circular references are allowed.
class Widget
{
class Widget {
public:
/// The default constructor for Widget. Generally, JUI widgets will initialize member data,
/// and apply a default style in this constructor. Be aware of this, as this is not "idiomatic C++".
@@ -49,15 +43,16 @@ namespace JUI
Widget(Widget* parent);
virtual ~Widget() {}
public:
#pragma region Events
/// An event that triggers when the widget is in-focus, generally when the mouse is hovering it.
/// (The actual trigger may differ for each widget)
Event<> Focused;
/// 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.
@@ -66,9 +61,9 @@ namespace JUI
Event<Widget *> ChildRemoved;
/// This event triggers right before this widget gets deallocated.
Event<Widget *> Destroying;
#pragma endregion
public:
#pragma region Hierarchy
/// Adds a given widget to this widget's list of children.
/// @return The widget in question.
Widget* Add(Widget* newChild);
@@ -99,6 +94,24 @@ namespace JUI
/// @see GetFamilyTreeRoot().
Widget* GetParent() const;
/// Sets the parent object of this widget. Positioning and sizing of a widget is relative to it's parent.
void Parent(Widget*);
/// Returns true if this widget is a 'descendant' of the specified potential ancestor. Otherwise returns false.
bool IsDescendantOf(Widget *ancestor);
/// Returns true if this widget is a 'ancestor' of the specified potential descendant. Otherwise returns false.
bool IsAncestorOf(Widget *descendant);
/// Returns the first ancestor in this widgets hierarchy that does not have its own parent.
/// In a well-formed JUI menu, this **should** always be a Scene.
Widget* GetFamilyTreeRoot() const;
#pragma endregion
#pragma region Layout
/// Returns the menu-coordinates that are used to position this widget in relation to its parent.
/// @see class UDim2, Position(const UDim2&),
[[nodiscard]] UDim2 Position() const;
@@ -129,18 +142,24 @@ namespace JUI
/// @see Size(), class UDim2.
void Size(const UDim2&);
/// Sets the parent object of this widget. Positioning and sizing of a widget is relative to it's parent.
void Parent(Widget*);
/// Creates and runs an animation on the Position of this widget.
Tween* TweenPosition(const UDim2& goal, TweenInfo params = {});
/// Returns true if this widget is a 'descendant' of the specified potential ancestor. Otherwise returns false.
bool IsDescendantOf(Widget *ancestor);
/// Returns true if this widget is a 'ancestor' of the specified potential descendant. Otherwise returns false.
bool IsAncestorOf(Widget *descendant);
/// Creates and runs an animation on the Size of this widget.
Tween* TweenSize(const UDim2& goal, TweenInfo params = {});
/// Determines the origin point of a Widget, relative to it's absolute size.
[[nodiscard]] Vector2 AnchorPoint() const;
///
void AnchorPoint(const Vector2 &point);
///
Tween* TweenAnchorPoint(const Vector2& goal, TweenInfo info = {});
#pragma endregion
#pragma region Padding
/// Returns the padding factor on the left of this widget.
/// Padding refers to spacing on the inside of elements, while margin is spacing outside the element.
/// @see PaddingLeft(), class UDim.
@@ -174,6 +193,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
@@ -186,6 +206,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.
@@ -219,6 +250,17 @@ 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
#pragma region Metadata
/// Returns this widgets mnemonic name.
/// Widgets can optionally be assigned a name that can be used to retrieve it from a widget tree node.
/// @see Name().
@@ -238,70 +280,113 @@ namespace JUI
/// @see IsVisible().
void Visible(bool enabled);
/// Returns the first ancestor in this widgets hierarchy that does not have its own parent.
/// In a well-formed JUI menu, this **should** always be a Scene.
Widget* GetFamilyTreeRoot() const;
/// Returns whether or not the mouse is inside this widget's approximate bounding-box.
bool IsMouseInside() const;
void AnchorPoint(const Vector2 &point);
int LayoutOrder() const { return layout_order; }
void LayoutOrder(int value) { layout_order = value;}
#pragma endregion
/// Returns the complete bounding box around this instance that will be rendered onto.
/// This differs from AbsolutePosition and AbsoluteSize in that they are computed for positioning elements relative to each other.
/// 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);
Vector2 ComputeElementPadding(const Vector2& size, const UDim2& padding) const;
public:
virtual void PreDraw() {}
virtual void PostDraw() {}
virtual void InnerDraw() {}
/// Renders the widget to the current OpenGL Context using JGL.
/// The user should call this on their Scene instances only. JUI will handle the rest.
virtual void Draw();
/// Performs the update logic of the widget.
/// The user should call this on their Scene instances only. JUI will handle the rest.
virtual void Update(float delta);
/// Informs a widget that the mouse has been moved to a new location.
/// This is designed in such a way that the end-user can plug this into their existing code.
/// The user should call this on their Scene instances only. JUI will handle the rest.
/// See ReWindowIntegrationDemo for an example.
virtual void ObserveMouseMovement(const Vector2& latest_known_pos);
/// @return True if this widget, or one of its descendants should "consume" the input event,
/// meaning it no longer needs to be passed to the next widgets in the hierarchy.
/// @note It is acceptable for a widget to "observe" the input event, but not "consume" it.
virtual bool ObserveMouseMovement(const Vector2& latest_known_pos);
/// Informs a widget that a mouse button has pressed or released.
/// This is designed in such a way that the end-user can plug this into their existing code.
/// The user should call this on their Scene instances only. JUI will handle the rest.
/// See ReWindowIntegrationDemo for an example.
virtual void ObserveMouseInput(MouseButton btn, bool pressed);
/// @return True if this widget, or one of its descendants should "consume" the input event,
/// meaning it no longer needs to be passed to the next widgets in the hierarchy.
/// @note It is acceptable for a widget to "observe" the input event, but not "consume" it.
virtual bool ObserveMouseInput(MouseButton btn, bool pressed);
/// Informs a widget that the mouse wheel has been moved.
/// This is designed in such a way that the end-user can plug this into their existing code.
/// The user should call this on their Scene instances only. JUI will handle the rest.
/// See ReWindowIntegrationDemo for an example.
virtual bool ObserveMouseWheel(int mwheel);
/// Informs a widget that a key has been pressed or released.
/// This is designed in such a way that the end-user can plug this into their existing code.
/// The user should call this on their Scene instances only. JUI will handle the rest.
/// See ReWindowIntegrationDemo for an example.
virtual bool ObserveKeyInput(Key key, bool pressed);
protected:
void DrawChildWidgets();
void UpdateChildWidgets(float delta);
protected:
MouseButton mbtn;
bool mb_state;
bool prev_mb_state;
//int last_known_mouse_button;
//bool last_known_mouse_button_state;
Vector2 last_known_mouse_pos;
UDim2 position;
UDim2 size;
MouseButton mbtn = MouseButton::Left;
bool mb_state = false;
bool prev_mb_state = false;
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;
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;
/// 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,72 @@
#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 UnfocusedOutlineColor = Colors::Gray;
const Color4 ViewportBackgroundColor = Colors::DarkGray;
const int OutlineWidth = 2;
}
}
namespace ExperimentalStyle {
}
namespace Style = DefaultStyle;
}

View File

@@ -1,8 +1,31 @@
#pragma once
#include <jlog/Logger.hpp>
#include <Event.h>
namespace JUI
{
extern jlog::GenericLogger UILogs;
namespace JUI {
class JUILogger : public jlog::GenericLogger {
public:
Event<std::string, Color4> OnLog;
JUILogger(const std::string& context,
std::ofstream& file,
Color4 contextColor = Colors::White,
Color4 timestampColor = Colors::Purples::Fuchsia,
Color4 locationColor = Colors::Pinks::Pink,
Color4 pointerColor = Colors::Pinks::LightPink,
Color4 messageColor = Colors::Greens::LightGreen);
void Log(const std::string& message, const std::source_location &location = std::source_location::current(), const jlog::Timestamp &ts = jlog::Timestamp()) override;
};
extern JUILogger UILogs;
/// An enumeration for mouse buttons, used by JUI to decouple from external systems.
/// Some boilerplate is required in order to get input mechanisms up and running. See the demo files for reference.
enum class MouseButton {
Left = 1,
Middle = 2,
Right = 3
};
}

View File

@@ -23,7 +23,7 @@ namespace JUI
Event<Vector2, MouseButton> OnClickEvent;
Event<Vector2, MouseButton, bool> OnReleaseEvent;
public:
bool IsClicked() const { return clicked; };
[[nodiscard]] bool IsClicked() const { return clicked; };
void SetClicked(bool manual_click);
public:
virtual void OnClick(const Vector2& MousePos, const MouseButton& MouseButton);;

View File

@@ -14,6 +14,7 @@
#include <J3ML/LinearAlgebra/Vector2.hpp>
#include <Event.h>
#include <JUI/Widgets/Tooltip.hpp>
namespace JUI
{
@@ -26,12 +27,16 @@ namespace JUI
public:
bool IsHovered() const { return hovered; };
public:
virtual void OnHover(const Vector2& MousePos);;
virtual void OnExit(const Vector2& MousePos);;
virtual void OnHover(const Vector2& MousePos);
virtual void OnExit(const Vector2& MousePos);
void Update(const Vector2& m_pos, float delta);
//virtual void SetTooltip(const std::string& content, float delay) {}
protected:
bool hovered;
bool hover_debounce;
//Tooltip* tooltip = nullptr;
//float tooltip_threshold = 0.f;
//float tooltip_limit = 0.125f;
bool hovered = false;
bool hover_debounce = false;
};
}

View File

@@ -1,3 +1,14 @@
/// 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 Toggleable.hpp
/// @desc Toggleable Mixin Helper Class - Added to widgets that should have special behavior when hovered by the mouse.
/// @edit 2024-10-11
#pragma once
#include <Event.h>

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

@@ -0,0 +1,137 @@
#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;
bool overwritable = true;
};
/// A class that represents an animation-in-action.
class Tween {
public:
Tween(TweenTickFunc tick_func);
Tween(TweenTickFunc tick_func, const 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;
this->overwritable = info.overwritable;
}
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;
bool overwritable = true;
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

@@ -0,0 +1,25 @@
#pragma once
#include <JUI/Widgets/Window.hpp>
namespace JUI
{
struct BindAction
{
std::string id;
std::string display_name;
std::vector<std::string> key_ids;
};
class BindMenu : public Window {
public:
explicit BindMenu();
void AddAction(std::string id, std::string disp_name);
};
}

View File

@@ -29,7 +29,7 @@ namespace JUI
/// The Button class is a basic rect widget that accepts mouse inputs, and provides event hooks.
/// The button also provides built-in automatic coloring based on it's current state, which are listed above.
class Button : public Widget, public RectBase, public Clickable, public Hoverable
class Button : public Rect, public Clickable, public Hoverable
{
public:
Event<> OnEnabled;
@@ -83,6 +83,10 @@ namespace JUI
/// Sets the border color of this object when it is disabled.
void DisabledBorderColor(const Color4& color);
void BGColors(const Color4& base, const Color4& hover, const Color4& pressed, const Color4& disabled = Colors::Gray);
void BorderColors(const Color4& base, const Color4& hover, const Color4& pressed, const Color4& disabled = Colors::Gray);
/// Returns whether is button is interactable. If enabled, it will listen to mouse events and react accordingly.
bool Enabled() const;
/// Returns whether is button is interactable. If enabled, it will listen to mouse events and react accordingly.
@@ -95,23 +99,26 @@ namespace JUI
/// Enables this button. @see Disable, SetEnabled
void Enable();
void Draw() override;
void Update(float delta) override;
void OnClick(const Vector2& mouse_pos, const MouseButton& btn) override;
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;
protected:
bool disabled = false;
Color4 hover_bg;
Color4 hover_border;
Color4 pressed_bg;
Color4 pressed_border;
Color4 disabled_bg;
Color4 disabled_border;
Color4 base_bg;
Color4 base_border;
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,8 +0,0 @@
//
// Created by dawsh on 8/1/24.
//
#ifndef JUI_CANVAS_HPP
#define JUI_CANVAS_HPP
#endif //JUI_CANVAS_HPP

View File

@@ -13,49 +13,43 @@
#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
{
namespace JUI {
// TODO: Find a nice way to implement a checkmark.
class CheckboxBase {
public:
[[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
{
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);
}
void Draw() override
{
Button::Draw();
if (checked)
{
J2D::Begin();
//J2D::DrawLine(check_color, );
}
}
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;
private:
};
class ImageCheckbox : public ImageButton, public CheckboxBase {
public:
protected:
private:
};
}

View File

@@ -0,0 +1,10 @@
#pragma once
namespace JUI
{
/// A composite class which combines a Checkbox and TextLabel into one element.
class CheckboxLabel : public Rect
{
};
}

View File

@@ -0,0 +1,45 @@
/// 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
#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;
Tween* resize_anim = nullptr;
};
}

View File

@@ -0,0 +1,78 @@
#pragma once
#include <JUI/Widgets/Window.hpp>
#include <JUI/Widgets/TextInputForm.hpp>
#include <JUI/Widgets/ScrollingRect.hpp>
#include <JUI/Widgets/ListLayout.hpp>
namespace JUI {
// TODO: Implement "Rich Text" message support.
// TODO: Implement command autocomplete helper API.
// TODO: Implement API for user-projects to bind commands.
// TODO: Implement API for user-projects to bind loggers.
// TODO: Implement message history.
// TODO: As of now, the API user is in charge of parsing and executing commands.
// TODO: A fairly-generic CommandInterpreter which suits our purposes.
// TODO: <command> <arg1> <arg2> <arg3>
// TODO: Tight JSON integration for specifying complex metadata in commands.
/// A generic "Game Console", which provides a log history, and input box for user-defined commands.
class CommandLine : public Window {
public:
/// This event is invoked when the user has sent a message to the command line.
Event<std::string> OnInput;
/// This event is invoked when the CommandLine window is opened.
Event<> OnOpen;
/// This event is invoked when the CommandLine window is closed.
Event<> OnClose;
/// The default constructor initializes the layout and events of child elements.
/// This constructor is a delegate called by the explicit constructor with parent parameter.
CommandLine();
/// Constructs a command line by specifying the widget it should be parented to.
/// @param parent
explicit CommandLine(Widget* parent);
/// Adds a message to the CommandLine message history.
/// @param message
/// @param color
void Log(const std::string& message, const Color4& color = Colors::White);
/// @return True if the window widget is currently open and visible. Input will be ignored if not.
[[nodiscard]] bool Opened() const;
/// @return True if the window widget is currently closed and invisible. Input will be ignored if so.
[[nodiscard]] bool Closed() const;
/// Opens the window widget.
void Open(bool openVal = true);
/// Closes the window widget.
void Close(bool openVal = false);
/// Sets the "open"-ness of the window widget. The widget will be invisible and ignore input if it is not open.
void SetOpen(bool openVal);
/// Passes input events down the widget hierarchy.
/// @return True if this widget will "consume" the input.
bool ObserveKeyInput(Key key, bool pressed) override;
/// Invoked when a message is sent to the command line.
virtual void OnInputBoxSend(const std::string& message);
protected:
TextInputForm* input_box;
ScrollingRect* message_log_box;
VerticalListLayout* message_log_list;
protected:
int index = 0;
int input_form_height = 20;
int message_height = 16;
bool open = false;
std::vector<std::string> msg_history;
private:
};
}

View File

@@ -1,8 +0,0 @@
//
// Created by dawsh on 8/1/24.
//
#ifndef JUI_CONTEXTMENU_HPP
#define JUI_CONTEXTMENU_HPP
#endif //JUI_CONTEXTMENU_HPP

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,8 @@
//
// Created by dawsh on 11/24/24.
//
#ifndef LISTBOX_HPP
#define LISTBOX_HPP
#endif //LISTBOX_HPP

View File

@@ -31,30 +31,35 @@ namespace JUI
/// Lays child elements out in a vertical list.
/// Child element positions are overridden by this widget.
class VerticalListLayout : public ListLayout
{
class VerticalListLayout : public ListLayout {
public:
VerticalListLayout();
explicit VerticalListLayout(Widget* parent);
/// Computes the order and positioning of all child elements to display them in an ordered list.
void ApplyLayout() override;
void LayoutOrder(LayoutOrder::V order);
[[nodiscard]] LayoutOrder::V LayoutOrder() const;
/// @return the most-recently-computed sum of the heights of all child elements, with padding also taken into account.
[[nodiscard]] float CurrentContentHeight() const;
protected:
LayoutOrder::V layout = LayoutOrder::V::TOP;
float content_height = 0;
};
/// Lays child elements out in a horizontal list.
/// Child element positions are overridden by this widget.
class HorizontalListLayout : public ListLayout
{
class HorizontalListLayout : public ListLayout {
public:
HorizontalListLayout();
explicit HorizontalListLayout(Widget* parent);
void ApplyLayout() override;
void LayoutOrder(LayoutOrder::H order);
[[nodiscard]] LayoutOrder::H LayoutOrder() const;
[[nodiscard]] float CurrentContentWidth() const;
protected:
LayoutOrder::H layout = LayoutOrder::H::LEFT;
float content_width = 0;
};
}

View File

@@ -0,0 +1,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

@@ -28,14 +28,18 @@ namespace JUI {
~Rect() override {}
bool IsMouseInside() const;
AABB2D GetActualRenderBounds() const override;
public:
void PreDraw() override;
void InnerDraw() override;
void PostDraw() override;
void Draw() override;
void Update(float delta) override;
protected:
GLint old_scissor_bounds[4];
bool clip_was_enabled;
};
}

View File

@@ -31,11 +31,11 @@
& &amp;
*/
namespace RichTextFormat
{
// TODO: Simpler "One-liner" Rich text, which is a horizontal sequence of textlabels automatically generated from a format.
class RichTextToken
{
namespace RichTextFormat {
class RichTextToken {
public:
std::string font_face_name;
std::string content;

View File

@@ -20,18 +20,21 @@ namespace JUI
Scene();
~Scene() override {}
void Draw() override;
void SetViewportSize(int w, int h);
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;
bool ObserveMouseMovement(const Vector2 &latest_known_pos) override;
protected:
int viewport_w;
int viewport_h;
Vector2 ui_scale = Vector2(1, 1);
};
}

View File

@@ -10,17 +10,91 @@
/// @edit 2024-10-11
#pragma once
#include <JUI/Widgets/Rect.hpp>
namespace JUI {
class ScrollingRect;
}
namespace JUI
{
class ScrollingRect : public Rect
{
public:
protected:
Vector2 canvas_size;
private:
};
}
// 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 {
public:
~ScrollingRect() override;
/// The default constructor initializes all child elements and members to reasonable defaults.
ScrollingRect();
/// Constructs a ScrollingRect by specifying the parent widget to bind it to.
explicit ScrollingRect(Widget* parent);
public:
JGL::RenderTarget* GetCanvas();
[[nodiscard]] Vector2i CanvasSize() const;
[[nodiscard]] float ScrollPos() const { return scroll; }
[[nodiscard]] Vector2 CanvasPosition() const;
// TODO scrolling in either direction. Assuming vertical scroll for now.
Vector2 CanvasAbsolutePosition() const;
Vector2 CanvasAbsoluteSize() const;
public:
void ScrollPos(float pos) { scroll = pos; }
void CanvasSize(const Vector2i& new_size);
/// Sets the scroll amount of this ScrollingRect.
/// The value will be clamped between ScrollMinimum() and ScrollMaximum().
/// @param value The new scrolling amount to apply.
/// @see ScrollMinimum(), ScrollMaximum().
void SetScrollAmount(float value);
/// Scrolls this ScrollingRect by the specified amount.
/// @param amount The quantity of pixels by which to scroll the object.
/// @see SetScrollAmount()
float Scroll(float amount);
/// @return The amount of pixels this scrolling rect is currently scrolled by.
float GetScrollAmount() const;
[[nodiscard]] bool CanScroll() const;
/// The lower-bound of how far up the scroll can be.
[[nodiscard]] float ScrollMinimum() const;
/// The upper-bound of how far down the scroll can go.
[[nodiscard]] float ScrollMaximum() const;
void SetAutoAdjustScrollAmount(bool value);
bool GetAutoAdjustScrollAmount() const;
void ScrollToTop();
void ScrollToBottom();
// TODO: Figure out how to automatically adjust this as child widgets are added, removed, and changed in size.
float canvas_height = 0;
public:
void InnerDraw() override;
void Draw() override;
void Update(float delta) override;
bool ObserveKeyInput(Key key, bool pressed) override;
bool ObserveMouseWheel(int mwheel) override;
protected:
bool vertical_scrollbar_enabled = true;
bool horizontal_scrollbar_enabled = true;
bool scrollbar_visible = true;
float scrollbar_width = 12;
Color4 scrollbar_color = Colors::Whites::Azure;
float scroll = 0;
float scroll_amount = 10;
JGL::RenderTarget* canvas = nullptr;
bool auto_adjust_scroll_amount = false;
protected:
/* This isn't public because nothing should ever
* have to do this from the outside. -Redacted */
void RecomputeRenderTarget();
void DrawVerticalScrollbar();
};

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,10 +65,9 @@ namespace JUI
float maximum = 1;
float interval = 0.1;
float current;
bool dragging;
// TODO: Later refactor scrubber into a RectBase class?
bool dragging = false;
float scrubber_width = 20;
Color4 scrubber_color;
Color4 scrubber_color = Colors::White;
private:
};
}

View File

@@ -0,0 +1,7 @@
#pragma once
namespace JUI
{
}

View File

@@ -6,8 +6,8 @@
namespace JUI
{
/// A Text Widget
/// @see TextBase.hpp
/// A Widget that displays text within the bounds of it's parent widget.
/// @see TextRect.hpp, TextBase.hpp
class Text : public Widget, public TextBase
{
public:

View File

@@ -9,10 +9,116 @@
/// @desc A box that accepts user keyboard input.
/// @edit 2024-08-02
#pragma once
namespace JUI
{
#include <set>
#include <JUI/Mixins/Clickable.hpp>
#include "TextRect.hpp"
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;
Event<std::string> OnReturn;
TextInputForm();
explicit TextInputForm(Widget* parent);
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);
bool ObserveKeyInput(Key key, bool pressed) override;
bool ObserveMouseInput(MouseButton btn, bool pressed) override;
bool ObserveMouseMovement(const Vector2 &latest_known_pos) override;
[[nodiscard]] std::string GetAutocompleteText() const;
void SetAutoCompleteText(const std::string& text);
[[nodiscard]] Color4 GetAutocompleteTextColor() const;
void SetAutocompleteTextColor(const Color4& color);
[[nodiscard]] bool HideAutocompleteOnSelect() const;
void SetHideAutocompleteOnSelect(bool hide);
[[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;
protected:
bool clear_text_on_return = true;
bool drop_focus_on_return = false;
bool focused = false;
bool selection_enabled;
bool selection_active;
int selection_start_index;
int selection_end_index;
unsigned int cursor_position = 0;
std::string input_buffer;
float cursor_blink_time = 0.f;
Color4 autocomplete_color = Style::InputForm::AutocompleteTextColor;
std::string autocomplete_text = "Hello World";
bool hide_autocomplete_on_select = true;
bool autocomplete_text_enabled = true;
std::set<std::string> blacklist;
private:
};
}

View File

@@ -11,9 +11,17 @@ namespace JUI {
TextRect(Widget* parent);
void Update(float delta) override;
void Draw() override;
[[nodiscard]] bool FitText() const { return fit_text; }
void FitText(bool on) { fit_text = on; }
bool AutoFitSizeToText() const { return fit_size_to_text; }
void AutoFitSizeToText(bool resize){ fit_size_to_text = resize; }
bool TextWrap() const;
void TextWrap(bool enable) const;
void InnerDraw() override;
protected:
bool fit_text = false;
bool fit_size_to_text = false;
bool wrap_text = false;
};
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include <JUI/Widgets/TextRect.hpp>
#include "ReWindow/InputService.h"
namespace JUI
{
class Tooltip : public TextRect
{
public:
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

@@ -0,0 +1,107 @@
#pragma once
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()
{
this->Parent(parent);
}
void SetFont(const JGL::Font& use_my_font)
{
font = use_my_font;
}
TextButton* AddItem(const std::string &name)
{
auto* line_item = new TextButton(layout);
line_item->SetFont(font);
line_item->SetContent(name);
line_item->SetTextSize(16);
line_item->SetTextColor(Colors::Black);
line_item->Size({0, 20, 1, 0});
return line_item;
}
protected:
VerticalListLayout* layout;
JGL::Font font;
private:
};
class UtilityBar : public Rect
{
public:
UtilityBar() : Rect()
{
// TODO: Make a note that all JGL::Font members on widgets need to be initialized to JGL::Fonts::Jupiteroid inside the constructor.
font = JGL::Fonts::Jupiteroid;
this->Size({0, 20, 1, 0});
this->Position({0,0,0,0});
this->BGColor(Colors::White);
this->BorderColor(Colors::Blues::CornflowerBlue);
this->BorderWidth(2);
this->Margin(2_px);
this->BorderMode(BorderMode::Outline);
layout = new HorizontalListLayout(this);
//layout->PaddingLeft(2_px);
layout->PaddingRight(2_px);
//layout->PaddingRight(2_px);
}
explicit UtilityBar(Widget* parent) : UtilityBar() {
this->Parent(parent);
}
TextButton* AddSubmenu(const std::string& name)
{
auto btn = AddButton(name);
return btn;
}
TextButton* AddButton(const std::string& name)
{
auto str_width = font.MeasureString(name, 14);
auto* btn = new TextButton(layout);
btn->SetFont(font);
btn->SetTextSize(14);
btn->SetTextColor(Colors::Black);
btn->Size({static_cast<int>(str_width.x)+16, 0, 0, 1});
btn->BorderWidth(0.f);
btn->SetContent(name);
return btn;
}
void SetFont(const JGL::Font& use_my_font)
{
font = use_my_font;
}
protected:
HorizontalListLayout* layout;
JGL::Font font;
private:
};
}

View File

@@ -23,24 +23,21 @@
#include <JUI/Mixins/Hoverable.hpp>
#include <JUI/Mixins/Clickable.hpp>
#include <JUI/Widgets/TextButton.hpp>
#include <JUI/Widgets/ImageButton.hpp>
namespace JUI
{
namespace JUI {
using J3ML::LinearAlgebra::Vector2;
/// TODO: Scope out.
class DockingStation {};
/// A container widget class, with title bar and buttons,
/// which can be dragged around, resized, and docked into other applicable widgets.
class Window : public Widget, public RectBase, public Clickable, public Hoverable, public Draggable, public Resizable, public Dockable
{
class Window : public Widget, public RectBase, public Clickable, public Hoverable, public Draggable, public Resizable, public Dockable {
public:
/// The default constructor sets a default style for this Window.
Window();
/// 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;
@@ -54,10 +51,24 @@ 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;
/// @return True if this window widget is on-top of all other window-widgets with the same parent.
bool OnTop() const;
/// Brings this window widget to the top of the window-widget stack.
void BringToTop();
/// Moves this window up in the window-widget stack.
void MoveUp() const;
/// Moves this window down in the window-widget stack.
void MoveDown() const;
void TitlebarHeight(int height);
/// Returns the text displayed as the Window's title.
[[nodiscard]] std::string Title() const;
@@ -66,7 +77,7 @@ namespace JUI
/// @see class Draggable.
bool IsDraggable() const;
/// Sets the window to have the ability to be dragged around by the mouse.
void SetDraggable(bool value);
/// Returns whether this Window is able to be 'Docked' into another widget.
@@ -75,29 +86,38 @@ 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;
/// 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,21 +126,64 @@ namespace JUI
void OnClick(const Vector2& m_pos, const MouseButton& m_btn) override;
/// @see class Clickable.
void OnRelease(const Vector2& m_pos, const MouseButton& m_btn, bool still_hovering) override;
/// @see class Hoverable.
void OnHover(const J3ML::LinearAlgebra::Vector2 &MousePos) override
{
Hoverable::OnHover(MousePos);
zindex++;
focused = true;
this->BorderColor(Style::Window::OutlineColor);
this->BGColor(Style::Window::OutlineColor);
}
/// @see class Hoverable.
void OnExit(const J3ML::LinearAlgebra::Vector2 &MousePos) override {
Hoverable::OnExit(MousePos);
zindex--;
focused = false;
this->BorderColor(Style::Window::UnfocusedOutlineColor);
this->BGColor(Style::Window::UnfocusedOutlineColor);
}
bool ObserveMouseInput(JUI::MouseButton btn, bool pressed) override
{
// TODO: Consider how this plays with Clickable::OnClick and Clickable::OnRelease
if (this->visible && this->focused)
return Widget::ObserveMouseInput(btn, pressed);
return false;
}
[[nodiscard]] bool IsOpen() const;
void Open();
void Close();
void SetOpen(bool value);
void Toggle();
protected:
JUI::Rect* Topbar;
JUI::Rect* Viewport;
JUI::Text* TitleLabel;
JUI::TextButton* exit_btn;
JUI::TextButton* fs_btn;
void UpdateInternalWidgetsTitlebarHeight();
protected:
JUI::Rect* Topbar = nullptr;
JUI::Rect* Viewport = nullptr;
JUI::Text* TitleLabel = nullptr;
JUI::ImageButton* exit_btn = nullptr;
JUI::ImageButton* fs_btn = nullptr;
std::string title = "JUI Window";
bool resizable = true;
bool focused = false;
bool open = false;
//bool resizing = false;
bool draggable = true;
bool dockable = false;
int titlebar_height = 16;
Vector2 max_size;
Vector2 min_size; //= {30, 30};
int titlebar_height = Style::Window::TitlebarHeight;
int title_font_size = 16;
Vector2 max_size = {800, 600};
Vector2 min_size = {200, 100}; //= {30, 30};
UDim2 size_when_restart_began;
};
void DragTo(const Vector2 &pos);
};
}

488
main.cpp
View File

@@ -20,203 +20,309 @@
#include <JUI/Widgets/TextRect.hpp>
#include <JUI/Widgets/Image.hpp>
#include <JUI/Widgets/Slider.hpp>
#include <rewindow/types/window.h>
#include <JUI/Widgets/ScrollingRect.hpp>
#include <JUI/Widgets/UtilityBar.hpp>
#include <JUI/Widgets/Checkbox.hpp>
#include <JUI/Widgets/TextInputForm.hpp>
#include <ReWindow/types/Window.h>
#include <ReWindow/Logger.h>
#include "JUI/Widgets/NineSlice.hpp"
#include <JUI/Widgets/Collapsible.hpp>
#include <JUI/Widgets/CommandLine.hpp>
JGL::Font FreeSans;
JUI::Scene* scene;
JGL::Texture* sample_texture;
JGL::Texture* slicer;
JUI::VerticalListLayout* list;
JUI::ScrollingRect* scroller;
JUI::TextRect* widget_count;
JUI::CommandLine* console;
int count_descendants(JUI::Widget* w) {
return w->GetDescendants().size();
}
JUI::Scene* CreateScene() {
using namespace JUI;
Scene *root = new Scene();
auto *root = new Scene();
auto* JUI = new TextRect(root);
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});
// 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});
/*root->DescendantAdded += [&] (auto* node) {
widget_count->SetContent("Widgets: " + count_descendants(root));
};
rect_element->MouseExit += [rect_element](auto coords) {
rect_element->BGColor({0, 64, 0});
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);
console = new JUI::CommandLine(root);
console->Close();
JUI::UILogs.OnLog += [&] (const std::string& msg, Color4 c){ console->Log(msg, c);};
auto* topbar = new UtilityBar(root);
topbar->ZIndex(3);
auto* file = topbar->AddButton("Demos");
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));
auto* open_nineslice = ctx_menu->AddItem("9-Slice Widget Demo");
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("");
auto* open_console = ctx_menu->AddItem("Command Line");
open_console->OnClickEvent += [&] (Vector2 pos, JUI::MouseButton btn)mutable {
console->Open();
};
ctx_menu->ZIndex(3);
};
// End Rect //
topbar->AddButton("Edit");
auto* view = topbar->AddButton("View");
Slider* slide = new Slider(root);
slide->Size({200, 40, 0, 0});
slide->BGColor({255, 255, 0});
slide->Position({0, 0, 0.125, 0.125});
topbar->AddButton("Help");
// 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);
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 bpos = rect_element->Size();
//auto* horizontal = new HorizontalListLayout(root);
//horizontal->ZIndex(1);
//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 //
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);
// Window //
JUI::Window* win_element = new JUI::Window(root);
win_element->SetTitleFont(FreeSans);
win_element->CornerRounding(5);
win_element->Size({50_percent, 50_percent});
win_element->SetTitle("JUI Example Window Widget");
//win_element->Padding(1_px);
auto* column_layout = new VerticalListLayout(column_rect);
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, 20, 0.32f, 0});
button->SetTextColor(Colors::Black);
button->SetContent("Button");
button->AlignLeft();
//button->Padding(5_px);
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, 20, 0.32f, 0});
button2->SetTextColor(Colors::Black);
button2->SetContent("Button");
button2->AlignCenterHorizontally();
auto* button3 = new TextButton(btn_h_layout1);
//button2->Position({5, 105, 0, 0});
button3->Size({0, 20, 0.32f, 0});
button3->SetTextColor(Colors::Black);
button3->SetContent("Button");
button3->AlignRight();
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(column_layout);
input_form->Size({0,24, 1, 0});
input_form->SetContent("");
input_form->SetTextSize(14);
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");
Tween* t = other_window->TweenPosition({50_percent, 50_percent}, {.time = 5});
t->Completed += [] () { std::cout << "Tween type test!!" << std::endl; };
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);
//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->SetFont(FreeSans);
a->Size({0, 20, 1, 0});
//a->FitText(true);
a->Center();
a->SetContent("Greetings. This is the JUI demo program!");
a->SetContent("This is a virtual window.");
TextRect* b = new TextRect(list);
b->SetTextSize(16);
b->SetContent("JUI is my home-coded game menu building toolkit.");
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();
b->SetFont(FreeSans);
TextRect* c = new TextRect(list);
c->SetTextSize(16);
c->SetContent("Settings");
//c->FitText(true);
c->Size({0, 20, 1, 0});
c->Center();
c->SetFont(FreeSans);
TextRect* d = new TextRect(list);
d->SetTextSize(16);
d->SetContent("Exit");
//d->FitText(true);
d->Size({0, 20, 1, 0});
d->Center();
d->SetFont(FreeSans);
Text *text = new Text(rect_element);
text->SetContent("YO MAMA");
text->SetFont(FreeSans);
text->SetTextSize(48);
text->SetTextColor({255, 0, 0});
root->SetViewportSize(800, 600);
root->SetViewportSize({800, 600});
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() {
gladLoadGL();
JGL::Update(getSize());
JGL::InitTextEngine();
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
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // NOTE: This MUST be called for text rendering to work properly!!!
}
void Update()
void Update(float elapsed)
{
scene->Update(0.f);
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->canvas_height += text->GetAbsoluteSize().y;
}
}
void Draw()
@@ -224,17 +330,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();
JGL::Update(getSize());
scene->SetViewportSize(getSize().x, getSize().y);
Update(elapsed);
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();
}
@@ -244,61 +352,87 @@ public:
return true;
}
void OnMouseButtonUp(const ReWindow::WindowEvents::MouseButtonUpEvent &) override
void OnMouseButtonUp(const ReWindow::MouseButtonUpEvent &) override
{
}
void OnMouseButtonDown(const ReWindow::WindowEvents::MouseButtonDownEvent &) override
void OnMouseButtonDown(const ReWindow::MouseButtonDownEvent &) override
{
}
void OnMouseMove(const ReWindow::WindowEvents::MouseMoveEvent &) override
void OnMouseMove(const ReWindow::MouseMoveEvent &e) override
{
//JUI::UILogs.Log("Big Stepper");
Vector2 new_mouse_pos = Vector2(e.Position.x, e.Position.y);
if (scene->ObserveMouseMovement(new_mouse_pos))
return;
}
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;
if (scene->ObserveMouseWheel(w.WheelMovement))
return;
/// As a demo, change the scene's UI scale.
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);
window->setVsyncEnabled(false);
window->setResizable(true);
window->SetFullscreen(false);
window->SetVsyncEnabled(false);
window->SetResizable(true);
JGL::Update({800, 600});
FreeSans = JGL::Font("assets/fonts/FreeSans.ttf");
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.x, size.y);
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);
};
window->OnMouseMoveEvent += [&] (MouseMoveEvent e) { };
window->OnMouseButtonUpEvent += [&] (MouseButtonUpEvent e) {
/// Invalid operands to binary expression 'MouseButton' and 'const MouseButton'
@@ -321,13 +455,17 @@ int main()
window->OnMouseButtonDownEvent += [&] (MouseButtonDownEvent e) {};
window->OnKeyDownEvent += [&] (KeyDownEvent e) {};
window->OnKeyDownEvent += [&] (KeyDownEvent e) {
scene->ObserveKeyInput(e.key, true);
};
window->OnKeyUpEvent += [&] (KeyUpEvent e) {};
window->OnKeyUpEvent += [&] (KeyUpEvent e) {
scene->ObserveKeyInput(e.key, false);
};
while (window->isAlive()) {
window->pollEvents();
window->refresh();
while (window->IsAlive()) {
//window->PollEvents();
window->ManagedRefresh();
}
return 0;
}

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,40 +4,43 @@
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) {
// Background rect
J2D::Begin();
if (corner_rounding_radius > 0)
J2D::FillRoundedRect(bg_color, abs_pos, abs_size, corner_rounding_radius);
J2D::FillRoundedRect(bgColor, abs_pos, abs_size, corner_rounding_radius);
else
J2D::FillRect(bg_color, abs_pos, abs_size);
// Outline rect
J2D::FillRect(bgColor, abs_pos, abs_size);
// Outline rect - compute the size change to fit the border accurately.
Vector2 border_offset = {0, 0};
if (border_mode == BorderMode::Inset)
@@ -47,19 +50,14 @@ namespace JUI {
if (border_mode == BorderMode::Outline)
border_offset = {border_width/2.f, border_width/2.f};
// TODO: implement border_mode behavior when rendering here.
if (border_width > 0)
{
// Draw the outline.
if (border_width > 0) {
if (corner_rounding_radius > 0)
J2D::OutlineRoundedRect(border_color, abs_pos - border_offset, abs_size + (border_offset*2), corner_rounding_radius, border_width);
J2D::OutlineRoundedRect(borderColor, abs_pos - border_offset, abs_size + (border_offset*2), corner_rounding_radius, border_width);
else
J2D::OutlineRect(border_color, abs_pos - border_offset, abs_size + (border_offset*2), border_width);
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

@@ -1,124 +1,144 @@
#include <JUI/Base/TextBase.hpp>
#include "JGL/JGL.h"
#include <JGL/JGL.h>
using namespace JUI;
void TextBase::SetContent(const std::string& content) { this->content = content; state_redraw = true; }
namespace JUI {
void TextBase::SetContent(const std::string &content) { this->content = content; }
void TextBase::SetTextColor(const Color4& color) { this->text_color = color; state_redraw = true; }
void TextBase::SetTextColor(const Color4 &color) { this->text_color = color; }
void TextBase::SetOutlineColor(const Color4& color) { this->outline_color = color; }
void TextBase::SetOutlineColor(const Color4 &color) { this->outline_color = color; }
void TextBase::SetHorizontalTextAlign(const TextAlign::H& align) { this->h_align = align;}
void TextBase::SetHorizontalTextAlign(const TextAlign::H &align) { this->h_align = align;}
void TextBase::SetVerticalTextAlign(const TextAlign::V& align) { this->v_align = align; }
void TextBase::SetVerticalTextAlign(const TextAlign::V &align) { this->v_align = align; }
void TextBase::SetTextAlign(const TextAlign::H &h_align, const TextAlign::V &v_align) {
SetHorizontalTextAlign(h_align);
SetVerticalTextAlign(v_align);
}
void TextBase::AlignLeft() { SetHorizontalTextAlign(TextAlign::H::Left);}
void TextBase::AlignRight() { SetHorizontalTextAlign(TextAlign::H::Right);}
void TextBase::AlignTop() {SetVerticalTextAlign(TextAlign::V::Top);}
void TextBase::AlignBottom() {SetVerticalTextAlign(TextAlign::V::Bottom);}
void TextBase::AlignCenterBoth() {
SetVerticalTextAlign(TextAlign::V::Center);
SetHorizontalTextAlign(TextAlign::H::Center);
}
void TextBase::Center() { AlignCenterBoth(); }
void TextBase::AlignCenterHorizontally() {
SetHorizontalTextAlign(TextAlign::H::Center);
}
void TextBase::AlignCenterVertically() {
SetVerticalTextAlign(TextAlign::V::Center);
}
void TextBase::SetWordWrap(bool wrap) { word_wrap = wrap; }
void TextBase::Draw(const Vector2 &abs_pos, const Vector2 &abs_size) {
// Calculate how much to origin the text based on alignment.
float align_x = abs_pos.x;
float align_y = abs_pos.y;
auto bounds = this->GetFont().MeasureString(this->content, this->text_size);
if (h_align == TextAlign::H::Left) {
// Render from left-side of boundary.
align_x = abs_pos.x;
}
if (h_align == TextAlign::H::Center) {
// Render from horizontal center, origin by half of the text's width.
align_x = abs_pos.x + (abs_size.x / 2) - (bounds.x / 2);
}
if (h_align == TextAlign::H::Right) {
// Render from right-side of boundary, origin by text width.
align_x = abs_pos.x + abs_size.x - bounds.x;
}
if (v_align == TextAlign::V::Top) {
// Render from top of boundary.
align_y = abs_pos.y;
}
if (v_align == TextAlign::V::Center) {
// Render from vertical center, origin by half of the text's height.
align_y = abs_pos.y + (abs_size.y / 2) - (bounds.y / 2);
}
if (v_align == TextAlign::V::Bottom) {
// Render from bottom of boundary, origin by the text's height.
align_y = abs_pos.y + abs_size.y - bounds.y;
}
// Now we render.
float scale = 1.f;
JGL::J2D::Begin();
JGL::J2D::DrawString(
this->text_color,
this->content,
align_x, align_y,
scale,
this->text_size,
this->set_font);
JGL::J2D::End();
}
std::string TextBase::GetContent() const { return content;}
Color4 TextBase::GetTextColor() const { return text_color;}
Color4 TextBase::GetOutlineColor() const { return outline_color;}
TextAlign::H TextBase::GetHorizontalTextAlign() const { return h_align;}
TextAlign::V TextBase::GetVerticalTextAlign() const {return v_align;}
Vector2 TextBase::GetTextBounds() const { return GetFont().MeasureString(content, text_size); }
JGL::Font TextBase::GetFont() const { return set_font;}
u32 TextBase::GetTextSize() const { return text_size; }
void TextBase::SetFont(const JGL::Font &font) {
set_font = font;
}
TextBase::TextBase() {}
void TextBase::SetTextSize(u32 size) {
text_size = size;
}
void TextBase::Update(float delta) {}
void TextBase::SetTextAlign(const TextAlign::H& h_align, const TextAlign::V& v_align) {
SetHorizontalTextAlign(h_align);
SetVerticalTextAlign(v_align);
}
void TextBase::AlignLeft() { SetHorizontalTextAlign(TextAlign::H::Left); }
void TextBase::AlignRight() { SetHorizontalTextAlign(TextAlign::H::Right); }
void TextBase::AlignTop() { SetVerticalTextAlign(TextAlign::V::Top); }
void TextBase::AlignBottom() { SetVerticalTextAlign(TextAlign::V::Bottom); }
void TextBase::AlignCenterBoth() {
SetVerticalTextAlign(TextAlign::V::Center);
SetHorizontalTextAlign(TextAlign::H::Center);
}
void TextBase::Center() { AlignCenterBoth(); }
void TextBase::AlignCenterHorizontally() {
SetHorizontalTextAlign(TextAlign::H::Center);
}
void TextBase::AlignCenterVertically() {
SetVerticalTextAlign(TextAlign::V::Center);
}
void TextBase::SetWordWrap(bool wrap) { word_wrap = wrap; }
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)
return;
// Render from left-side of boundary.
if (h_align == TextAlign::H::Left)
align_x = abs_pos.x;
// Render from horizontal center, origin by half of the text's width.
if (h_align == TextAlign::H::Center)
align_x = abs_pos.x + (abs_size.x / 2) - (bounds.x / 2);
// Render from right-side of boundary, origin by text width.
if (h_align == TextAlign::H::Right)
align_x = abs_pos.x + abs_size.x - bounds.x;
// Render from top of boundary.
if (v_align == TextAlign::V::Top)
align_y = abs_pos.y;
// Render from vertical center, origin by half of the text's height.
if (v_align == TextAlign::V::Center)
align_y = abs_pos.y + (abs_size.y / 2) - (bounds.y / 2);
// Render from bottom of boundary, origin by the text's height.
if (v_align == TextAlign::V::Bottom)
align_y = abs_pos.y + abs_size.y - bounds.y;
float scale = 1.f;
bool use_render_target = false;
// Has to be checked because this function lets you pass in parameters instead
// of using the ones we have saved. Which again, defeats the point. - Redacted.
if (content == this->content) {
// if Render Target needs updating.
if (state_redraw) {
text_canvas->Resize({(int) bounds.x, (int) bounds.y});
J2D::Begin(text_canvas, true);
J2D::DrawString(color, content, 0, 0, scale, size, this->set_font);
J2D::End();
state_redraw = false;
}
use_render_target = true;
}
Vector2 text_pos = {align_x, align_y};
//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();
}
void TextBase::Draw(const Vector2& abs_pos, const Vector2& abs_size) {
Draw(abs_pos, abs_size, this->content, this->text_size, this->text_color);
}
std::string TextBase::GetContent() const { return content; }
Color4 TextBase::GetTextColor() const { return text_color; }
Color4 TextBase::GetOutlineColor() const { return outline_color; }
TextAlign::H TextBase::GetHorizontalTextAlign() const { return h_align; }
TextAlign::V TextBase::GetVerticalTextAlign() const { return v_align; }
Vector2 TextBase::GetTextBounds() { return GetFont().MeasureString(content, text_size); }
JGL::Font TextBase::GetFont() const { return set_font; }
u32 TextBase::GetTextSize() const { return text_size; }
void TextBase::SetFont(const JGL::Font& font) {
set_font = font;
state_redraw = true;
}
void TextBase::SetTextSize(u32 size) {
text_size = size;
state_redraw = true;
}
void TextBase::Update(float delta) {}

View File

@@ -2,343 +2,631 @@
#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;
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;
// 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.");
// 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(newParent->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() {}*/
Vector2 Widget::GetAbsolutePosition() const {
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();
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);
}
auto abs_size = GetAbsoluteSize();
Vector2 Widget::GetAbsoluteMarginTopLeft() {
// TODO: Implement correctly.
return {0,0};
}
auto anchor_offset = abs_size * AnchorPoint();
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.
UDim padding_h = parent->PaddingLeft();
UDim padding_v = parent->PaddingTop();
UDim padding_h = parent->PaddingLeft() + parent->PaddingRight();
UDim padding_v = parent->PaddingTop() + parent->PaddingBottom();
UDim2 pad_bottom_left = {padding_h, padding_v};
float calculated_padding_x = padding_h.Pixels + (padding_h.Scale * parent_abs_size.x);
float calculated_padding_y = padding_v.Pixels + (padding_v.Scale * parent_abs_size.y);
return ComputeElementPadding(parent->GetAbsoluteSize(), pad_bottom_left);
}
Vector2 padding_offset = {calculated_padding_x, calculated_padding_y};
Vector2 Widget::GetAbsoluteMarginBottomRight() {
// TODO: Implement correctly.
return {0,0};
}
Vector2 absolute_position =
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 =
parent_abs_pos + child_pos_pixels + (parent_abs_size * child_pos_scale) + padding_offset;
return absolute_position - anchor_offset;
}
// TODO: implement padding shrinking abs_size
Vector2 Widget::GetAbsoluteSize() const {
Vector2 child_size_scale = this->Size().GetScale();
Vector2 child_size_px = this->Size().GetPixels();
Vector2 parent_abs_size = this->GetParent()->GetAbsoluteSize();
UDim padding_h = parent->PaddingLeft() + parent->PaddingRight();
float final_pad_x = padding_h.Pixels + (padding_h.Scale * parent->GetAbsoluteSize().x);
UDim padding_v = parent->PaddingTop() + parent->PaddingBottom();
float final_pad_y = padding_v.Pixels + (padding_v.Scale * parent->GetAbsoluteSize().y);
Vector2 pad_size_reduction = {final_pad_x, final_pad_y};
Vector2 abs_size = child_size_px + (parent_abs_size * child_size_scale) - pad_size_reduction;
// TODO: Take into account constraints on widgets.
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::Draw() {
if (!visible)
return;
void Widget::UpdateTweens(float elapsed) {
for (Tween* t: tweens)
t->Update(elapsed);
DrawChildWidgets();
}
// TODO: Remove tweens if "dead"
void Widget::Update(float delta) {
UpdateChildWidgets(delta);
}
void Widget::DrawChildWidgets() {
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) {
tweens.erase(std::remove_if(tweens.begin(), tweens.end(), [](Tween* t){
if (t->HasCompleted())
{
return true;
}
return false;
}), tweens.end());
}
void Widget::Update(float delta) {
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;
}
bool Widget::ObserveMouseInput(MouseButton btn, bool pressed) {
mbtn = btn;
mb_state = pressed;
for (Widget* child : children) {
if (child->ObserveMouseInput(btn, pressed))
return true;
}
return false;
}
void Widget::ObserveMouseInput(MouseButton btn, bool pressed) {
mbtn = btn;
mb_state = pressed;
bool Widget::ObserveMouseMovement(const Vector2 &latest_known_pos) {
last_known_mouse_pos = latest_known_pos;
for (Widget* child : children)
{
child->ObserveMouseInput(btn, pressed);
}
for (Widget* child: children)
if (child->ObserveMouseMovement(latest_known_pos))
return true;
return false;
}
bool Widget::ObserveKeyInput(Key key, bool pressed) {
for (Widget* child : children)
if (child->ObserveKeyInput(key, pressed))
return true;
return false;
}
bool Widget::ObserveMouseWheel(int mwheel) {
// TODO: Log that this is a stub.
for (auto& child : children) {
if (child->ObserveMouseWheel(mwheel))
return true;
}
return false;
}
void Widget::ObserveMouseMovement(const Vector2 &latest_known_pos) {
last_known_mouse_pos = latest_known_pos;
for (Widget* child : children)
{
child->ObserveMouseMovement(latest_known_pos);
}
}
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;
}
bool Widget::IsAncestorOf(Widget *descendant) const {
if (descendant == nullptr)
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;
}
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;
}
Widget *Widget::Add(Widget *newChild) {
newChild->Parent(this);
return newChild;
}
Widget *Widget::Add(Widget *newChild) {
newChild->Parent(this);
return newChild;
}
void Widget::AnchorPoint(const Vector2& point) {
anchor_point = point;
}
void Widget::AnchorPoint(const Vector2& point) {
anchor_point = point;
}
// TODO: Implement Z-Index sorting in render routine.
int Widget::ZIndex() const { return zindex;}
int Widget::ZIndex() const { return zindex;}
void Widget::ZIndex(int new_zindex) { zindex = new_zindex; }
void Widget::ZIndex(int new_zindex) { zindex = new_zindex; }
AABB2D Widget::GetActualRenderBounds() const {
return {GetAbsolutePosition(), GetAbsoluteSize()};
}
void Widget::SetViewportSize(const Vector2 &vps) {
viewport_size = vps;
for(auto& child : children)
child->SetViewportSize(viewport_size);
}
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);
return {pad_x, pad_y};
}
AABB2D Widget::AbsoluteBounds() const {
return {GetAbsolutePosition(), GetAbsoluteSize()};
}
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);
};
Tween* t = new Tween(updateTillGoalReached, params);
tweens.push_back(t);
return t;
}
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);
};
Tween* t = new Tween(updateTillGoalReached, params);
tweens.push_back(t);
return t;
}
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);
};
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;
}
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(step);
};
Tween* t = new Tween(updateTillGoalReached, info);
tweens.push_back(t);
return t;
}
float Widget::ComputeElementPadding(float size, const UDim &padding) {
return padding.Pixels + (padding.Scale * size);
}
AABB2D Widget::GetActualRenderBounds() const {
return {GetAbsolutePosition(), GetAbsoluteSize()};
}
}

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

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

View File

@@ -2,8 +2,45 @@
namespace JUI
{
// TODO: This is duplicated here and in ReCaveGame::Loggers.cpp
// Bring into jlog as utility functions.
std::string format_timestamp(jlog::Timestamp ts) {
return std::format(
"{}-{}-{} {}:{}:{}.{}",
ts.Year(),
ts.Month(),
ts.Day(),
ts.Hour().count(),
ts.Minute().count(),
ts.Second().count(),
ts.Millisecond().count());
}
// TODO: This is duplicated here and in ReCaveGame::Loggers.cpp
// Bring into jlog as utility functions.
std::string format_source_location(const std::source_location& location) {
return std::format("{} @ {}:{}", location.function_name(), location.file_name(), location.line());
}
JUILogger::JUILogger(const std::string &context, std::ofstream &file, Color4 contextColor, Color4 timestampColor,
Color4 locationColor, Color4 pointerColor, Color4 messageColor)
: jlog::GenericLogger(context, file, contextColor, timestampColor, locationColor, pointerColor, messageColor) { }
void JUILogger::Log(const std::string &message, const std::source_location &location, const jlog::Timestamp &ts) {
GenericLogger::Log(message, location, ts);
std::string formatted_timestamp = format_timestamp(ts);
std::string formatted_source_location = format_source_location(location);
std::string final_result = std::format("[{}] [{}] []", formatted_timestamp, this->ConsoleLogger::context, message);
OnLog.Invoke(final_result, messageColor);
}
std::ofstream GlobalLogFile("latest.log", std::ios_base::app);
jlog::GenericLogger UILogs {"JUI", GlobalLogFile, Colors::Green, Colors::Gray, Colors::Gray, Colors::Green, Colors::White};
JUILogger UILogs {"JUI", GlobalLogFile, Colors::Green, Colors::Gray, Colors::Gray, Colors::Green, Colors::White};
}

View File

@@ -6,9 +6,23 @@ void JUI::Hoverable::OnHover(const Vector2 &MousePos) {
void JUI::Hoverable::OnExit(const Vector2 &MousePos) {
OnExitEvent.Invoke(MousePos);
//tooltip_threshold = 0;
}
void JUI::Hoverable::Update(const Vector2 &m_pos, float delta) {
/*if (tooltip != nullptr)
{
if (IsHovered()) {
tooltip->Visible(true);
tooltip->Position(UDim2::FromPixels(m_pos.x, m_pos.y));
} else {
tooltip->Visible(false);
}
}*/
if (IsHovered() && !hover_debounce) {
OnHover(m_pos);
hover_debounce = true;
@@ -19,3 +33,5 @@ void JUI::Hoverable::Update(const Vector2 &m_pos, float delta) {
hover_debounce = false;
}
}

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,65 +2,22 @@
#include <JUI/Widgets/Button.hpp>
#include <jlog/Logger.hpp>
namespace JUI
{
Button::Button(): Widget() {}
namespace JUI {
Button::Button(): Rect(), Clickable() {
Name("Button");
BGColor(BaseBGColor());
BorderColor(BaseBorderColor());
}
Button::Button(Widget *parent): Clickable() {
Button::Button(Widget *parent): Button() {
this->Parent(parent);
};
void Button::Draw() {
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();
}
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,
@@ -70,9 +27,22 @@ namespace JUI
return;
Clickable::OnRelease(mouse_pos, btn, still_hovering);
}
BGColor(BaseBGColor());
BorderColor(BaseBorderColor());
void Button::UpdateVisualState() {
if (Disabled()) {
BGColor(DisabledBGColor());
BorderColor(DisabledBorderColor());
} else if (IsClicked()) {
BGColor(PressedBGColor());
BorderColor(PressedBorderColor());
} else if (IsHovered()) {
BGColor(HoveredBGColor());
BorderColor(HoveredBorderColor());
} else {
BGColor(BaseBGColor());
BorderColor(BaseBorderColor());
}
}
void Button::Update(float delta) {
@@ -92,7 +62,11 @@ namespace JUI
OnRelease(last_known_mouse_pos, mbtn, IsHovered());
}
UpdateVisualState();
prev_mb_state = mb_state;
Rect::Update(delta);
}
void Button::Enable() {
@@ -134,8 +108,6 @@ namespace JUI
if (Disabled() || IsClicked())
return;
BGColor(HoveredBGColor());
BorderColor(HoveredBorderColor());
}
void Button::OnExit(const Vector2 &MousePos) {
@@ -143,9 +115,6 @@ namespace JUI
if (Disabled() || IsClicked())
return;
BGColor(BaseBGColor());
BorderColor(BaseBorderColor());
}
Color4 Button::HoveredBGColor() const { return hover_bg; }
@@ -180,5 +149,19 @@ namespace JUI
void Button::DisabledBorderColor(const Color4 &color) { disabled_border = color; }
void Button::BorderColors(const Color4 &base, const Color4 &hover, const Color4 &pressed, const Color4 &disabled) {
BaseBorderColor(base);
HoveredBorderColor(hover);
PressedBorderColor(pressed);
DisabledBGColor(disabled);
UpdateVisualState();
}
void Button::BGColors(const Color4 &base, const Color4 &hover, const Color4 &pressed, const Color4 &disabled) {
BaseBGColor(base);
HoveredBGColor(hover);
PressedBGColor(pressed);
DisabledBGColor(disabled);\
UpdateVisualState();
}
}

View File

@@ -1 +1,33 @@
#include <JUI/Widgets/Checkbox.hpp>
#include <JUI/Widgets/Checkbox.hpp>
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,84 @@
#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() {
//Size(saved_size);
lil_arrow->SetContent("\\/");
content_box->Visible(false);
if (resize_anim != nullptr && !resize_anim->HasCompleted()) {
resize_anim->Cancel();
} else {
saved_size = Size();
}
resize_anim = TweenSize(UDim2(saved_size.X, header_height), {
.time = 0.25f
});
collapsed = true;
}
void Collapsible::Expand() {
lil_arrow->SetContent("/\\");
content_box->Visible(true);
if (resize_anim != nullptr && !resize_anim->HasCompleted())
resize_anim->Cancel();
resize_anim = TweenSize(saved_size, {
.time = 0.25f
});
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

@@ -0,0 +1,87 @@
#include <JUI/Widgets/CommandLine.hpp>
namespace JUI
{
// Get current date/time, format is YYYY-MM-DD.HH:mm:ss
const std::string currentDateTime() {
time_t now = time(0);
struct tm tstruct;
char buf[80];
tstruct = *localtime(&now);
// Visit http://en.cppreference.com/w/cpp/chrono/c/strftime
// for more information about date/time format
strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);
return buf;
}
CommandLine::CommandLine() {
this->SetTitle("Console");
message_log_box = new JUI::ScrollingRect(this->ViewportInstance());
message_log_box->Size(JUI::UDim2(0, -input_form_height, 1, 1));
message_log_list = new JUI::VerticalListLayout(message_log_box);
message_log_list->LayoutOrder(JUI::LayoutOrder::V::BOTTOM);
input_box = new JUI::TextInputForm(this->ViewportInstance());
input_box->SetAutoCompleteText(">Type Commands Here");
input_box->SetContent("");
input_box->Size(JUI::UDim2(0, 20, 1, 0));
input_box->Position(JUI::UDim2(0, -20, 0, 1));
input_box->BGColor(Colors::Grays::DarkSlateGray);
input_box->SetTextSize(16);
input_box->SetTextColor(Colors::White);
input_box->SetAutocompleteTextColor(Colors::Gray);
input_box->OnReturn += [this] (const std::string& msg) {
OnInputBoxSend(msg);
Log(std::format("[{}] >{}", currentDateTime(), msg));
};
}
CommandLine::CommandLine(Widget *parent): CommandLine() {
this->Parent(parent);
}
bool CommandLine::Opened() const { return this->open == true;}
bool CommandLine::Closed() const { return this->open == false;}
void CommandLine::Open(bool openVal) { SetOpen(openVal); }
void CommandLine::Close(bool openVal) { SetOpen(openVal);}
void CommandLine::SetOpen(bool openVal) {
this->Visible(openVal);
this->open = openVal;
}
void CommandLine::Log(const std::string &message, const Color4 &color) {
const Color4 light {60, 60, 60};
const Color4 dark {80, 80, 80};
auto* entry = new JUI::TextRect(message_log_list);
entry->Size({100_percent, UDim(message_height, 0)});
entry->SetContent(message);
entry->LayoutOrder(index++);
entry->BGColor(index%2 == 0 ? light : dark);
entry->SetTextColor(color);
entry->SetTextSize(14);
}
bool CommandLine::ObserveKeyInput(Key key, bool pressed) {
if (!IsVisible())
return false;
return Widget::ObserveKeyInput(key, pressed);
}
void CommandLine::OnInputBoxSend(const std::string &message) {
OnInput.Invoke(message);
input_box->SetContent("");
input_box->SetAutoCompleteText("");
}
}

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,26 @@
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() {
content_height = 0;
std::sort(children.begin(), children.end(), layoutOrderSort);
if (layout == LayoutOrder::V::TOP)
{
int consumed_height = 0;
@@ -21,10 +30,18 @@ namespace JUI
// TODO: Implement widget.LayoutOrder property
// TODO: Sort children by LayoutOrder
consumed_height += pad_top.Pixels;
content_height += pad_top.Pixels;
child_widget->Position({0, consumed_height, 0, 0});
consumed_height += child_widget->GetAbsoluteSize().y;
content_height += child_widget->GetAbsoluteSize().y;
consumed_height += pad_bottom.Pixels;
content_height += pad_bottom.Pixels;
}
}
if (layout == LayoutOrder::V::BOTTOM)
@@ -34,13 +51,20 @@ namespace JUI
{
// TODO: Implement widget.LayoutOrder property
// TODO: Sort children by LayoutOrder
consumed_height -= pad_top.Pixels;
content_height += pad_top.Pixels;
consumed_height -= child_widget->GetAbsoluteSize().y;
content_height += child_widget->GetAbsoluteSize().y;
child_widget->Position({0, consumed_height, 0, 0});
consumed_height -= pad_bottom.Pixels;
content_height += pad_bottom.Pixels;
}
}
}
void VerticalListLayout::LayoutOrder(LayoutOrder::V order) {
@@ -49,6 +73,10 @@ namespace JUI
LayoutOrder::V VerticalListLayout::LayoutOrder() const { return layout;}
float VerticalListLayout::CurrentContentHeight() const {
return content_height;
}
HorizontalListLayout::HorizontalListLayout() : ListLayout() {}
@@ -59,15 +87,25 @@ 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)
{
consumed_width += pad_left.Pixels;
content_width += pad_left.Pixels;
child_widget->Position({consumed_width, 0, 0, 0});
consumed_width += child_widget->GetAbsoluteSize().x;
content_width += child_widget->GetAbsoluteSize().x;
consumed_width += pad_right.Pixels;
content_width += pad_right.Pixels;
}
}
@@ -77,9 +115,15 @@ namespace JUI
for (auto &child_widget : children)
{
consumed_width -= pad_left.Pixels;
content_width += pad_left.Pixels;
consumed_width -= child_widget->GetAbsoluteSize().x;
content_width += child_widget->GetAbsoluteSize().x;
child_widget->Position({consumed_width, 0, 0, 0});
consumed_width -= pad_right.Pixels;
content_width += pad_right.Pixels;
}
}
}
@@ -91,4 +135,6 @@ namespace JUI
LayoutOrder::H HorizontalListLayout::LayoutOrder() const {
return layout;
}
float HorizontalListLayout::CurrentContentWidth() const { return content_width;}
}

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,64 +3,57 @@
#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::Draw() {
if (!visible)
return;
J2D::Begin();
void Rect::PreDraw()
{
Vector2 abs_pos = GetAbsolutePosition();
Vector2 abs_size = GetAbsoluteSize();
auto root_size = GetFamilyTreeRoot()->GetAbsoluteSize();
GLint *old_scissor_bounds;
bool clip_was_enabled;
// TODO: Re-enable clipping
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;
float presumed_screen_height = viewport_size.y;
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();
void Rect::PostDraw()
{
// 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]);
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);
glDisable(GL_SCISSOR_TEST);
}
J2D::End();
}
void Rect::InnerDraw() {
RectBase::Draw(GetAbsolutePosition(), GetAbsoluteSize());
}
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;
}
@@ -68,20 +61,6 @@ namespace JUI {
Widget::Update(delta);
}
bool Rect::IsMouseInside() const {
float x = last_known_mouse_pos.x;
float y = last_known_mouse_pos.y;
auto pos = GetAbsolutePosition();
auto size = GetAbsoluteSize();
//DEBUG(std::format("X {} {} Y {} {}", x,pos.x, y,pos.y));
if (x > pos.x && y > pos.y && x < pos.x + size.x && y < pos.y + size.y){
return true;
}
return false;
}
AABB2D Rect::GetActualRenderBounds() const {
Vector2 border = {border_width, border_width};
@@ -98,6 +77,9 @@ namespace JUI {
return { GetAbsolutePosition(), GetAbsoluteSize()};
}
void Rect::Draw() {
Widget::Draw();
}
}

View File

@@ -1,24 +1,36 @@
#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 {(float)viewport_w,(float)viewport_h};}
Vector2 Scene::GetAbsoluteSize() const { return viewport_size / ui_scale;}
void Scene::Draw() {
Widget::Draw();
}
void Scene::SetViewportSize(int w, int h) {
viewport_w = w;
viewport_h = h;
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; }
bool Scene::ObserveMouseMovement(const Vector2 &latest_known_pos) {
Vector2 new_pos = latest_known_pos / ui_scale;
return Widget::ObserveMouseMovement(new_pos);
}
}

View File

@@ -1,7 +1,185 @@
#include <JUI/Widgets/ScrollingRect.hpp>
namespace JUI
{
using namespace JUI;
}
void ScrollingRect::RecomputeRenderTarget() {
InnerDraw();
}
void ScrollingRect::DrawVerticalScrollbar() {
// TODO: Ratio for size is fine, but the computation for the scrollbar position is just a raw pixel-offset
// when it needs to be a size_ratio of the amount scrolled.
float size_ratio = canvas_height / GetAbsoluteSize().y;
float pos_ratio = scroll / canvas_height;
float travel = (GetAbsoluteSize().y * pos_ratio);
Vector2 scrollbar_base_pos = GetAbsolutePosition() + Vector2(GetAbsoluteSize().x - scrollbar_width, 0);
Vector2 scrollbar_base_size = {scrollbar_width, GetAbsoluteSize().y};
// Draw box for scrollbar to fit into.
J2D::FillRect(Colors::LightGray, scrollbar_base_pos, scrollbar_base_size);
// Compute change in size and position that is appropriate.
Vector2 scrollbar_pos = scrollbar_base_pos + Vector2(0, travel);
Vector2 scrollbar_size = {scrollbar_base_size.x, (scrollbar_base_size.y / size_ratio)};
J2D::FillRoundedRect(scrollbar_color, scrollbar_pos, scrollbar_size, 4);
// Draw little square box at the point where the vertical and horizontal scrollbars would meet.
//Vector2 lil_box_size = {scrollbar_width, scrollbar_width};
//Vector2 lil_box_pos = GetAbsolutePosition() + GetAbsoluteSize() - lil_box_size;
//J2D::FillRect(Colors::DarkGray, lil_box_pos, lil_box_size);
}
void ScrollingRect::Draw() {
// TODO: When `scroll` is 0, we are at the **bottom** of the render target.
// TODO: Flip this around so 0 is at the top
// TODO: Also flip the scroll-bar around.
// TODO: Only recompute when something has changed.
// Q: How do we know when something has changed?
RecomputeRenderTarget();
J2D::DrawPartialRenderTarget(canvas, GetAbsolutePosition(), {0, 0}, Vector2(GetAbsoluteSize().x, GetAbsoluteSize().y));
bool canvas_larger_than_widget = canvas_height > GetAbsoluteSize().y;
if (vertical_scrollbar_enabled && canvas_larger_than_widget) {
DrawVerticalScrollbar();
}
//J2D::DrawString(Colors::Black, std::format("scroll {}, canvas {}", scroll, canvas_height), GetAbsolutePosition().x, GetAbsolutePosition().y, 1, 12);
}
Vector2 ScrollingRect::CanvasPosition() const {
return {GetAbsolutePosition().x, GetAbsolutePosition().y - scroll};
}
// Wouldn't inner draw actually render everything onto the RenderTarget?
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));
// TODO: Do something with this fake canvas-size?
// TODO: How frequently can we update the RenderTarget size.
Size({200_percent, 500_percent});
Rect::InnerDraw();
DrawChildWidgets();
//J2D::DrawPoint(Colors::Blue, {1,1}, 2);
//J2D::DrawPoint(Colors::Blue, Vector2(GetAbsoluteSize()), 2);
// 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(); }
void ScrollingRect::Update(float delta) {
//scroll += delta*5;
//canvas->Resize(Vector2i(GetAbsoluteSize().x, GetAbsoluteSize().y));
Rect::Update(delta);
}
bool ScrollingRect::ObserveKeyInput(Key key, bool pressed) {
if (Widget::ObserveKeyInput(key, pressed))
return true;
// TODO: Forego this in favor of mouse scrolling and clicking.
if (key == Keys::UpArrow && pressed)
{
Scroll(-scroll_amount);
return true;
}
if (key == Keys::DownArrow && pressed)
{
Scroll(scroll_amount);
return true;
}
if (key == Keys::PageDown && pressed) {
ScrollToBottom();
return true;
}
if (key == Keys::PageUp && pressed) {
ScrollToTop();
return true;
}
return false;
}
bool ScrollingRect::CanScroll() const {
return canvas_height > GetAbsoluteSize().y;
}
void ScrollingRect::SetScrollAmount(float value) {
scroll = Math::Clamp(value, ScrollMinimum(), ScrollMaximum());
}
float ScrollingRect::Scroll(float amount) {
SetScrollAmount(scroll + amount);
return scroll;
}
float ScrollingRect::ScrollMaximum() const {
return Math::Max(0.f, canvas_height-GetAbsoluteSize().y);
}
void ScrollingRect::ScrollToTop() {
SetScrollAmount(ScrollMinimum());
}
void ScrollingRect::ScrollToBottom() {
SetScrollAmount(ScrollMaximum());
}
float ScrollingRect::ScrollMinimum() const { return 0; }
float ScrollingRect::GetScrollAmount() const { return scroll; }
bool ScrollingRect::ObserveMouseWheel(int mwheel) {
if (Widget::ObserveMouseWheel(mwheel))
return true;
if (IsMouseInside()) {
Scroll(mwheel*scroll_amount);
return true;
}
return false;
}

View File

@@ -4,7 +4,7 @@
namespace JUI
{
Slider::Slider(JUI::Widget *parent)
Slider::Slider(JUI::Widget *parent) : Slider()
{
this->Parent(parent);
}
@@ -68,8 +68,11 @@ namespace JUI
float value = translated + minimum;
if (current != value)
ValueChanged(value);
current = value;
std::cout << "slider value: " << value << std::endl;
//std::cout << "slider value: " << value << std::endl;
}
Rect::Update(delta);
}
@@ -82,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; }
@@ -112,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);
}
@@ -14,6 +16,7 @@ namespace JUI
void TextButton::Draw() {
Button::Draw();
TextBase::Draw(this->GetAbsolutePosition(), this->GetAbsoluteSize());
// TODO: Factor in padding here.
TextBase::Draw(this->GetAbsolutePosition()+GetAbsolutePaddingTopLeft(), this->GetAbsoluteSize()-GetAbsolutePaddingBottomRight());
}
}

View File

@@ -0,0 +1,253 @@
#include <JUI/Widgets/TextInputForm.hpp>
#include <ReWindow/InputService.h>
namespace JUI {
TextInputForm::TextInputForm() : TextRect(), Clickable() {
Name("TextInputForm");
}
TextInputForm::TextInputForm(Widget* parent) : TextInputForm() {
this->Parent(parent);
}
bool TextInputForm::ObserveMouseMovement(const Vector2 &latest_known_pos) {
return Widget::ObserveMouseMovement(latest_known_pos);
}
std::string TextInputForm::GetAutocompleteText() const { return autocomplete_text;}
void TextInputForm::SetAutoCompleteText(const std::string &text) { autocomplete_text = text;}
Color4 TextInputForm::GetAutocompleteTextColor() const { return autocomplete_color; }
void TextInputForm::SetAutocompleteTextColor(const Color4 &color) { autocomplete_color = color; }
bool TextInputForm::HideAutocompleteOnSelect() const { return hide_autocomplete_on_select; }
void TextInputForm::SetHideAutocompleteOnSelect(bool hide) { hide_autocomplete_on_select = hide; }
bool TextInputForm::AutocompleteTextEnabled() const { return autocomplete_text_enabled; }
void TextInputForm::SetAutocompleteTextEnabled(bool enabled) { autocomplete_text_enabled = enabled; }
bool TextInputForm::ObserveMouseInput(MouseButton btn, bool pressed) {
if (pressed && btn == MouseButton::Left) {
if (IsMouseInside()) {
if (!focused) {
OnSelect.Invoke();
}
focused = true;
return true;
} else {
if (focused)
OnDeselect.Invoke();
focused = false;
}
}
return Widget::ObserveMouseInput(btn, pressed);
}
void TextInputForm::InnerDraw()
{
Rect::InnerDraw();
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 = abs_pos + pad_shifts_pos_by;
Vector2 size = abs_pos - (pad_shrinks_size_by*2.f);
if (!focused || !hide_autocomplete_on_select)
TextBase::Draw(pos, size, autocomplete_text, this->text_size, autocomplete_color);
TextBase::Draw(pos, size, content, text_size, text_color);
//TextRect::InnerDraw();
}
void TextInputForm::Draw() {
TextRect::Draw();
}
void TextInputForm::Update(float elapsed) {
TextRect::Update(elapsed);
// TODO: Make cursor actually blink
if (focused) {
cursor_blink_time += elapsed;
if (cursor_blink_time > 1.f)
cursor_blink_time = 0.f;
if (cursor_blink_time > 0.5f) {
std::string result = input_buffer;
result.insert(cursor_position, 1, '|');
SetContent(result);
} else {
SetContent(input_buffer);
}
}
}
std::string uppercase(const std::string& input) {
std::string s = input;
std::ranges::transform(s, s.begin(), ::toupper);
return s;
}
std::string lowercase(const std::string& input) {
std::string s = input;
std::ranges::transform(s, s.begin(), ::tolower);
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);
}
bool TextInputForm::ObserveKeyInput(Key key, bool pressed) {
Widget::ObserveKeyInput(key, pressed);
if (!pressed || !focused) return false;
if (key == Keys::Return || key == Keys::NumPadReturn) {
SendInput(clear_text_on_return);
return true;
}
if (key == Keys::LeftArrow) {
MoveCursorLeft();
return true;
}
if (key == Keys::RightArrow) {
MoveCursorRight();
return true;
}
if (InputService::IsKeyDown(Keys::LeftControl)) {
if (key == Keys::C) { Copy(); return true; }
if (key == Keys::V) { Paste(); return true; }
if (key == Keys::X) { Cut(); return true; }
}
if (IsNonAlphanumerosymbolic(key)) return false;
if (blacklist.contains(key.Mnemonic)) return false;
if (key == Keys::Backspace) {Backspace(); return true; }
// No other condition met, so we assume we should push this key into the input buffer.
PushKeyToCurrentPlaceInInputBuffer(key);
return true;
}
bool TextInputForm::HasFocus() const { return focused; }
void TextInputForm::SetFocused(bool focused) { this->focused = focused;}
void TextInputForm::GrabFocus() { SetFocused(true); }
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,14 +1,16 @@
#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);
}
void TextRect::Update(float delta) {
if (fit_text)
if (fit_size_to_text)
{
Vector2 abs_pos = this->GetAbsolutePosition();
@@ -39,25 +41,20 @@ namespace JUI {
}
void TextRect::Draw() {
Rect::Draw();
void TextRect::InnerDraw()
{
Rect::InnerDraw();
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

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

View File

@@ -3,37 +3,17 @@
#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);
//this->OnHoverEvent += [&] (Vector2 MousePos) {
//};
//this->OnExitEvent += [&] (Vector2 MousePos) {
// hovered;
//};
/*
this->OnClickEvent += [&] (Vector2 dummy, MouseButton btn) {
if (draggable && btn == MouseButton::Left) {
DEBUG("Window draggable")
SetDrag(true);
}
if (resizable && btn == MouseButton::Right)
DEBUG("Window resizable")
};
*/
// TODO: Move out of Event callback
this->OnReleaseEvent += [&] (Vector2 dummy, MouseButton btn, bool dummy3) {
if (dragging) {
@@ -43,60 +23,74 @@ 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);
Topbar = new Rect(this);
Topbar->Position({0_px, 0_px});
Topbar->Size({100_percent, 20_px});
Topbar->BGColor({92,92,192, 255});
Topbar->Size({100_percent, UDim(titlebar_height, 0)});
//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(titlebar_height);
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->BGColor({92,92,192, 255});
//JGL::Font FreeSans
//exit_btn_text->SetFont(TitleLabel->GetFont());
exit_btn->SetTextSize(titlebar_height);
exit_btn->SetTextColor({255, 0, 0});
//exit_btn->Center();
exit_btn->OnClickEvent += [&] (auto... _) {
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->OnReleaseEvent += [&] (auto... _) {
this->Visible(false);
};
jlog::Debug(std::format("{} {} {} {}", Topbar->Size().X.Pixels, Topbar->Size().Y.Pixels, Topbar->Size().X.Scale,
Topbar->Size().Y.Scale));
//min_size = exit_btn->GetSize() + Topbar->GetSize() + TitleLabel->Size();
//= Topbar->GetSize();//+ exit_btn->Size();
// TODO: fs_btn
}
Window::Window(Widget* parent) : Window() {
@@ -117,11 +111,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);
@@ -130,7 +124,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)
@@ -138,10 +132,10 @@ namespace JUI
Clickable::OnClick(mouse_pos, btn);
clicked = true;
if (draggable && btn == MouseButton::Left)
if (draggable && btn == MouseButton::Left && TitleInstance()->IsMouseInside())
this->SetDrag(true);
if (resizable && btn == MouseButton::Right) {
if (resizable && btn == MouseButton::Right && TitleInstance()->IsMouseInside()) {
this->StartResizing(last_known_mouse_pos);
this->SetResize(true);
size_when_restart_began = Size();
@@ -159,11 +153,17 @@ namespace JUI
this->SetResize(false);
}
void Window::DragTo(const Vector2& pos)
{
this->Position(UDim2{(int) pos.x, (int) pos.y, 0, 0});
}
void Window::Update(float delta) {
if (dragging) {
//jlog::Debug(std::format("mpos {} {}", last_known_mouse_pos.x, last_known_mouse_pos.y));
Vector2 mpos = last_known_mouse_pos - initial_drag_offset;
this->Position(UDim2{(int) mpos.x, (int) mpos.y, 0, 0});
// Move the window with the mouse, accounting for initial offset of window-mouse when we first clicked.
Vector2 goal = last_known_mouse_pos - initial_drag_offset;
DragTo(goal);
}
if (resizing) {
@@ -188,7 +188,6 @@ namespace JUI
float needed_amt_to_resize = min_size.y - abs_size.y;
Size(Size() + UDim2(0, needed_amt_to_resize, 0, 0));
}
}
Widget::Update(delta);
@@ -219,7 +218,7 @@ namespace JUI
}
void Window::SetTitle(const std::string &title) {
GetTitleInstance()->SetContent(title);
TitleInstance()->SetContent(title);
}
void Window::MinSize(const Vector2 &constraint) {
@@ -242,7 +241,65 @@ 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;
}
void Window::CornerRounding(float radius) {
RectBase::CornerRounding(radius);
}
void Window::SetOpen(bool value) {
open = value;
Visible(open);
}
void Window::Toggle() {
SetOpen(!IsOpen());
}
void Window::Close() { SetOpen(false); }
void Window::Open() { SetOpen(true); }
bool Window::IsOpen() const { return open;}
}