Files
ReJUI/main.cpp
2025-05-02 01:18:48 -05:00

593 lines
19 KiB
C++

/// 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 main.cpp
/// @desc Demo Program Entry Point
/// @edit 2024-07-11
/// This is a guided tour through creating, designing, and using a JUI scene.
#include <iostream>
#include <JGL/JGL.h>
#include <JUI/Widgets/Rect.hpp>
#include <JUI/Widgets/RadioButton.hpp>
#include <JUI/Widgets/Window.hpp>
#include <JUI/Widgets/Scene.hpp>
#include <JUI/Widgets/Text.hpp>
#include <JUI/Widgets/ListLayout.hpp>
#include <JUI/Widgets/TextRect.hpp>
#include <JUI/Widgets/Image.hpp>
#include <JUI/Widgets/Slider.hpp>
#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>
using namespace JUI;
float ui_scale = 1.f;
float accum = 0;
int iter = 0;
JUI::Scene* scene;
JGL::Texture* sample_texture;
JGL::Texture* slicer;
JUI::VerticalListLayout* list;
JUI::Window* scroller_window;
JUI::ScrollingRect* scroller;
JUI::TextRect* widget_count;
JUI::CommandLine* console;
JUI::Window* nineslice_demo_window;
JUI::UtilityBar* topbar;
/// Returns the sum total of widgets that are considered "descendnats" of the given widget.
/// A descendant is classified as being a child, or child of a child, and so on, of a given widget.
int count_descendants(JUI::Widget* w) {
return w->GetDescendants().size();
}
/// Creates the window that provides a "Welcome" dialog to the user.
/// Project metadata and copyright information is presented.
JUI::Window* CreateInfoboxWindow(JUI::Widget* root) {
auto* window = new JUI::Window(root);
auto* content_box = new JUI::Rect(window->Content());
content_box->Size({100_percent, 100_percent - 25_pixels});
auto* controls_box = new JUI::Rect(window->Content());
controls_box->Position({0_px, 100_percent - 25_pixels});
controls_box->Size({100_percent, 25_pixels});
auto* controls_layout = new JUI::HorizontalListLayout(controls_box);
auto make_dialog_btn = [controls_layout] (const std::string& label) {
auto* btn = new JUI::TextButton(controls_layout);
btn->SetContent(label);
//btn->Position({100_percent - 125_px, 100_percent - 20_px});
// TODO: AUTO SIZE - HORIZONTAL.
btn->Size({125_px, 20_px});
btn->SetTextColor(Colors::Black);
return btn;
};
auto* ok_btn = new JUI::TextButton(controls_layout);
ok_btn->SetContent("OK, Stop Yapping.");
ok_btn->Position({100_percent - 125_px, 100_percent - 20_px});
ok_btn->Size({125_px, 20_px});
ok_btn->SetTextColor(Colors::Black);
ok_btn->OnClickEvent += [window] (...) mutable {
window->Close();
};
return window;
}
/// Constructs, applies layout, and returns, a JUI::Window which demonstrates the NineSliceRect capability.
/// @param root The Widget to parent the NineSliceRect to.
JUI::Window* CreateNinesliceWindow(JUI::Widget* root) {
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->Close();
nineslice_demo_window->TitlebarHeight(12); {
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(nineslice_demo_window->ViewportInstance()); {
list->Padding(10_px);
TextRect* a = new TextRect(list); {
a->SetTextSize(16); a->Size({0, 20, 1, 0}); a->Center();
a->SetContent("This is a virtual window.");
}
TextRect* b = new TextRect(list); {
b->SetTextSize(16); b->Size({0, 20, 1, 0}); b->Center();
b->SetContent("You can drag it around via left-click, and resize via right-click. Isn't that cool??");
}
}
}
}
return nineslice_demo_window;
}
JUI::UtilityBar* CreateUtilityBar(JUI::Widget* root) {
auto* topbar = new UtilityBar(root); {
topbar->ZIndex(3);
// TODO: Make it so that when you mouse over another option in the *parent* menu, it closes any open submenus.
auto* demos = topbar->AddSubmenu("Demos");
{
demos->AddButton("9-Slice Widget Demo",
[&] mutable { nineslice_demo_window->Toggle(); });
demos->AddButton("Scroll Widget Demo", [&] mutable {});
demos->AddButton("Command Line",
[&] mutable{ console->Toggle();});
auto* test_sub = demos->AddSubmenu("Fruit"); {
test_sub->AddButton("Apples");
test_sub->AddButton("Bananas");
test_sub->AddSeparator(20_px);
auto* berries = test_sub->AddSubmenu("Berries");
{
berries->AddButton("Grapes");
berries->AddButton("Strawberries");
berries->AddButton("Raspberries");
berries->AddButton("Blueberries");
}
}
}
auto* edit = topbar->AddButton("Edit");
auto* view = topbar->AddSubmenu("View"); {
view->AddButton("Increase UI Scale 5%", [&]mutable {
ui_scale += 0.05f;
});
view->AddButton("Decrease UI Scale 5%", [&] mutable{
ui_scale -= 0.05f;
});
}
auto* help = topbar->AddButton("About", [&] mutable {
});
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();
}
return topbar;
}
/// Constructs, styles, and returns a JUI::Rect which contains a sample of each widget in action.
JUI::Rect* CreateWidgetList(JUI::Widget* root) {
auto* widget_list = new Rect(root);
//column_rect->ZIndex(4);
widget_list->Size({0, -24, 0.2f, 1.f});
widget_list->Position({0, 24, 0, 0});
widget_list->BGColor(Colors::Grays::Gainsboro);
auto* column_layout = new VerticalListLayout(widget_list);
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* radio_btn_set = new Rect(collapsible->ContentBox());
radio_btn_set->Size({100_percent, 20_px});
auto* radio_btn_set_layout = new HorizontalListLayout(radio_btn_set);
radio_btn_set_layout->Padding(2_px);
auto* radio_a_btn = new Checkbox(radio_btn_set_layout);
radio_a_btn->Size({20_px, 20_px});
auto* radio_a_label = new TextRect(radio_btn_set_layout);
radio_a_label->BorderWidth(0);
radio_a_label->Size({20_px, 20_px});
radio_a_label->AutoFitSizeToText(true);
radio_a_label->SetContent("A ");
radio_a_label->SetTextSize(12);
auto* radio_b_btn = new Checkbox(radio_btn_set_layout);
radio_b_btn->Size({20_px, 20_px});
auto* radio_b_label = new TextRect(radio_btn_set_layout);
radio_b_label->BorderWidth(0);
radio_b_label->Size({20_px, 20_px});
radio_b_label->AutoFitSizeToText(true);
radio_b_label->SetContent("B ");
radio_b_label->SetTextSize(12);
auto* radio_c_btn = new Checkbox(radio_btn_set_layout);
radio_c_btn->Size({20_px, 20_px});
auto* radio_c_label = new TextRect(radio_btn_set_layout);
radio_c_label->BorderWidth(0);
radio_c_label->Size({20_px, 20_px});
radio_c_label->AutoFitSizeToText(true);
radio_c_label->SetContent("C ");
radio_c_label->SetTextSize(12);
return widget_list;
}
JUI::Window* CreateScrollDemoWindow(Widget* root) {
auto* scroller_demo = new JUI::Window(root);
scroller_demo->Position({10_percent, 10_percent});
scroller_demo->Size({30_percent, 25_percent});
scroller_demo->SetTitle("ScrollingRect Demonstration");
Tween* t = scroller_demo->TweenPosition({50_percent, 50_percent}, {.time = 5});
t->Completed += [] () { std::cout << "Tween type test!!" << std::endl; };
scroller = new JUI::ScrollingRect(scroller_demo->ViewportInstance());
scroller->Size({100_percent, 100_percent});
scroller->BGColor(Colors::Reds::LightCoral);
list = new JUI::VerticalListLayout(scroller);
list->LayoutOrder(JUI::LayoutOrder::V::BOTTOM);
return scroller_demo;
}
JUI::Window* CreateAnimationDemoWindow() {
return nullptr;
}
JUI::Scene* CreateScene() {
auto *root = new Scene();
topbar = CreateUtilityBar(root);
nineslice_demo_window = CreateNinesliceWindow(root);
scroller_window = CreateScrollDemoWindow(root);
CreateInfoboxWindow(root);
console = new JUI::CommandLine(root); {
console->Close();
JUI::UILogs.OnLog += [&] (const std::string& msg, Color4 c){ console->Log(msg, c);};
}
auto* widget_list = CreateWidgetList(root);
//auto* horizontal = new HorizontalListLayout(root);
//horizontal->ZIndex(1);
//auto* separator_a = new Rect(radio_btn_set_layout());
//nineslice_demo_window->Padding(1_px);
// End Window //
root->SetViewportSize({800, 600});
return root;
}
class JUIDevelopmentTestWindow : public ReWindow::OpenGLWindow {
public:
void initGL() {
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(float elapsed)
{
widget_count->SetContent(std::format("Widgets: {}", count_descendants(scene)));
using namespace JUI::UDimLiterals;
accum += elapsed;
scene->Update(elapsed);
/// As a demo, change the scene's UI scale.
scene->GlobalUIScale({ui_scale, ui_scale});
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()
{
scene->Draw();
}
JUIDevelopmentTestWindow(const std::string& title, int w, int h) : ReWindow::OpenGLWindow(title, w, h, 2, 1) {}
void OnRefresh(float elapsed) override {
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->SwapBuffers();
}
bool OnResizeRequest(const ReWindow::WindowResizeRequestEvent& e) override {
std::cout << "RESIZED:" << e.Size.x << ", " << e.Size.y << std::endl;
return true;
}
void OnMouseButtonUp(const ReWindow::MouseButtonUpEvent &) override
{
}
void OnMouseButtonDown(const ReWindow::MouseButtonDownEvent &) 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 {
}
void OnMouseWheel(const ReWindow::MouseWheelEvent &w) override {
ui_scale += w.WheelMovement * 0.125f;
if (scene->ObserveMouseWheel(w.WheelMovement))
return;
/// As a demo, change the scene's UI scale.
scene->GlobalUIScale({ui_scale, ui_scale});
}
};
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->Open();
window->initGL();
window->SetFullscreen(false);
window->SetVsyncEnabled(false);
window->SetResizable(true);
JGL::Update({800, 600});
sample_texture = new JGL::Texture("assets/ld.png");
slicer = new JGL::Texture("assets/9slice.png");
scene = CreateScene();
inspect_widget(scene);
window->OnResizeRequestEvent += [&] (ReWindow::WindowResizeRequestEvent e){
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) { };
window->OnMouseButtonUpEvent += [&] (MouseButtonUpEvent e) {
/// Invalid operands to binary expression 'MouseButton' and 'const MouseButton'
if (e.Button == MouseButtons::Left)
scene->ObserveMouseInput(JUI::MouseButton::Left, false);
if (e.Button == MouseButtons::Middle)
scene->ObserveMouseInput(JUI::MouseButton::Middle, false);
if (e.Button == MouseButtons::Right)
scene->ObserveMouseInput(JUI::MouseButton::Right, false);
};
window->OnMouseButtonDownEvent += [&] (MouseButtonDownEvent e) {
if (e.Button == MouseButtons::Left)
scene->ObserveMouseInput(JUI::MouseButton::Left, true);
if (e.Button == MouseButtons::Middle)
scene->ObserveMouseInput(JUI::MouseButton::Middle, true);
if (e.Button == MouseButtons::Right)
scene->ObserveMouseInput(JUI::MouseButton::Right, true);
};
window->OnMouseButtonDownEvent += [&] (MouseButtonDownEvent e) {};
window->OnKeyDownEvent += [&] (KeyDownEvent e) {
scene->ObserveKeyInput(e.key, true);
};
window->OnKeyUpEvent += [&] (KeyUpEvent e) {
scene->ObserveKeyInput(e.key, false);
};
while (window->IsAlive()) {
//window->PollEvents();
window->ManagedRefresh();
}
return 0;
}