diff --git a/.clang-format b/.clang-format index e5e9a2b..7f8e865 100644 --- a/.clang-format +++ b/.clang-format @@ -76,7 +76,7 @@ BreakBeforeTernaryOperators: true BreakConstructorInitializers: AfterColon BreakInheritanceList: BeforeColon BreakStringLiterals: true -ColumnLimit: 400 +ColumnLimit: 120 CommentPragmas: "^ IWYU pragma:" CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false @@ -157,7 +157,7 @@ RemoveBracesLLVM: false RequiresClausePosition: OwnLine SeparateDefinitionBlocks: Leave ShortNamespaceLines: 1 -SortIncludes: CaseSensitive +SortIncludes: Never SortJavaStaticImport: Before SortUsingDeclarations: true SpaceAfterCStyleCast: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 872ad95..c41b819 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,4 +26,5 @@ add_subdirectory(Graphics) add_subdirectory(RayTracer) add_subdirectory(DataAnalysis) add_subdirectory(Objects) +add_subdirectory(Widgets) add_subdirectory(LibraryViewer) diff --git a/Connection/private/bindings/Network.cpp b/Connection/private/bindings/Network.cpp index 8cdcc92..1d5a33f 100644 --- a/Connection/private/bindings/Network.cpp +++ b/Connection/private/bindings/Network.cpp @@ -42,10 +42,7 @@ public: Server::Server() { mContext = new ServerContext(); } -Server::~Server() { - delete mContext; - assert(0); -} +Server::~Server() { delete mContext; } void Server::start(ualni port) { mContext->socket.open(asio::ip::tcp::v4()); @@ -61,29 +58,23 @@ Server::Socket Server::accept() { return clientSocket; } -int1* Server::read(Socket client) { - short messageSize; - if (asio::read(*(ServerContext::Socket*) client, asio::buffer(&messageSize, 2)) == -1) { - std::cerr << "Failed to read from socket" << std::endl; - ((ServerContext::Socket*) client)->close(); - return nullptr; +bool Server::read(Socket client, int1* message, halni messageSize) { + try { + std::size_t bytesRead = asio::read(*(ServerContext::Socket*) client, asio::buffer(message, messageSize)); + return bytesRead == messageSize; + } catch (const std::exception& e) { + std::cerr << "Error during read: " << e.what() << "\n"; + return false; } - auto message = new char[messageSize + 1]; - message[messageSize] = '\0'; - if (asio::read(*(ServerContext::Socket*) client, asio::buffer(message, messageSize)) == -1) { - std::cerr << "Failed to read from socket" << std::endl; - memcpy(message, "Client wanna say something but i cant read", 100); - } - return message; } -void Server::write(Socket client, const char* message) { - auto messageSize = (short) strlen(message); - if (asio::write(*(ServerContext::Socket*) client, asio::buffer(&messageSize, 2)) == -1) { - std::cerr << "Failed to write to socket" << std::endl; - } - if (asio::write(*(ServerContext::Socket*) client, asio::buffer(message, messageSize)) == -1) { - std::cerr << "Failed to write to socket" << std::endl; +bool Server::write(Socket client, const int1* message, halni messageSize) { + try { + std::size_t bytesWritten = asio::write(*(ServerContext::Socket*) client, asio::buffer(message, messageSize)); + return bytesWritten == messageSize; + } catch (const std::exception& e) { + std::cerr << "Error during write: " << e.what() << "\n"; + return false; } } @@ -91,37 +82,35 @@ void Server::write(Socket client, const char* message) { Client::Client() { mContext = new ClientContext(); } -Client::~Client() { - delete mContext; - assert(0); +Client::~Client() { delete mContext; } + +bool Client::connect(const char* IP, ualni PORT) { + try { + asio::ip::tcp::endpoint endpoint(asio::ip::address::from_string(IP), PORT); + mContext->socket.connect(endpoint); + return true; + } catch (const std::exception& e) { + std::cerr << "Error during read: " << e.what() << "\n"; + return false; + } } -void Client::connect(const char* IP, ualni PORT) { - asio::ip::tcp::endpoint endpoint(asio::ip::address::from_string(IP), PORT); - mContext->socket.connect(endpoint); +bool Client::read(int1* buff, halni size) { + try { + std::size_t bytesRead = asio::read(mContext->socket, asio::buffer(buff, size)); + return bytesRead == size; + } catch (const std::exception& e) { + std::cerr << "Error during read: " << e.what() << "\n"; + return false; + } } -char* Client::read() { - short messageSize; - if (asio::read(mContext->socket, asio::buffer(&messageSize, 2)) == -1) { - std::cerr << "Failed to read from socket" << std::endl; - return nullptr; - } - auto message = new char[messageSize + 1]; - message[messageSize] = '\0'; - if (asio::read(mContext->socket, asio::buffer(message, messageSize)) == -1) { - std::cerr << "Failed to read from socket" << std::endl; - memcpy(message, "Cant wanna say something but i cant read", 100); - } - return message; -} - -void Client::write(const char* message) { - auto messageSize = (short) strlen(message); - if (asio::write(mContext->socket, asio::buffer(&messageSize, 2)) == -1) { - std::cerr << "Failed to write to socket" << std::endl; - } - if (asio::write(mContext->socket, asio::buffer(message, messageSize)) == -1) { - std::cerr << "Failed to write to socket" << std::endl; +bool Client::write(const int1* message, halni messageSize) { + try { + std::size_t bytesWritten = asio::write(mContext->socket, asio::buffer(message, messageSize)); + return bytesWritten == messageSize; + } catch (const std::exception& e) { + std::cerr << "Error during write: " << e.what() << "\n"; + return false; } } diff --git a/Connection/private/bindings/Network.hpp b/Connection/public/Network.hpp similarity index 58% rename from Connection/private/bindings/Network.hpp rename to Connection/public/Network.hpp index 9258c49..604e0b2 100644 --- a/Connection/private/bindings/Network.hpp +++ b/Connection/public/Network.hpp @@ -18,8 +18,8 @@ namespace tp { void start(ualni port); Socket accept(); - static int1* read(Socket client); - static void write(Socket client, const int1* message); + bool read(Socket client, int1* message, halni size); + bool write(Socket client, const int1* message, halni size); }; class Client { @@ -29,8 +29,8 @@ namespace tp { Client(); ~Client(); - void connect(const int1* IP, ualni PORT); - int1* read(); - void write(const int1* message); + bool connect(const int1* IP, ualni PORT); + bool read(int1* buff, halni size); + bool write(const int1* message, halni size); }; } \ No newline at end of file diff --git a/Externals/imgui b/Externals/imgui index 4144d7d..1109de3 160000 --- a/Externals/imgui +++ b/Externals/imgui @@ -1 +1 @@ -Subproject commit 4144d7d772a800f08693b8b13f7c81f1f22a73c4 +Subproject commit 1109de38277fd2d14d4dca4c1cb8d4a2c4ff0f95 diff --git a/Graphics/private/bindings/Canvas.cpp b/Graphics/private/bindings/Canvas.cpp index 0320a2f..9a56e61 100644 --- a/Graphics/private/bindings/Canvas.cpp +++ b/Graphics/private/bindings/Canvas.cpp @@ -24,25 +24,21 @@ Graphics::Canvas::Canvas() { mContext = new Context(); } Graphics::Canvas::~Canvas() { delete mContext; } -void Graphics::Canvas::init() { - mContext->vg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); - +void Graphics::Canvas::init() { + mContext->vg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); + if (nvgCreateFont(mContext->vg, "default", "Font.ttf") == -1) { - // TODO + // TODO } } -void Graphics::Canvas::deinit() { - nvgDeleteGL3(mContext->vg); -} +void Graphics::Canvas::deinit() { nvgDeleteGL3(mContext->vg); } -RectF Graphics::Canvas::getAvaliableArea() { - return { (halnf) 0, (halnf) 0, mWidth, mHeight }; -} +RectF Graphics::Canvas::getAvaliableArea() { return { (halnf) 0, (halnf) 0, mWidth, mHeight }; } void Graphics::Canvas::rect(const RectF& rec, const RGBA& col, halnf round) { nvgBeginPath(mContext->vg); - + if (round == 0) { nvgRect(mContext->vg, rec.x, rec.y, rec.z, rec.w); } else { @@ -53,49 +49,69 @@ void Graphics::Canvas::rect(const RectF& rec, const RGBA& col, halnf round) { nvgFill(mContext->vg); } -void Graphics::Canvas::text(const String& string, const RectF& aRec, halnf size, Align align, halnf marging, const RGBA& col) { +void Graphics::Canvas::pushClamp(const RectF& rec) { + RectF intersection = rec; + if (mScissors.size()) { + mScissors.last().calcIntersection(rec, intersection); + } + nvgScissor(mContext->vg, intersection.x, intersection.y, intersection.z, intersection.w); + mScissors.append(rec); +} + +void Graphics::Canvas::popClamp() { + DEBUG_ASSERT(mScissors.size()); + mIsClamping = false; + mScissors.pop(); + if (mScissors.size()) { + const auto& rec = mScissors.last(); + nvgScissor(mContext->vg, rec.x, rec.y, rec.z, rec.w); + } else { + nvgResetScissor(mContext->vg); + } +} + +void Graphics::Canvas::text( + const String& string, const RectF& aRec, halnf size, Align align, halnf marging, const RGBA& col +) { RectF rec = { aRec.x + marging, aRec.y + marging, aRec.z - marging * 2, aRec.w - marging * 2 }; - nvgScissor(mContext->vg, rec.x, rec.y, rec.z, rec.w); + pushClamp(rec); nvgFontSize(mContext->vg, size); nvgFontFace(mContext->vg, "default"); - nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a } ); + nvgFillColor(mContext->vg, { col.r, col.g, col.b, col.a }); float centerX = rec.x; float centerY = rec.y; int alignNVG = 0; - if (((int1*)&align)[1] == 0x00) { // center x + if (((int1*) &align)[1] == 0x00) { // center x alignNVG |= NVG_ALIGN_CENTER; centerX += rec.z * 0.5f; - } else if (((int1*)&align)[1] == 0x01) { // left x + } else if (((int1*) &align)[1] == 0x01) { // left x alignNVG |= NVG_ALIGN_LEFT; - } else if (((int1*)&align)[1] == 0x02) { // right x + } else if (((int1*) &align)[1] == 0x02) { // right x alignNVG |= NVG_ALIGN_RIGHT; centerX += rec.z; } - if (((int1*)&align)[0] == 0x00) { // center y + if (((int1*) &align)[0] == 0x00) { // center y alignNVG |= NVG_ALIGN_MIDDLE; centerY += rec.w * 0.5f; - } else if (((int1*)&align)[0] == 0x01) { // top y + } else if (((int1*) &align)[0] == 0x01) { // top y alignNVG |= NVG_ALIGN_TOP; centerY += rec.w; - } else if (((int1*)&align)[0] == 0x02) { // bottom y + } else if (((int1*) &align)[0] == 0x02) { // bottom y alignNVG |= NVG_ALIGN_BOTTOM; } nvgTextAlign(mContext->vg, alignNVG); nvgText(mContext->vg, centerX, centerY, string.read(), nullptr); - nvgResetScissor(mContext->vg); + + popClamp(); } -void Graphics::Canvas::proc() { - nvgBeginFrame(mContext->vg, mWidth, mHeight, 1.0); -} +void Graphics::Canvas::proc() { nvgBeginFrame(mContext->vg, mWidth, mHeight, 1.0); } -void Graphics::Canvas::draw() { - nvgEndFrame(mContext->vg); -} +void Graphics::Canvas::draw() { nvgEndFrame(mContext->vg); } diff --git a/Graphics/private/bindings/DebugGUI.cpp b/Graphics/private/bindings/DebugGUI.cpp index 2435a84..376d79b 100644 --- a/Graphics/private/bindings/DebugGUI.cpp +++ b/Graphics/private/bindings/DebugGUI.cpp @@ -4,9 +4,9 @@ // -------- Debug UI -------- // #include -#include #include #include +#include namespace tp { class Graphics::GUI::Context { @@ -33,6 +33,9 @@ void Graphics::GUI::init(Window* window) { // ImGui_ImplGlfw_GetBackendData(); ImGui_ImplOpenGL3_Init("#version 330"); + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->AddFontFromFileTTF("Font.ttf", 20.f); } void Graphics::GUI::deinit() { diff --git a/Graphics/private/bindings/Window.cpp b/Graphics/private/bindings/Window.cpp index 2f23ca7..5fbd168 100644 --- a/Graphics/private/bindings/Window.cpp +++ b/Graphics/private/bindings/Window.cpp @@ -47,8 +47,7 @@ void Graphics::GL::deinit() { glDeleteVertexArrays(1, &mContext->vao); } -void Graphics::GL::proc() { -} +void Graphics::GL::proc() {} void Graphics::GL::draw() { glClearColor(0.f, 0.f, 0.f, 0.f); @@ -102,7 +101,7 @@ Window::Window(int width, int height, const char* title) { } mContext->inputManager.init(mContext->window); - + mGraphics.init(this); mEvents.mContext = mContext; @@ -149,7 +148,7 @@ bool Window::shouldClose() const { return glfwWindowShouldClose(mContext->window void Window::processEvents() { mContext->inputManager.isEvent = false; glfwPollEvents(); - + int w, h; glfwGetWindowSize(mContext->window, &w, &h); @@ -176,22 +175,14 @@ auto Window::getContext() -> Context* { return mContext; } Graphics::Canvas& Window::getCanvas() { return mGraphics.mCanvas; } const Window::Events& Window::getEvents() { return mEvents; } -const Vec2F& Window::Events::getPos() const { - return mContext->inputManager.getPos(); -} +const Vec2F& Window::Events::getPos() const { return mContext->inputManager.getPos(); } -bool Window::Events::isPressed() const { - return mContext->inputManager.isPressed(); -} +bool Window::Events::isPressed() const { return mContext->inputManager.isPressed(); } -bool Window::Events::isDown() const { - return mContext->inputManager.isDown(); -} +bool Window::Events::isReleased() const { return mContext->inputManager.isReleased(); } -halnf Window::Events::getScrollY() const { - return mContext->inputManager.getScrollY(); -} +bool Window::Events::isDown() const { return mContext->inputManager.isDown(); } -bool Window::Events::isEvent() const { - return mContext->inputManager.isEvents(); -} +halnf Window::Events::getScrollY() const { return mContext->inputManager.getScrollY(); } + +bool Window::Events::isEvent() const { return mContext->inputManager.isEvents(); } diff --git a/Graphics/private/bindings/WindowContext.hpp b/Graphics/private/bindings/WindowContext.hpp index 77623ae..9aa780d 100644 --- a/Graphics/private/bindings/WindowContext.hpp +++ b/Graphics/private/bindings/WindowContext.hpp @@ -11,8 +11,8 @@ namespace tp { class InputManager { public: InputManager() = default; - - void init(GLFWwindow* aWindow) { + + void init(GLFWwindow* aWindow) { window = aWindow; // Set up key and mouse button callbacks @@ -24,25 +24,17 @@ namespace tp { const Vec2F& getPos() const { return mousePos; } - bool isKeyPressed(int key) const { - return keyStates[key] == GLFW_PRESS; - } + bool isKeyPressed(int key) const { return keyStates[key] == GLFW_PRESS; } - bool isKeyDown(int key) const { - return keyStates[key] == GLFW_REPEAT || keyStates[key] == GLFW_PRESS; - } + bool isKeyDown(int key) const { return keyStates[key] == GLFW_REPEAT || keyStates[key] == GLFW_PRESS; } - bool isPressed() const { - return mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] == GLFW_PRESS; - } + bool isPressed() const { return mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] == GLFW_PRESS; } - bool isDown() const { - return mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] == GLFW_REPEAT || mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] == GLFW_PRESS; - } + bool isReleased() const { return mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] == GLFW_RELEASE; } - bool isEvents() const { - return isEvent; - } + bool isDown() const { return mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] == GLFW_REPEAT || mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] == GLFW_PRESS; } + + bool isEvents() const { return isEvent; } double getScrollY() const { return scrollY; } @@ -105,7 +97,7 @@ namespace tp { bool isEvent = false; double scrollX = 0.0; - double scrollY = 0.0; + double scrollY = 0.0; GLFWwindow* window; Vec2F mousePos; Vec2F mousePosPrev; diff --git a/Graphics/public/Graphics.hpp b/Graphics/public/Graphics.hpp index 18d3f16..ff7bea3 100644 --- a/Graphics/public/Graphics.hpp +++ b/Graphics/public/Graphics.hpp @@ -69,19 +69,21 @@ namespace tp { void draw(); public: - enum Align : int2 { - CC = 0x0000, - CT = 0x0001, - CB = 0x0002, - LC = 0x0100, - LT = 0x0101, - LB = 0x0102, - RC = 0x0200, - RT = 0x0201, + enum Align : int2 { + CC = 0x0000, + CT = 0x0001, + CB = 0x0002, + LC = 0x0100, + LT = 0x0101, + LB = 0x0102, + RC = 0x0200, + RT = 0x0201, RB = 0x0202, }; RectF getAvaliableArea(); + void pushClamp(const RectF& rec); + void popClamp(); void rect(const RectF& rec, const RGBA& col, halnf round = 0); void text(const String&, const RectF&, halnf size, Align, halnf marging, const RGBA&); @@ -90,6 +92,8 @@ namespace tp { private: halnf mWidth = 600; halnf mHeight = 600; + Buffer mScissors; + bool mIsClamping = false; }; private: diff --git a/Graphics/public/Window.hpp b/Graphics/public/Window.hpp index 477c95f..eddd121 100644 --- a/Graphics/public/Window.hpp +++ b/Graphics/public/Window.hpp @@ -21,9 +21,10 @@ namespace tp { struct Events { Vec2F mPointer; - + const Vec2F& getPos() const; bool isPressed() const; + bool isReleased() const; bool isDown() const; halnf getScrollY() const; bool isEvent() const; diff --git a/LibraryViewer/CMakeLists.txt b/LibraryViewer/CMakeLists.txt index b5c64c4..1bb13fe 100644 --- a/LibraryViewer/CMakeLists.txt +++ b/LibraryViewer/CMakeLists.txt @@ -2,7 +2,7 @@ project(LibraryViewer) ### ---------------------- Externals --------------------- ### set(BINDINGS_INCLUDE ../Externals/glfw/include ${GLEW_INCLUDE_DIR} ${PORTAUDIO_INCLUDE_DIR}) -set(BINDINGS_LIBS glfw Imgui Nanovg ${GLEW_LIB} ${ALSA_LIBRARY} ${PORTAUDIO_LIB}) +set(BINDINGS_LIBS glfw ${GLEW_LIB} ${ALSA_LIBRARY} ${PORTAUDIO_LIB}) ### ---------------------- Static Library --------------------- ### file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp") @@ -11,7 +11,7 @@ file(GLOB HEADERS "./public/*.hpp" "./public/*/*.hpp" "./applications/*.hpp") add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ${BINDINGS_INCLUDE} ./ext/) -target_link_libraries(${PROJECT_NAME} PUBLIC Graphics Connection) +target_link_libraries(${PROJECT_NAME} PUBLIC Graphics Connection Widgets) target_link_libraries(${PROJECT_NAME} PUBLIC ${BINDINGS_LIBS}) file(COPY "library" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") diff --git a/LibraryViewer/public/GUI.hpp b/LibraryViewer/public/GUI.hpp index 7da83da..ee825dc 100644 --- a/LibraryViewer/public/GUI.hpp +++ b/LibraryViewer/public/GUI.hpp @@ -2,17 +2,12 @@ #include "Library.hpp" #include "Player.hpp" -#include "Rect.hpp" -#include "Animations.hpp" -#include "imgui.h" +#include "Widgets.hpp" -template -concept DrawableConcept = true; - -template requires(DrawableConcept) +template class ResizerWidget { public: - ResizerWidget() {} + ResizerWidget() = default; void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& area) { if (!areaParent.isOverlap(area)) { @@ -23,7 +18,7 @@ public: hover = area.isInside(events.getPos()); if (events.isPressed() && hover) { - resizing = true; + resizing = true; } else if (!events.isDown()) { resizing = false; } @@ -43,11 +38,11 @@ public: } } - void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { + void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { if (!areaParent.isOverlap(area)) return; - + if (hover || resizing) { - canvas.rect(area, { 0.23f, 0.23f, 0.23f, 1.f}, 0.f); + canvas.rect(area, { 0.23f, 0.23f, 0.23f, 1.f }, 0.f); } else { canvas.rect(area, { 0.04f, 0.04f, 0.04f, 1.f }, 0.f); } @@ -61,7 +56,8 @@ public: tp::halnf value = 0; }; -template requires(DrawableConcept) +template + requires(DrawableConcept) class TrackInfoWidget { struct SortType { tp::String text; @@ -70,31 +66,36 @@ class TrackInfoWidget { }; public: - TrackInfoWidget(const Track* track = nullptr) : mTrack(track) { - items.append( { "Date Added" } ); - items.append( { "Date Last Played" } ); + TrackInfoWidget(const Track* track = nullptr) : + mTrack(track) { + items.append({ "Date Added" }); + items.append({ "Date Last Played" }); } void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& area) { if (!mTrack) return; if (!areaParent.isOverlap(area)) return; - } - void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { + void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { if (!areaParent.isOverlap(area)) return; - - ImGui::SetNextWindowPos( { area.x, area.y } ); - ImGui::SetNextWindowSize( { area.z, area.w } ); + + ImGui::SetNextWindowPos({ area.x, area.y }); + ImGui::SetNextWindowSize({ area.z, area.w }); RenderUI(); - canvas.rect(area, { 0.13f, 0.13f, 0.13f, 1.f}, 4.f); + canvas.rect(area, { 0.13f, 0.13f, 0.13f, 1.f }, 4.f); if (!mTrack) return; } void RenderUI() { - ImGui::Begin("InfoWindow", 0, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoResize); + ImGui::Begin( + "InfoWindow", + 0, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_NoResize + ); if (mTrack) { @@ -146,20 +147,20 @@ public: if (ImGui::Checkbox("Loved only", &filterLoved)) { isSongFilterChanged |= true; } - + if (ImGui::SliderInt("Existing only", &filterExisting, 0, 3)) { isSongFilterChanged |= true; } isSongFilterChanged |= songFilter.Draw("Song Filter"); - + sortFilter.Draw("Sorting Type"); for (int i = 0; i < items.size(); ++i) { if (!sortFilter.PassFilter(items[i].text.read())) continue; ImGui::PushID(i); - + if (ImGui::Button("Inc")) { items[i].inc = true; } @@ -189,8 +190,8 @@ public: int filterExisting = 0; // all existing no-existing }; - -template requires(DrawableConcept) +template + requires(DrawableConcept) class ScrollBarWidget { public: ScrollBarWidget() = default; @@ -214,17 +215,17 @@ public: scrollInertia = -scrollInertia + offset; } } - + if (tp::abs(scrollInertia) > 0.1f) { auto offset = scrollInertia * mScrollFactor; mPositionFraction += offset; mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction); scrollInertia *= 0.f; - return; + return; } if (events.isPressed() && area.isInside(events.getPos())) { - mIsScrolling = true; + mIsScrolling = true; } else if (!events.isDown()) { mIsScrolling = false; } @@ -238,10 +239,10 @@ public: mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction); } - void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { + void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { if (mSizeFraction > 1.f) return; if (!areaParent.isOverlap(area)) return; - + auto minSize = 10.f; tp::RGBA col = tp::RGBA{ 0.25f, 0.25f, 0.25f, 1.f }; @@ -250,10 +251,10 @@ public: minSize = 20.f; } - canvas.rect(area, { 0.15f, 0.15f, 0.15f, 1.f }, 4.f); + canvas.rect(area, { 0.15f, 0.15f, 0.15f, 1.f }, 4.f); auto sliderSize = tp::clamp(area.w * mSizeFraction, minSize, area.w); auto diffSize = sliderSize - area.w * mSizeFraction; - canvas.rect({ area.x, area.y + (area.w - diffSize) * mPositionFraction, area.z, sliderSize }, col, 4.f); + canvas.rect({ area.x, area.y + (area.w - diffSize) * mPositionFraction, area.z, sliderSize }, col, 4.f); } public: @@ -264,11 +265,12 @@ public: tp::halnf mPositionFraction = 0.f; }; - -template requires(DrawableConcept) +template + requires(DrawableConcept) class TrackWidget { public: - TrackWidget(const Track* track = nullptr) : mTrack(track) { + TrackWidget(const Track* track = nullptr) : + mTrack(track) { col.mColor.setAnimTime(0); col.mColor.setNoTransition({ 0.15f, 0.15f, 0.15f, 0.f }); }; @@ -277,31 +279,33 @@ public: if (!mTrack) return; if (!areaParent.isOverlap(area)) return; if (area.isInside(events.getPos())) { - col.set( { 0.15f, 0.15f, 0.15f, 1.f } ); + col.set({ 0.15f, 0.15f, 0.15f, 1.f }); insideDebug = true; } else { - col.set( { 0.15f, 0.15f, 0.15f, 0.f } ); + col.set({ 0.15f, 0.15f, 0.15f, 0.f }); insideDebug = false; } } - void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { + void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { if (!mTrack) return; if (!areaParent.isOverlap(area)) return; - canvas.rect(area, col.get(), 4.f); + canvas.rect(area, col.get(), 4.f); const tp::RectF imageArea = { area.x + marging, area.y + marging, area.w - marging * 2, area.w - marging * 2 }; - canvas.rect(imageArea, { 0.25f, 0.25f, 0.25f, 1.f }, 4.f); + canvas.rect(imageArea, { 0.25f, 0.25f, 0.25f, 1.f }, 4.f); - const tp::RectF textArea = { area.x + area.w + marging, area.y + marging, area.z - area.w - marging * 2, area.w - marging * 2 }; - // canvas.rect(textArea, { 0.25f, 0.25f, 0.25f, 1.f }, 4.f); + const tp::RectF textArea = { + area.x + area.w + marging, area.y + marging, area.z - area.w - marging * 2, area.w - marging * 2 + }; + // canvas.rect(textArea, { 0.25f, 0.25f, 0.25f, 1.f }, 4.f); const tp::RectF textAreaName = { textArea.x, textArea.y, textArea.z, textArea.w * 0.5f }; const tp::RectF textAreaAuthor = { textArea.x, textArea.y + textArea.w * 0.5f, textArea.z, textArea.w * 0.5f }; - - canvas.text(mTrack->mName.read(), textAreaName, 15.f, Canvas::LC, 4.f, { 0.9f, 0.9f, 0.9f, 1.f} ); - canvas.text(mTrack->mArtist.read(), textAreaAuthor, 12.f, Canvas::LC, 4.f, { 0.8f, 0.8f, 0.8f, 1.f} ); + + canvas.text(mTrack->mName.read(), textAreaName, 15.f, Canvas::LC, 4.f, { 0.9f, 0.9f, 0.9f, 1.f }); + canvas.text(mTrack->mArtist.read(), textAreaAuthor, 12.f, Canvas::LC, 4.f, { 0.8f, 0.8f, 0.8f, 1.f }); } public: @@ -311,10 +315,13 @@ public: const Track* mTrack; }; -template requires(DrawableConcept) +template + requires(DrawableConcept) class LibraryWidget { public: - LibraryWidget(Library* lib, Player* player) : mLibrary(lib), mPlayer(player) { + LibraryWidget(Library* lib, Player* player) : + mLibrary(lib), + mPlayer(player) { for (auto track : mLibrary->mTraks) { mTraks.append(TrackWidget(&track.data())); } @@ -333,14 +340,16 @@ public: areaCP = { aArea.x, aArea.y + aArea.w - controllPanelSize, aArea.z, controllPanelSize }; area = { aArea.x, aArea.y, mResizeWidget.value, aArea.w - controllPanelSize }; - areaInfo = { mResizeWidget.value + resizeSize, aArea.y, aArea.z - mResizeWidget.value - resizeSize, aArea.w - controllPanelSize } ; + areaInfo = { + mResizeWidget.value + resizeSize, aArea.y, aArea.z - mResizeWidget.value - resizeSize, aArea.w - controllPanelSize + }; areaResize = { mResizeWidget.value, aArea.y, resizeSize, areaInfo.w }; mScroll.mSizeFraction = (area.w - 10) / (mTraksFiltered.size() * trackSize); mScroll.mScrollFactor = 1.f / mTraksFiltered.size(); - mScroll.proc(events, area, { area.x + area.z - scrollSize - 3, area.y + 10, scrollSize - 3.f, area.w - 20 } ); - mResizeWidget.proc(events, area, areaResize ); + mScroll.proc(events, area, { area.x + area.z - scrollSize - 3, area.y + 10, scrollSize - 3.f, area.w - 20 }); + mResizeWidget.proc(events, area, areaResize); if (area.isInside(events.getPos())) { @@ -348,7 +357,8 @@ public: auto idx = 0; for (auto track : mTraksFiltered) { - auto trackArea = tp::RectF{ area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 }; + auto trackArea = + tp::RectF{ area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 }; track->proc(events, area, trackArea); @@ -364,32 +374,33 @@ public: debugPos = events.getPos(); } - void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& aArea) { + void draw(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& aArea) { + + canvas.rect(aArea, { 0.1f, 0.1f, 0.1f, 1.f }); - canvas.rect(aArea, { 0.1f, 0.1f, 0.1f, 1.f }); - tp::halnf offset = mScroll.mPositionFraction * mTraksFiltered.size() * trackSize; auto idx = 0; for (auto track : mTraksFiltered) { - auto const trackArea = tp::RectF{ area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 }; + auto const trackArea = + tp::RectF{ area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 }; if (track->mTrack == mCurrentTrack.mTrack) canvas.rect(trackArea, { 0.2f, 0.2f, 0.2f, 0.5f }, 5); - track->draw(canvas, area, trackArea); + track->draw(canvas, area, trackArea); idx++; } - mScroll.draw(canvas, area, { area.x + area.z - scrollSize - 3, area.y + 10, scrollSize - 3.f, area.w - 20 } ); - - // canvas.rect( areaInfo, { 0.04f, 0.04f, 0.04f, 1.f }); - mCurrentTrackInfo.draw(canvas, aArea, areaInfo ); + mScroll.draw(canvas, area, { area.x + area.z - scrollSize - 3, area.y + 10, scrollSize - 3.f, area.w - 20 }); - mResizeWidget.draw(canvas, aArea, areaResize ); + // canvas.rect( areaInfo, { 0.04f, 0.04f, 0.04f, 1.f }); + mCurrentTrackInfo.draw(canvas, aArea, areaInfo); + + mResizeWidget.draw(canvas, aArea, areaResize); drawCP(canvas, aArea, areaCP); } void drawCP(Canvas& canvas, const tp::RectF& areaParent, const tp::RectF& area) { - canvas.rect( area, { 0.04f, 0.04f, 0.04f, 1.f }); + canvas.rect(area, { 0.04f, 0.04f, 0.04f, 1.f }); mCurrentTrack.draw(canvas, area, area); } @@ -416,7 +427,7 @@ public: break; } - mTraksFiltered.append( &track.data() ); + mTraksFiltered.append(&track.data()); } mCurrentTrackInfo.isSongFilterChanged = false; diff --git a/Math/public/Rect.hpp b/Math/public/Rect.hpp index c1e5c72..8d90b91 100644 --- a/Math/public/Rect.hpp +++ b/Math/public/Rect.hpp @@ -76,13 +76,13 @@ namespace tp { return ret; } - void calcIntersection(Rect& in, Rect& out) const { + void calcIntersection(const Rect& in, Rect& out) const { if (isOverlap(in)) { out = *this; for (char i = 0; i < 2; i++) { - clamp(out.pos[i], in.pos[i], in.pos[i] + in.size[i]); + out.pos[i] = tp::clamp(out.pos[i], in.pos[i], in.pos[i] + in.size[i]); Type p2 = pos[i] + size[i]; - clamp(p2, in.pos[i], in.pos[i] + in.size[i]); + p2 = tp::clamp(p2, in.pos[i], in.pos[i] + in.size[i]); out.size[i] = p2 - out.pos[i]; } } else { diff --git a/Widgets/CMakeLists.txt b/Widgets/CMakeLists.txt new file mode 100644 index 0000000..af81eb1 --- /dev/null +++ b/Widgets/CMakeLists.txt @@ -0,0 +1,25 @@ +project(Widgets) + +### ---------------------- Static Library --------------------- ### +file(GLOB SOURCES "./private/*.cpp" "./private/*/*.cpp") +file(GLOB HEADERS "./public/*.hpp") + +add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) +target_include_directories(${PROJECT_NAME} PUBLIC ./public/) +target_link_libraries(${PROJECT_NAME} PUBLIC Math Strings) + +### -------------------------- Tests -------------------------- ### +enable_testing() +file(GLOB TEST_SOURCES "./tests/*.cpp") +add_executable(${PROJECT_NAME}Tests ${TEST_SOURCES}) +target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME} Utils) +add_test(NAME ${PROJECT_NAME}Tests COMMAND ${PROJECT_NAME}Tests) + +### -------------------------- Applications -------------------------- ### + +add_executable(ExampleGui examples/Entry.cpp) + +target_link_libraries(ExampleGui ${PROJECT_NAME} Graphics Imgui Nanovg glfw ${GLEW_LIB}) +target_include_directories(ExampleGui PUBLIC ../Externals/glfw/include ${GLEW_INCLUDE_DIR}) + +file(COPY "examples/Font.ttf" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") \ No newline at end of file diff --git a/Widgets/examples/Entry.cpp b/Widgets/examples/Entry.cpp new file mode 100644 index 0000000..820945c --- /dev/null +++ b/Widgets/examples/Entry.cpp @@ -0,0 +1,46 @@ + +#include "ExampleGUI.hpp" +#include "Window.hpp" + +using namespace tp; + +void runApp() { + auto window = tp::Window::createWindow(800, 600, "Window 1"); + + tp::ComplexWidget gui; + + if (window) { + while (!window->shouldClose()) { + window->processEvents(); + + auto area = window->getCanvas().getAvaliableArea(); + + gui.proc(window->getEvents(), {}, { area.x, area.y, area.z, area.w }); + gui.draw(window->getCanvas()); + + tp::sleep(20); + + window->draw(); + } + } + + tp::Window::destroyWindow(window); +} + +int main() { + + tp::ModuleManifest* deps[] = { &tp::gModuleWidgets, nullptr }; + tp::ModuleManifest binModule("Chat", nullptr, nullptr, deps); + + if (!binModule.initialize()) { + return 1; + } + + { + tp::GlobalGUIConfig config; + tp::gGlobalGUIConfig = &config; + runApp(); + } + + binModule.deinitialize(); +} diff --git a/Widgets/examples/ExampleGUI.hpp b/Widgets/examples/ExampleGUI.hpp new file mode 100644 index 0000000..defc63e --- /dev/null +++ b/Widgets/examples/ExampleGUI.hpp @@ -0,0 +1,281 @@ +#pragma once + +#include "Widgets.hpp" + +namespace tp { + + template + class UserWidget : public Widget { + public: + UserWidget() { + this->createConfig("User"); + this->addColor("Base", "Base"); + this->addValue("Size", "FontSize"); + this->addValue("Padding", "Padding"); + this->addColor("ColUser", "Front"); + this->addColor("Accent", "Accent"); + this->addValue("Rounding", "Rounding"); + } + + void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override { + this->mArea = aArea; + this->mArea.w = 30; + this->mVisible = areaParent.isOverlap(aArea); + if (!this->mVisible) return; + mIsHover = aArea.isInside(events.getPos()); + } + + void draw(Canvas& canvas) const override { + if (!this->mVisible) return; + + if (mIsHover) canvas.rect(this->mArea, this->getColor("Accent"), this->getValue("Rounding")); + else canvas.rect(this->mArea, this->getColor("Base"), this->getValue("Rounding")); + + const auto padding = this->getValue("Padding"); + const auto size = this->getValue("Size"); + const auto colUser = this->getColor("ColUser"); + + canvas.text(mUser.read(), this->mArea, size, Canvas::CC, padding, colUser); + } + + public: + String mUser = "UserName"; + bool mIsHover = false; + }; + + template + class MessageWidget : public Widget { + public: + MessageWidget() { + this->createConfig("Message"); + this->addColor("Base", "Base"); + this->addValue("Size", "FontSize"); + this->addValue("SizeUser", "FontSizeDim"); + this->addValue("Padding", "Padding"); + this->addColor("ColUser", "Front"); + this->addColor("Col", "FrontDim"); + this->addValue("Rounding", "Rounding"); + } + + void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override { + this->mArea = aArea; + this->mArea.w = 50; + this->mVisible = areaParent.isOverlap(aArea); + if (!this->mVisible) return; + mIsHover = aArea.isInside(events.getPos()); + } + + void draw(Canvas& canvas) const override { + if (!this->mVisible) return; + + if (mIsHover) canvas.rect(this->mArea, this->getColor("Base"), this->getValue("Rounding")); + + const auto padding = this->getValue("Padding"); + const auto size = this->getValue("Size"); + const auto sizeUser = this->getValue("SizeUser"); + const auto col = this->getColor("Col"); + const auto colUser = this->getColor("ColUser"); + + auto userName = this->mArea; + userName.w = 25; + + auto content = this->mArea; + content.y = userName.y + userName.w; + content.w = this->mArea.w - userName.w; + + canvas.text(mContent.read(), content, size, Canvas::LC, padding, col); + canvas.text(mUser.read(), userName, sizeUser, Canvas::LC, padding, colUser); + } + + public: + String mContent = "Message Content"; + String mUser = "UserName"; + bool mIsHover = false; + }; + + template + class LoginWidget : public Widget { + public: + explicit LoginWidget() { + this->createConfig("Login"); + this->addColor("Back", "Background"); + mPass.mId = "pass"; + mUser.mId = "user"; + mButton.mLabel.mLabel = "Login"; + } + + void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override { + this->mArea = aArea; + mLogged = false; + + const auto xval = aArea.z / 2 - 100; + mUser.proc(events, aArea, { xval, 10, 200, 30 }); + mPass.proc(events, aArea, { xval, 50, 200, 30 }); + mButton.proc(events, aArea, { xval, 90, 200, 30 }); + + if (mButton.mIsReleased) { + mButton.mIsReleased = false; + mLogged = true; + } + } + + void draw(Canvas& canvas) const override { + canvas.rect(this->mArea, this->getColor("Back")); + mButton.draw(canvas); + mUser.draw(canvas); + mPass.draw(canvas); + } + + public: + TextInputWidget mUser; + TextInputWidget mPass; + ButtonWidget mButton; + bool mLogged = false; + }; + + template + class ActiveChatWidget : public Widget { + public: + ActiveChatWidget() { + this->createConfig("ActiveChat"); + this->addColor("Back", "Background"); + this->addValue("Padding", "Padding"); + mSend.mLabel.mLabel = "Send"; + mMessage.mId = "Message"; + } + + void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override { + this->mArea = aArea; + + auto history = this->mArea; + history.w -= 50; + + auto input = this->mArea; + input.y = history.w + 10; + input.w = 40 - this->getValue("Padding"); + input.x += this->getValue("Padding"); + input.z -= this->getValue("Padding"); + + auto inputMessage = input; + inputMessage.z -= 100; + + auto inputSend = input; + inputSend.x = inputMessage.x + inputMessage.z + this->getValue("Padding"); + inputSend.z = 100 - this->getValue("Padding") * 2; + + mSend.proc(events, this->mArea, inputSend); + mMessage.proc(events, this->mArea, inputMessage); + + if (mSend.mIsReleased) { + mSend.mIsReleased = false; + } + + mHistoryView.proc(events, this->mArea, history); + } + + void draw(Canvas& canvas) const override { + canvas.rect(this->mArea, this->getColor("Back")); + mHistoryView.draw(canvas); + mMessage.draw(canvas); + mSend.draw(canvas); + } + + public: + Buffer> mMessages; + ScrollableWindow mHistoryView; + TextInputWidget mMessage; + ButtonWidget mSend; + }; + + template + class ChattingWidget : public Widget { + public: + explicit ChattingWidget() { + this->createConfig("Chatting"); + this->addColor("Back", "Background"); + this->addValue("Padding", "Padding"); + + // todo : fetch code + mUsers.append(UserWidget()); + mUsers.append(UserWidget()); + mUsers.append(UserWidget()); + + mUsers[0].mArea = { 0, 0, 100, 100 }; + mUsers[1].mArea = { 0, 0, 100, 100 }; + mUsers[2].mArea = { 0, 0, 100, 100 }; + + for (auto message : mUsers) { + mSideView.mContents.append(&message.data()); + } + + mActive.mMessages.append(MessageWidget()); + mActive.mMessages.append(MessageWidget()); + mActive.mMessages.append(MessageWidget()); + + mActive.mMessages[0].mArea = { 0, 0, 100, 100 }; + mActive.mMessages[1].mArea = { 0, 0, 100, 100 }; + mActive.mMessages[2].mArea = { 0, 0, 100, 100 }; + + for (auto message : mActive.mMessages) { + mActive.mHistoryView.mContents.append(&message.data()); + } + } + + void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override { + this->mArea = aArea; + + mSplitView.proc(events, aArea, aArea); + + const auto padding = this->getValue("Padding"); + mSideView.proc(events, this->mArea, mSplitView.getSecond()); + + mActive.proc(events, aArea, mSplitView.getFirst()); + } + + void draw(Canvas& canvas) const override { + canvas.rect(this->mArea, this->getColor("Back")); + mSplitView.draw(canvas); + mSideView.draw(canvas); + mActive.draw(canvas); + } + + public: + Buffer> mUsers; + ScrollableWindow mSideView; + ActiveChatWidget mActive; + SplitView mSplitView; + }; + + template + class ComplexWidget : public Widget { + public: + ComplexWidget() { + this->createConfig("chat"); + this->addColor("Back", "Background"); + } + + void proc(const Events& events, const RectF& areaParent, const RectF& aArea) override { + this->mArea = aArea; + if (mLogged) { + mChatting.proc(events, aArea, aArea); + } else { + mLogin.proc(events, aArea, aArea); + mLogged = mLogin.mLogged; + if (mLogged) { + mChatting.proc(events, aArea, aArea); + } + } + } + + void draw(Canvas& canvas) const override { + canvas.rect(this->mArea, this->getColor("Back")); + if (mLogged) mChatting.draw(canvas); + else mLogin.draw(canvas); + } + + private: + bool mLogged = false; + LoginWidget mLogin; + ChattingWidget mChatting; + }; +} \ No newline at end of file diff --git a/Widgets/examples/Font.ttf b/Widgets/examples/Font.ttf new file mode 100644 index 0000000..8a63054 Binary files /dev/null and b/Widgets/examples/Font.ttf differ diff --git a/Graphics/private/Animations.cpp b/Widgets/private/Animations.cpp similarity index 100% rename from Graphics/private/Animations.cpp rename to Widgets/private/Animations.cpp diff --git a/Widgets/private/WidgetConfig.cpp b/Widgets/private/WidgetConfig.cpp new file mode 100644 index 0000000..be45cf5 --- /dev/null +++ b/Widgets/private/WidgetConfig.cpp @@ -0,0 +1,9 @@ + +#include "WidgetBase.hpp" + +namespace tp { + GlobalGUIConfig* gGlobalGUIConfig = nullptr; + + ModuleManifest* deps[] = { &gModuleMath, &gModuleStrings, nullptr }; + ModuleManifest gModuleWidgets = ModuleManifest("Widgets", nullptr, nullptr, deps); +} \ No newline at end of file diff --git a/Graphics/public/Animations.hpp b/Widgets/public/Animations.hpp similarity index 92% rename from Graphics/public/Animations.hpp rename to Widgets/public/Animations.hpp index f729471..1e33f40 100644 --- a/Graphics/public/Animations.hpp +++ b/Widgets/public/Animations.hpp @@ -1,6 +1,8 @@ #pragma once -#include "GraphicsCommon.hpp" +#include "Color.hpp" +#include "Rect.hpp" +#include "Timing.hpp" namespace tp { @@ -32,7 +34,7 @@ namespace tp { class AnimRect : Rect { public: - AnimRect() { + AnimRect() { setAnimTime(450); setNoTransition({ 0, 0, 0, 0 }); } diff --git a/Widgets/public/ButtonWidget.hpp b/Widgets/public/ButtonWidget.hpp new file mode 100644 index 0000000..cc5ac72 --- /dev/null +++ b/Widgets/public/ButtonWidget.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "LabelWidget.hpp" + +namespace tp { + + template + class ButtonWidget : public Widget { + public: + ButtonWidget() { + this->createConfig("Button"); + this->addColor("Pressed", "Action"); + this->addColor("Hovered", "Interaction"); + this->addColor("Default", "Accent"); + this->addValue("Rounding", "Rounding"); + } + + void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { + this->mArea = aArea; + mIsHover = false; + + if (!areaParent.isOverlap(aArea)) { + mIsReleased = false; + mIsPressed = false; + return; + } + + mIsHover = aArea.isInside(events.getPos()); + + if (events.isPressed() && mIsHover) { + mIsPressed = true; + } + + if (mIsPressed && mIsHover && events.isReleased()) { + mIsReleased = true; + mIsPressed = false; + } + + if (!mIsHover) mIsPressed = false; + + mLabel.proc(events, aArea, aArea); + } + + void draw(Canvas& canvas) const override { + if (mIsPressed) { + canvas.rect(this->mArea, this->getColor("Pressed"), this->getValue("Rounding")); + } else if (mIsHover) { + canvas.rect(this->mArea, this->getColor("Hovered"), this->getValue("Rounding")); + } else { + canvas.rect(this->mArea, this->getColor("Default"), this->getValue("Rounding")); + } + mLabel.draw(canvas); + } + + public: + LabelWidget mLabel; + bool mIsHover = false; + bool mIsPressed = false; + bool mIsReleased = false; + }; +} \ No newline at end of file diff --git a/Widgets/public/LabelWidget.hpp b/Widgets/public/LabelWidget.hpp new file mode 100644 index 0000000..ad946ee --- /dev/null +++ b/Widgets/public/LabelWidget.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "WidgetBase.hpp" + +namespace tp { + template + class LabelWidget : public Widget { + public: + LabelWidget() { + this->createConfig("Label"); + this->addValue("Size", "FontSize"); + this->addValue("Padding", "Padding"); + this->addColor("Default", "Front"); + } + + void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { + this->mArea = aArea; + } + + void draw(Canvas& canvas) const override { + canvas.text( + mLabel.read(), + this->mArea, + this->getValue("Size"), + Canvas::CC, + this->getValue("Padding"), + this->getColor("Default") + ); + } + + public: + String mLabel = "Label"; + }; +} \ No newline at end of file diff --git a/Widgets/public/ScrollableWidget.hpp b/Widgets/public/ScrollableWidget.hpp new file mode 100644 index 0000000..8fcd89b --- /dev/null +++ b/Widgets/public/ScrollableWidget.hpp @@ -0,0 +1,218 @@ +#pragma once + +#include "WidgetBase.hpp" + +namespace tp { + + template + class ScrollBarWidget : public Widget { + public: + ScrollBarWidget() { + this->createConfig("ScrollBar"); + this->addColor("Default", "Base"); + this->addColor("Handle", "Accent"); + this->addColor("Hovered", "Interaction"); + this->addColor("Scrolling", "Action"); + this->addValue("Padding", "Padding"); + this->addValue("HandleSize", 20.f); + this->addValue("MinSize", 20.f); + this->addValue("Rounding", "Rounding"); + } + + // takes whole area + void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { + this->mArea = aArea; + + auto area = getHandle(); + mHovered = getHandleHandle().isInside(events.getPos()); + + if (mSizeFraction > 1.f) { + mPositionFraction = 0; + return; + } + + if (!areaParent.isOverlap(area)) { + mIsScrolling = false; + return; + } + + if (events.getScrollY() != 0 && areaParent.isInside(events.getPos())) { + auto offset = events.getScrollY() < 0 ? 1.0f : -1.0f; + if (scrollInertia * offset > 0) { + scrollInertia += offset; + } else { + scrollInertia = -scrollInertia + offset; + } + } + + if (tp::abs(scrollInertia) > 0.1f) { + auto offset = scrollInertia * mScrollFactor; + mPositionFraction += offset; + mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction); + scrollInertia *= 0.f; + return; + } + + if (events.isPressed() && area.isInside(events.getPos())) { + mIsScrolling = true; + } else if (!events.isDown()) { + mIsScrolling = false; + } + + if (mIsScrolling) { + tp::halnf pos = events.getPos().y; + pos = (pos - area.y - mSizeFraction * area.w / 2.f) / area.w; + mPositionFraction = tp::clamp(pos, 0.f, 1.f - mSizeFraction); + } + + mPositionFraction = tp::clamp(mPositionFraction, 0.f, 1.f - mSizeFraction); + } + + void draw(Canvas& canvas) const override { + auto area = getHandle(); + + if (mSizeFraction > 1.f) return; + // if (!areaParent.isOverlap(getHandle())) return; + + tp::RGBA col = this->getColor("Handle"); + + if (mIsScrolling) { + col = this->getColor("Scrolling"); + } else if (mHovered) { + col = this->getColor("Hovered"); + } + + canvas.rect(area, this->getColor("Default"), this->getValue("Rounding")); + + canvas.rect(getHandleHandle(), col, this->getValue("Rounding")); + } + + RectF getHandleHandle() const { + auto minSize = this->getValue("MinSize"); + if (mIsScrolling) { + minSize = this->getValue("MinSize") * 2; + } else if (mHovered) { + minSize = this->getValue("MinSize") * 2; + } + auto area = getHandle(); + auto sliderSize = tp::clamp(area.w * mSizeFraction, minSize, area.w); + auto diffSize = sliderSize - area.w * mSizeFraction; + return { area.x, area.y + (area.w - diffSize) * mPositionFraction, area.z, sliderSize }; + } + + RectF getViewport() const { + if (mSizeFraction > 1.f) { + return this->mArea; + } + return { this->mArea.x, this->mArea.y, this->mArea.z - this->getValue("HandleSize"), this->mArea.w }; + } + + RectF getHandle() const { + return { this->mArea.x + this->mArea.z - this->getValue("HandleSize") + this->getValue("Padding"), + this->mArea.y + this->getValue("Padding"), + this->getValue("HandleSize") - this->getValue("Padding") * 2, + this->mArea.w - this->getValue("Padding") * 2 }; + } + + public: + halnf mScrollFactor = 0.f; + halnf scrollInertia = 0.f; + bool mIsScrolling = false; + halnf mSizeFraction = 1.f; + halnf mPositionFraction = 0.f; + bool mHovered = false; + }; + + template + class ScrollableWindow : public Widget { + public: + ScrollableWindow() { + this->createConfig("ScrollableWindow"); + this->addColor("Default", "Base"); + this->addValue("Padding", "Padding"); + } + + // takes whole area + void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { + this->mArea = aArea; + this->mVisible = areaParent.isOverlap(aArea); + + if (!this->mVisible) return; + + updateContents(); + updateContentSize(); + + const auto padding = this->getValue("Padding"); + + mScroller.mSizeFraction = this->mArea.w / mContentSize; + mScroller.proc(events, this->mArea, this->mArea); + + if (mScroller.mSizeFraction > 1.f) { + setOffset(0); + } else { + setOffset((-mScroller.mPositionFraction) * mContentSize); + } + + for (auto widget : mContents) { + widget->proc( + events, + this->mArea, + { this->mArea.x + padding, widget->mArea.y, mScroller.getViewport().z - padding * 2, widget->mArea.w } + ); + } + } + + void draw(Canvas& canvas) const override { + if (!this->mVisible) return; + mScroller.draw(canvas); + + canvas.pushClamp(this->mArea); + for (auto widget : mContents) { + widget->draw(canvas); + } + canvas.popClamp(); + } + + private: + void updateContents() { + if (mContents.size()) { + const auto padding = this->getValue("Padding"); + const halnf offset = mContents.first()->mArea.y + padding; + + halnf start = 0; + for (auto widget : mContents) { + widget->mArea.y = start; + start += widget->mArea.w + padding; + } + + for (auto widget : mContents) { + widget->mArea.y += offset; + } + } + } + + void updateContentSize() { + mContentSize = 0; + if (mContents.size()) { + const auto padding = this->getValue("Padding"); + mContentSize = mContents.last()->mArea.y - mContents.first()->mArea.y; + mContentSize += mContents.last()->mArea.w; + mContentSize += (mContents.size()) * padding; + } + } + + void setOffset(const halnf offset) { + if (!mContents.size()) return; + const auto padding = this->getValue("Padding"); + auto newOffset = offset - mContents.first()->mArea.y + padding; + for (auto widget : mContents) { + widget->mArea.y += newOffset; + } + } + + public: + halnf mContentSize = 0; + Buffer*> mContents; + ScrollBarWidget mScroller; + }; +} \ No newline at end of file diff --git a/Widgets/public/SplitViewWidget.hpp b/Widgets/public/SplitViewWidget.hpp new file mode 100644 index 0000000..4c75dd1 --- /dev/null +++ b/Widgets/public/SplitViewWidget.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "WidgetBase.hpp" + +namespace tp { + template + class SplitView : public Widget { + public: + SplitView() { + this->createConfig("SplitView"); + this->addColor("Handle", "Accent"); + this->addColor("Hovered", "Interaction"); + this->addColor("Resizing", "Action"); + this->addValue("Min", 200.f); + this->addValue("HandleSize", 7.f); + } + + void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { + this->mArea = aArea; + + if (!areaParent.isOverlap(aArea)) { + mResizeInProcess = false; + return; + } + + mIsHover = getHandle().isInside(events.getPos()); + + if (events.isPressed() && mIsHover) { + mResizeInProcess = true; + } else if (!events.isDown()) { + mResizeInProcess = false; + } + + if (mResizeInProcess) { + halnf pos = events.getPos().x; + auto diff = pos - (this->mArea.x + mFactor * this->mArea.z); + mFactor += diff / this->mArea.z; + } + + mFactor = tp::clamp(mFactor, this->getValue("Min") / this->mArea.z, 1 - this->getValue("Min") / this->mArea.z); + + if (this->getValue("Min") * 2.f > this->mArea.z) { + mFactor = 0.5f; + } + } + + // takes whole area + void draw(Canvas& canvas) const override { + if (mResizeInProcess) canvas.rect(getHandle(), this->getColor("Resizing")); + else if (mIsHover) canvas.rect(getHandle(), this->getColor("Hovered")); + else canvas.rect(getHandle(), this->getColor("Handle")); + } + + RectF getFirst() const { + return { + this->mArea.x, this->mArea.y, mFactor * this->mArea.z - this->getValue("HandleSize") / 2.f, this->mArea.w + }; + } + + RectF getSecond() const { + return { this->mArea.x + mFactor * this->mArea.z + this->getValue("HandleSize") / 2.f, + this->mArea.y, + (1.f - mFactor) * this->mArea.z - this->getValue("HandleSize") / 2.f, + this->mArea.w }; + } + + RectF getHandle() const { + return { this->mArea.x + mFactor * this->mArea.z - this->getValue("HandleSize") / 2.f, + this->mArea.y, + this->getValue("HandleSize"), + this->mArea.w }; + } + + public: + halnf mFactor = 0.7f; + bool mResizeInProcess = false; + bool mIsHover = false; + }; +} \ No newline at end of file diff --git a/Widgets/public/TextInputWidget.hpp b/Widgets/public/TextInputWidget.hpp new file mode 100644 index 0000000..7739827 --- /dev/null +++ b/Widgets/public/TextInputWidget.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "WidgetBase.hpp" + +#include "imgui.h" +#include "imgui_internal.h" + +namespace tp { + template + class TextInputWidget : public Widget { + public: + TextInputWidget() { + this->createConfig("TextInput"); + this->addColor("Accent", "Accent"); + this->addColor("Base", "Base"); + this->addValue("Rounding", "Rounding"); + this->addColor("Hovered", "Interaction"); + this->addValue("Padding", "Padding"); + } + + void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) override { + this->mArea = aArea; + nChanged = false; + + const auto col = this->getColor("Accent"); + const auto colSel = this->getColor("Hovered"); + + ImGui::GetStyle().Colors[ImGuiCol_FrameBg] = { col.r, col.g, col.b, col.a }; + ImGui::GetStyle().Colors[ImGuiCol_TextSelectedBg] = { colSel.r, colSel.g, colSel.b, colSel.a }; + + ImGui::SetNextWindowPos({ this->mArea.x, this->mArea.y }); + ImGui::SetNextWindowSize({ this->mArea.z, this->mArea.w }); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0, 0 }); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, this->getValue("Rounding") * 1.5f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { this->getValue("Padding"), this->getValue("Padding") }); + + // ImGui::PushID((int) alni(this)); + ImGui::Begin( + mId.read(), + 0, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_NoResize + ); + + if (mMultiline) { + if (ImGui::InputTextMultiline("input", mBuff, mMaxBufferSize, { this->mArea.z, this->mArea.w })) { + mValue = mBuff; + nChanged = true; + } + } else { + if (ImGui::InputTextEx("input", mId.read(), mBuff, mMaxBufferSize, { this->mArea.z, this->mArea.w }, 0)) { + mValue = mBuff; + nChanged = true; + } + } + + ImGui::End(); + ImGui::PopStyleVar(3); + } + + void draw(Canvas& canvas) const override { + // canvas.rect(this->mArea, this->getColor("Base")); + } + + enum { mMaxBufferSize = 512 }; + char mBuff[mMaxBufferSize] = ""; + bool nChanged = false; + String mValue; + String mId = "id"; + bool mMultiline = false; + }; +} \ No newline at end of file diff --git a/Widgets/public/WidgetBase.hpp b/Widgets/public/WidgetBase.hpp new file mode 100644 index 0000000..e47cb6e --- /dev/null +++ b/Widgets/public/WidgetBase.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "WidgetConfig.hpp" + +namespace tp { + + template + class Widget { + public: + Widget() = default; + + [[nodiscard]] const RGBA& getColor(const String& name) const { + const auto& node = cfg->values.get(name); + if (node.type == WidgetConfig::Node::REF) { + return globalCfg->configs.get(node.refGroup).values.get(node.refName).color; + } + return cfg->values.get(name).color; + } + + [[nodiscard]] halnf getValue(const String& name) const { + const auto& node = cfg->values.get(name); + if (node.type == WidgetConfig::Node::REF) { + return globalCfg->configs.get(node.refGroup).values.get(node.refName).value; + } + return cfg->values.get(name).value; + } + + void addColor(const String& name, const RGBA& val) { cfg->values.put(name, WidgetConfig::Node(val)); } + void addValue(const String& name, halnf val) { cfg->values.put(name, WidgetConfig::Node(val)); } + + void addColor(const String& name, const String& presetName) { + cfg->values.put(name, WidgetConfig::Node(presetName)); + } + void addValue(const String& name, const String& presetName) { + cfg->values.put(name, WidgetConfig::Node(presetName)); + } + + void createConfig(const String& name) { + globalCfg->configs.put(name, {}); + cfg = &globalCfg->configs.get(name); + } + + virtual void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) { + mVisible = areaParent.isOverlap(aArea); + if (!mVisible) { + return; + } + + this->mArea = aArea; + } + + virtual void draw(Canvas& canvas) const { + if (!mVisible) { + return; + } + } + + public: + GlobalGUIConfig* globalCfg = gGlobalGUIConfig; + WidgetConfig* cfg = nullptr; + RectF mArea; + bool mVisible = false; + }; +} \ No newline at end of file diff --git a/Widgets/public/WidgetConfig.hpp b/Widgets/public/WidgetConfig.hpp new file mode 100644 index 0000000..7e12f2e --- /dev/null +++ b/Widgets/public/WidgetConfig.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "Animations.hpp" +#include "Map.hpp" +#include "Rect.hpp" +#include "Strings.hpp" + +namespace tp { + + extern ModuleManifest gModuleWidgets; + + struct WidgetConfig { + struct Node { + enum Type { NONE, VAL, COL, REF }; + + halnf value = 0.f; + RGBA color = {}; + + Type type = NONE; + + String refGroup; + String refName; + + Node() = default; + + explicit Node(halnf val) { + type = VAL; + value = val; + } + + explicit Node(const RGBA& val) { + type = COL; + color = val; + } + + explicit Node(const String& val) { + refGroup = "Presets"; + refName = val; + type = REF; + } + }; + + Map values; + }; + + struct GlobalGUIConfig { + GlobalGUIConfig() { + configs.put("Presets", {}); + auto& presets = configs.get("Presets"); + + presets.values.put("FontSize", WidgetConfig::Node(15.f)); + presets.values.put("FontSizeDim", WidgetConfig::Node(12.f)); + presets.values.put("Rounding", WidgetConfig::Node(5.f)); + presets.values.put("Padding", WidgetConfig::Node(5.f)); + presets.values.put("HandleSize", WidgetConfig::Node(5.f)); + + presets.values.put("Background", WidgetConfig::Node({ 0.03f, 0.03f, 0.03f, 1.f })); + presets.values.put("Base", WidgetConfig::Node({ 0.07f, 0.07f, 0.07f, 1.f })); + presets.values.put("Accent", WidgetConfig::Node({ 0.13f, 0.13f, 0.13f, 1.f })); + presets.values.put("Interaction", WidgetConfig::Node({ 0.33f, 0.33f, 0.3f, 1.f })); + presets.values.put("Action", WidgetConfig::Node({ 0.44f, 0.44f, 0.4f, 1.f })); + presets.values.put("Front", WidgetConfig::Node({ 1.f, 1.f, 1.f, 1.f })); + presets.values.put("FrontDim", WidgetConfig::Node({ 0.7f, 0.7f, 0.7f, 1.f })); + } + + Map configs; + }; + + extern GlobalGUIConfig* gGlobalGUIConfig; +} \ No newline at end of file diff --git a/Widgets/public/Widgets.hpp b/Widgets/public/Widgets.hpp new file mode 100644 index 0000000..1661f73 --- /dev/null +++ b/Widgets/public/Widgets.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "ButtonWidget.hpp" +#include "LabelWidget.hpp" +#include "ScrollableWidget.hpp" +#include "SplitViewWidget.hpp" +#include "TextInputWidget.hpp" +#include "Animations.hpp" \ No newline at end of file diff --git a/Widgets/tests/Tests.cpp b/Widgets/tests/Tests.cpp new file mode 100644 index 0000000..c272dab --- /dev/null +++ b/Widgets/tests/Tests.cpp @@ -0,0 +1 @@ +int main() {} \ No newline at end of file