/// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "JUI/Widgets/NineSlice.hpp" #include #include #include "JUI/Widgets/ColorPicker.hpp" #include "JUI/Widgets/FpsGraph.hpp" #include "JUI/Widgets/Link.hpp" using namespace JUI; float ui_scale = 1.f; float accum = 0; int iter = 0; JUI::Scene* scene = nullptr; JGL::Texture* sample_texture = nullptr; JGL::Texture* slicer = nullptr; JUI::VerticalListLayout* list = nullptr; JUI::Window* scroller_window = nullptr; JUI::ScrollingRect* scroller = nullptr; JUI::TextRect* widget_count = nullptr; JUI::CommandLine* console = nullptr; JUI::Window* nineslice_demo_window = nullptr; JUI::UtilityBar* topbar = nullptr; /// 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->Content(label); //btn->Position({100_percent - 125_px, 100_percent - 20_px}); // TODO: AUTO SIZE - HORIZONTAL. btn->Size({125_px, 20_px}); btn->TextColor(Colors::Black); return btn; }; auto* ok_btn = new JUI::TextButton(controls_layout); ok_btn->Content("OK, Stop Yapping."); ok_btn->Position({100_percent - 125_px, 100_percent - 20_px}); ok_btn->Size({125_px, 20_px}); ok_btn->TextColor(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->Title("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* t_list = new VerticalListLayout(nineslice_demo_window->ViewportInstance()); { t_list->Padding(10_px); TextRect* a = new TextRect(t_list); { a->TextSize(16); a->Size({0, 20, 1, 0}); a->Center(); a->Content("This is a virtual window."); } TextRect* b = new TextRect(t_list); { b->TextSize(16); b->Size({0, 20, 1, 0}); b->Center(); b->Content("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", [&] { nineslice_demo_window->Toggle(); }); demos->AddButton("Scroll Widget Demo", [&] {}); demos->AddButton("Command Line", [&]{ 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%", [&] { ui_scale += 0.05f; }); view->AddButton("Decrease UI Scale 5%", [&] { ui_scale -= 0.05f; }); } auto* help = topbar->AddButton("About", [&] { }); 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->TextColor(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->TextColor(Colors::Black); button->Content("Button"); button->AlignLeft(); //button->Padding(5_px); auto* tt2 = new JUI::Tooltip(button); tt2->Content("Test 123"); auto* button2 = new TextButton(btn_h_layout1); //button2->Position({5, 105, 0, 0}); button2->Size({0, 20, 0.32f, 0}); button2->TextColor(Colors::Black); button2->Content("Button"); button2->AlignCenterHorizontally(); auto* button3 = new TextButton(btn_h_layout1); //button2->Position({5, 105, 0, 0}); button3->Size({0, 20, 0.32f, 0}); button3->TextColor(Colors::Black); button3->Content("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->TextColor(Colors::Black); button4->Content("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->TextColor(Colors::Black); button5->Content("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->TextColor(Colors::Black); button6->Content("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->Content("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->Content(""); input_form->TextSize(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->Content("A "); radio_a_label->TextSize(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->Content("B "); radio_b_label->TextSize(12); auto* radio_c_btn = new Checkbox(radio_btn_set_layout); radio_c_btn->Size({20_px, 20_px}); auto* link_container = new Rect(collapsible->ContentBox()); { link_container->Size({100_percent, 30_px}); link_container->Position({0_px, 20_px}); auto* link = new JUI::Link(link_container); link->Content("Clickable Link"); link->Clicked += [](auto event) { std::cout << "Link Released" << std::endl; }; } // What the FUCK? /*auto* radio_c_label = new TextRect(radio_btn_set_layout); radio_c_label->BorderWidth(0); radio_c_label->Size({20_px, 20_px}); radio_c_label->AutoFitSizeToText(true); radio_c_label->SetContent("C "); radio_c_label->SetTextSize(12);*/ return widget_list; } JUI::Window* CreateScrollDemoWindow(Widget* root) { auto* scroller_demo = new JUI::Window(root); scroller_demo->Name("ScrollDemo Window"); scroller_demo->Position({10_percent, 10_percent}); scroller_demo->Size({30_percent, 25_percent}); scroller_demo->Title("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->Content()); 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(); auto* graph_window = new JUI::FpsGraphWindow(root); auto* colorpicker_wnd = new JUI::Window(root); colorpicker_wnd->Name("Slider ColorPicker"); colorpicker_wnd->Size({100_px, 150_px}); colorpicker_wnd->MinSize({100, 150}); auto* cpicker = new JUI::ColorPicker(colorpicker_wnd->Content()); topbar = CreateUtilityBar(root); nineslice_demo_window = CreateNinesliceWindow(root); // TODO: This instance will segfault on it's first call to be updated and I have **NO** idea why... //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->Content(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(); if (!focused) std::this_thread::sleep_for(std::chrono::milliseconds(48)); 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; }