From e10a494223f7fc3852dfcda0635838c801271a38 Mon Sep 17 00:00:00 2001 From: IlyaShurupov Date: Sun, 20 Oct 2024 12:01:42 +0300 Subject: [PATCH] Widgets Additions and sketch3d new gui --- .gitmodules | 3 + 3DEditor/private/Editor.cpp | 2 +- 3DEditor/public/EditorWidget.hpp | 2 +- Externals/CMakeLists.txt | 5 +- Externals/implot | 1 + Graphics/private/Canvas.cpp | 192 ++++++++++++++++++ Graphics/private/DebugGUI.cpp | 7 + Graphics/private/Window.cpp | 2 + Graphics/public/Graphics.hpp | 11 +- LibraryViewer/public/GUI.hpp | 8 +- Modules/public/Timing.hpp | 16 ++ Sketch3D/applications/Entry.cpp | 11 +- Sketch3D/private/Sketch3D.cpp | 22 ++ Sketch3D/private/SketchGUI.cpp | 184 +++++++++++++++++ Sketch3D/public/Sketch3D.hpp | 9 +- Sketch3D/public/Sketch3DWidget.hpp | 169 --------------- Sketch3D/public/SketchGUI.hpp | 95 +++++++++ TODO | 11 +- Widgets/CMakeLists.txt | 6 +- Widgets/examples/Example.cpp | 42 ++-- Widgets/private/RootWidget.cpp | 37 ++-- Widgets/private/WidgetApplication.cpp | 56 ++++- Widgets/private/layouts/DockLayout.cpp | 6 +- Widgets/private/layouts/OverlayLayout.cpp | 29 --- Widgets/private/layouts/ScrollableLayout.cpp | 4 +- Widgets/private/layouts/SimpleLayouts.cpp | 32 +++ Widgets/private/managers/DebugManager.cpp | 60 +++--- Widgets/private/managers/LayoutManager.cpp | 4 + Widgets/private/managers/UpdateManager.cpp | 17 +- Widgets/private/widgets/ColorPickerWidget.cpp | 22 ++ Widgets/private/widgets/DockWidget.cpp | 4 + Widgets/private/widgets/ScrollableWidget.cpp | 21 +- Widgets/private/widgets/SimpleWidgets.cpp | 46 ++++- Widgets/public/Layout.hpp | 2 +- Widgets/public/RootWidget.hpp | 2 - Widgets/public/Widget.hpp | 1 + Widgets/public/WidgetApplication.hpp | 35 +--- Widgets/public/layouts/DockLayout.hpp | 2 +- .../{OverlayLayout.hpp => SimpleLayouts.hpp} | 15 ++ Widgets/public/mangers/DebugManager.hpp | 58 +++++- Widgets/public/widgets/ColorPickerWidget.hpp | 18 ++ Widgets/public/widgets/DockWidget.hpp | 2 + Widgets/public/widgets/ScrollableWidget.hpp | 10 + Widgets/public/widgets/SimpleWidgets.hpp | 56 ++++- 44 files changed, 1015 insertions(+), 322 deletions(-) create mode 160000 Externals/implot create mode 100644 Sketch3D/private/SketchGUI.cpp delete mode 100644 Sketch3D/public/Sketch3DWidget.hpp create mode 100644 Sketch3D/public/SketchGUI.hpp delete mode 100644 Widgets/private/layouts/OverlayLayout.cpp create mode 100644 Widgets/private/layouts/SimpleLayouts.cpp create mode 100644 Widgets/private/widgets/ColorPickerWidget.cpp rename Widgets/public/layouts/{OverlayLayout.hpp => SimpleLayouts.hpp} (51%) create mode 100644 Widgets/public/widgets/ColorPickerWidget.hpp diff --git a/.gitmodules b/.gitmodules index 9d1d1de..7bafd8b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "Externals/asio"] path = Externals/asio url = https://github.com/elushaX/asio.git +[submodule "Externals/implot"] + path = Externals/implot + url = https://github.com/epezent/implot.git diff --git a/3DEditor/private/Editor.cpp b/3DEditor/private/Editor.cpp index 26599a0..59facdf 100644 --- a/3DEditor/private/Editor.cpp +++ b/3DEditor/private/Editor.cpp @@ -57,7 +57,7 @@ void Editor::loadDefaults() { } void Editor::setViewportSize(const Vec2F& size) { - if (size.x < 0 || size.y < 0) return; + if (size.x <= 0 || size.y <= 0) return; // TODO remove // mScene.mCamera.rotate(0.01f, 0.0); diff --git a/3DEditor/public/EditorWidget.hpp b/3DEditor/public/EditorWidget.hpp index 00899df..0914937 100644 --- a/3DEditor/public/EditorWidget.hpp +++ b/3DEditor/public/EditorWidget.hpp @@ -17,7 +17,7 @@ namespace tp { ~ViewportWidget() override { mCanvas->deleteImageHandle(mImage); } void process(const EventHandler& events) override { - mEditor->setViewportSize(getArea().size); + mEditor->setViewportSize(getAreaT().size); } void draw(Canvas& canvas) override { diff --git a/Externals/CMakeLists.txt b/Externals/CMakeLists.txt index bca8259..4f40aa3 100644 --- a/Externals/CMakeLists.txt +++ b/Externals/CMakeLists.txt @@ -12,11 +12,14 @@ set(${PROJECT_NAME}_SOURCES imgui/backends/imgui_impl_glfw.cpp imgui/backends/imgui_impl_opengl3.cpp + + implot/implot.cpp + implot/implot_items.cpp ) add_library(${PROJECT_NAME} STATIC ${${PROJECT_NAME}_SOURCES}) include_directories(${PROJECT_NAME} ./glfw/include) -target_include_directories(${PROJECT_NAME} PUBLIC ./imgui/ ./imgui/backends/) +target_include_directories(${PROJECT_NAME} PUBLIC ./imgui/ ./imgui/backends/ ./implot/) project(Nanovg) set(${PROJECT_NAME}_SOURCES diff --git a/Externals/implot b/Externals/implot new file mode 160000 index 0000000..f156599 --- /dev/null +++ b/Externals/implot @@ -0,0 +1 @@ +Subproject commit f156599faefe316f7dd20fe6c783bf87c8bb6fd9 diff --git a/Graphics/private/Canvas.cpp b/Graphics/private/Canvas.cpp index c4b118d..c108eff 100644 --- a/Graphics/private/Canvas.cpp +++ b/Graphics/private/Canvas.cpp @@ -209,3 +209,195 @@ void Canvas::drawEnd() { glViewport(0, 0, size.x, size.y); nvgEndFrame(mContext->vg); } + + +void Canvas::colorWheel(const RectF& rec, const ColorWheel& colorWheel) { + auto NVG = mContext->vg; + const auto sizeInner = 5.f; + const auto sizeOuter = 7.f; + + float const x = rec.x + mOrigin.x; + float const y = rec.y + mOrigin.y; + float const w = rec.z; + float const h = rec.w; + + const HSV& hsv = colorWheel.color; + float const hue = hsv.h / (NVG_PI * 2); + + int i; + float r0, r1, ax, ay, bx, by, cx, cy, aeps, r; + NVGpaint paint; + + nvgSave(mContext->vg); + + cx = x + w * 0.5f; + cy = y + h * 0.5f; + r1 = (w < h ? w : h) * 0.5f - colorWheel.margin; + r0 = r1 - colorWheel.thickness; + + aeps = 0.5f / r1; // half a pixel arc length in radians (2pi cancels out). + + for (i = 0; i < 6; i++) { + float a0 = (float)i / 6.0f * NVG_PI * 2.0f - aeps; + float a1 = (float)(i + 1.0f) / 6.0f * NVG_PI * 2.0f + aeps; + nvgBeginPath(NVG); + nvgArc(NVG, cx, cy, r0, a0, a1, NVG_CW); + nvgArc(NVG, cx, cy, r1, a1, a0, NVG_CCW); + nvgClosePath(NVG); + ax = cx + cosf(a0) * (r0 + r1) * 0.5f; + ay = cy + sinf(a0) * (r0 + r1) * 0.5f; + bx = cx + cosf(a1) * (r0 + r1) * 0.5f; + by = cy + sinf(a1) * (r0 + r1) * 0.5f; + paint = nvgLinearGradient(NVG, ax, ay, bx, by, nvgHSLA(a0 / (NVG_PI * 2), 1.0f, 0.55f, 255), nvgHSLA(a1 / (NVG_PI * 2), 1.0f, 0.55f, 255)); + nvgFillPaint(NVG, paint); + nvgFill(NVG); + } + + nvgBeginPath(NVG); + nvgCircle(NVG, cx, cy, r0 - 0.5f); + nvgCircle(NVG, cx, cy, r1 + 0.5f); + nvgStrokeColor(NVG, nvgRGBA(0, 0, 0, 64)); + nvgStrokeWidth(NVG, 1.0f); + nvgStroke(NVG); + + // Selector + nvgSave(NVG); + nvgTranslate(NVG, cx, cy); + nvgRotate(NVG, hue * NVG_PI * 2); + + // Marker on + nvgStrokeWidth(NVG, 2.0f); + nvgBeginPath(NVG); + nvgRect(NVG, r0 - 1, -3, r1 - r0 + 2, 6); + nvgStrokeColor(NVG, nvgRGBA(255, 255, 255, 192)); + nvgStroke(NVG); + + paint = nvgBoxGradient(NVG, r0 - 3, -5, r1 - r0 + 6, 10, 2, 4, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0)); + nvgBeginPath(NVG); + nvgRect(NVG, r0 - 2 - 10, -4 - 10, r1 - r0 + 4 + 20, 8 + 20); + nvgRect(NVG, r0 - 2, -4, r1 - r0 + 4, 8); + nvgPathWinding(NVG, NVG_HOLE); + nvgFillPaint(NVG, paint); + nvgFill(NVG); + + // Center triangle + r = r0 - 6; + ax = cosf(120.0f / 180.0f * NVG_PI) * r; + ay = sinf(120.0f / 180.0f * NVG_PI) * r; + bx = cosf(-120.0f / 180.0f * NVG_PI) * r; + by = sinf(-120.0f / 180.0f * NVG_PI) * r; + nvgBeginPath(NVG); + nvgMoveTo(NVG, r, 0); + nvgLineTo(NVG, ax, ay); + nvgLineTo(NVG, bx, by); + nvgClosePath(NVG); + paint = nvgLinearGradient(NVG, r, 0, ax, ay, nvgHSLA(hue, 1.0f, 0.5f, 255), nvgRGBA(255, 255, 255, 255)); + nvgFillPaint(NVG, paint); + nvgFill(NVG); + paint = nvgLinearGradient(NVG, (r + ax) * 0.5f, (0 + ay) * 0.5f, bx, by, nvgRGBA(0, 0, 0, 0), nvgRGBA(0, 0, 0, 255)); + nvgFillPaint(NVG, paint); + nvgFill(NVG); + nvgStrokeColor(NVG, nvgRGBA(0, 0, 0, 64)); + nvgStroke(NVG); + + // Select circle on triangle + float yt = hsv.v * hsv.s; + float xt = hsv.v - 0.5 * yt; + ay = sinf(120.0f / 180.0f * NVG_PI) * r * (-1.0f + xt * 2.0f); + ax = cosf(120.0f / 180.0f * NVG_PI) * r * (1.0f - yt * 3.f); + nvgStrokeWidth(NVG, 2.0f); + nvgBeginPath(NVG); + nvgCircle(NVG, ax, ay, sizeInner); + nvgStrokeColor(NVG, nvgRGBA(255, 255, 255, 192)); + nvgStroke(NVG); + + paint = nvgRadialGradient(NVG, ax, ay, 7, 9, nvgRGBA(0, 0, 0, 64), nvgRGBA(0, 0, 0, 0)); + nvgBeginPath(NVG); + nvgRect(NVG, ax - 20, ay - 20, 40, 40); + nvgCircle(NVG, ax, ay, sizeOuter); + nvgPathWinding(NVG, NVG_HOLE); + nvgFillPaint(NVG, paint); + nvgFill(NVG); + + nvgRestore(NVG); + + nvgRestore(NVG); +} + +void Canvas::ColorWheel::fromPoint(const tp::RectF& area, const tp::Vec2F& crs) { + auto wheel_rec = area; + wheel_rec.pos += margin; + wheel_rec.size -= margin * 2; + + auto center = wheel_rec.pos + wheel_rec.size / 2.f; + auto edge = min(wheel_rec.size.x, wheel_rec.size.y); + wheel_rec.pos = center - edge / 2.f; + wheel_rec.size = edge; + + HSV hsv = color; + + auto pos = crs - center; + + auto r = (crs - center).length(); + if (r < edge / 2.f) { + if (r > edge / 2.f - thickness) { + auto angle = halnf(atan2(pos.y, pos.x)); + angle = halnf(angle > 0 ? angle : PI * 2 + angle); + angle = clamp(angle, 0.f, halnf(PI * 2)); + hsv.h = angle; + } + else { + pos.y *= -1; + pos /= (edge / 2.f) - thickness - 10; + + auto angle = PI * 2 - hsv.h - PI / 2; + if (angle < 0) { + angle += PI * 2; + } + + auto sin = halnf(::sin(-angle)); + auto cos = halnf(::cos(-angle)); + auto sv_pos = Vec2F{ pos.x * cos - pos.y * sin, pos.x * sin + pos.y * cos }; + + auto sv_angle = halnf(atan2(sv_pos.x, sv_pos.y)); + sv_angle = halnf(sv_angle > 0 ? sv_angle : PI * 2 + sv_angle); + sv_angle = clamp(sv_angle, 0.f, halnf(PI * 2)); + + auto p1 = Vec2F(0, 1); + auto p2 = Vec2F(::cos(PI / 6), -::sin(PI / 6)); + auto p3 = Vec2F{ -p2.x, p2.y }; + + Vec2F intersection = sv_pos; + if (sv_angle < PI / 3 * 2) { + if (intersectLines2D({ 0, 0 }, sv_pos, p1, p2, &intersection)) { + sv_pos = intersection; + } + } + else if (sv_angle < PI / 3 * 4) { + if (intersectLines2D({ 0, 0 }, sv_pos, p3, p2, &intersection)) { + sv_pos = intersection; + } + } + else { + if (intersectLines2D({ 0, 0 }, sv_pos, p1, p3, &intersection)) { + sv_pos = intersection; + } + } + + sv_pos.y = halnf((sv_pos.y + 1 / 2.f) * 2 / 3.f * 1.0); + sv_pos.x = halnf(sv_pos.x * 2 / sqrt(3) * 1.0); + sv_pos.x = (sv_pos.x + 1.f) / 2; + + sv_pos.x = clamp(sv_pos.x, 0.f, 1.f); + sv_pos.y = clamp(sv_pos.y, 0.f, 1.f); + + auto v = sv_pos.y * 0.5f + sv_pos.x; + auto s = sv_pos.y / v; + + hsv.v = clamp(v, 0.001f, 0.999f); + hsv.s = clamp(s, 0.001f, 0.999f); + } + } + + color = hsv; +} \ No newline at end of file diff --git a/Graphics/private/DebugGUI.cpp b/Graphics/private/DebugGUI.cpp index d824a88..03f6b1f 100644 --- a/Graphics/private/DebugGUI.cpp +++ b/Graphics/private/DebugGUI.cpp @@ -9,6 +9,8 @@ #include #include +#include "implot.h" + namespace tp { class DebugGUI::Context { public: @@ -41,6 +43,8 @@ DebugGUI::DebugGUI(Window* window) { // appearance io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + ImPlot::CreateContext(); + return; auto& colors = ImGui::GetStyle().Colors; @@ -75,10 +79,13 @@ DebugGUI::DebugGUI(Window* window) { } DebugGUI::~DebugGUI() { + ImPlot::DestroyContext(); + ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); + delete mContext; } diff --git a/Graphics/private/Window.cpp b/Graphics/private/Window.cpp index 06f8d77..ccaa39b 100644 --- a/Graphics/private/Window.cpp +++ b/Graphics/private/Window.cpp @@ -29,6 +29,8 @@ Window::Window(Vec2F size, const char* title) { // glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, 1); // glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); + mSize = size; + // Create a window and OpenGL context mContext->window = glfwCreateWindow((int) size.x, (int) size.y, title, nullptr, nullptr); if (!mContext->window) { diff --git a/Graphics/public/Graphics.hpp b/Graphics/public/Graphics.hpp index c193080..f854d3f 100644 --- a/Graphics/public/Graphics.hpp +++ b/Graphics/public/Graphics.hpp @@ -55,17 +55,26 @@ namespace tp { ualni id = 0; }; + struct ColorWheel { + HSV color = { 0.13, 0.5, 1 }; + halnf thickness = 20; + halnf margin = 5; + + void fromPoint(const RectF& area, const Vec2F& point); + }; + void setOrigin(const Vec2F& origin); void pushClamp(const RectF& rec); void popClamp(); - const RectF& getClampedArea() const; + [[nodiscard]] const RectF& getClampedArea() const; void debugCross(RectF rec, const RGBA& col); void rect(RectF rec, const RGBA& col, halnf round = 0); void frame(RectF rec, const RGBA& col, halnf round = 0); void circle(Vec2F pos, halnf size, const RGBA& col); void text(const char*, const RectF&, halnf size, Align, halnf padding, const RGBA&); + void colorWheel(const RectF& rec, const ColorWheel& colorWheel); ImageHandle createImageFromTextId(ualni id, Vec2F size); void updateTextureID(ImageHandle handle, ualni id); diff --git a/LibraryViewer/public/GUI.hpp b/LibraryViewer/public/GUI.hpp index 9ab1060..10a1ad7 100644 --- a/LibraryViewer/public/GUI.hpp +++ b/LibraryViewer/public/GUI.hpp @@ -248,7 +248,7 @@ namespace tp { void process(const EventHandler& events) override { filter(); - for (auto track : mSongList.getContent()) { + for (auto track : mSongList.getContainer()->getChildren()) { if (auto trackWidget = dynamic_cast(track.data())) { if (trackWidget->mState == TrackWidget::SELECTED) { mCurrentTrackInfo.mTrack = trackWidget->mTrack; @@ -268,7 +268,7 @@ namespace tp { void filter() { if (!mCurrentTrackInfo.isSongFilterChanged) return; - mSongList.clearChildren(); + mSongList.getContainer()->clear(); for (auto track : mTracks) { if (!mCurrentTrackInfo.songFilter.PassFilter(track->mTrack->mName.c_str()) && @@ -290,7 +290,7 @@ namespace tp { break; } - mSongList.addToMenu(track.data()); + mSongList.getContainer()->addChild(track.data()); } mCurrentTrackInfo.isSongFilterChanged = false; @@ -302,7 +302,7 @@ namespace tp { Buffer mTracks; - FloatingMenu mSongList; + ScrollableWidget mSongList; TrackInfoWidget mCurrentTrackInfo; TrackWidget mCurrentTrack; }; diff --git a/Modules/public/Timing.hpp b/Modules/public/Timing.hpp index 52d91c7..505738b 100644 --- a/Modules/public/Timing.hpp +++ b/Modules/public/Timing.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include "Environment.hpp" namespace tp { @@ -56,4 +57,19 @@ namespace tp { } } }; + + struct TimerWrapper { + explicit TimerWrapper(const std::function& arg) { + codeSnippet = arg; + } + + time_ms exec() { + Timer timer; + codeSnippet(); + return timer.timePassed(); + } + + private: + std::function codeSnippet; + }; } \ No newline at end of file diff --git a/Sketch3D/applications/Entry.cpp b/Sketch3D/applications/Entry.cpp index db4d6af..8574a62 100644 --- a/Sketch3D/applications/Entry.cpp +++ b/Sketch3D/applications/Entry.cpp @@ -2,19 +2,22 @@ #include "WidgetApplication.hpp" #include "Sketch3D.hpp" -#include "Sketch3DWidget.hpp" +#include "SketchGUI.hpp" using namespace tp; class Sketch3DApplication : public WidgetApplication { public: - Sketch3DApplication() : - mGui(*mGraphics->getCanvas(), { 1920, 1080 }) { + Sketch3DApplication() { setRoot(&mGui); + + mGui.createRenderWidget(mGraphics->getCanvas(), { 1920, 1080 }); + mGui.setProject(&mSketch); } private: - Sketch3DGUI mGui; + SketchGUI mGui; + Project mSketch; }; void runApp() { diff --git a/Sketch3D/private/Sketch3D.cpp b/Sketch3D/private/Sketch3D.cpp index a8b32c8..183036b 100644 --- a/Sketch3D/private/Sketch3D.cpp +++ b/Sketch3D/private/Sketch3D.cpp @@ -283,6 +283,14 @@ PencilBrush::~PencilBrush() { if (mStroke) delete mStroke; } +void PencilBrush::finish(Project* proj) { + if (mStroke) { + ensureReady(mStroke, &proj->mCamera); + proj->mLayers[proj->mActiveLayer]->strokes.pushBack(mStroke); + mStroke = nullptr; + } +} + Layer::~Layer() { for (auto str : strokes) { delete str.data(); @@ -316,6 +324,20 @@ void Project::sample(halnf pressure, halnf cameraRatio, Vec2F relativeCameraPos) } } +void Project::setPencil() { + if (auto idx = mBrushes.presents(mActiveBrush)) { + mBrushes.getSlotVal(idx)->finish(this); + } + mActiveBrush = "pencil"; +} + +void Project::setEraser() { + if (auto idx = mBrushes.presents(mActiveBrush)) { + mBrushes.getSlotVal(idx)->finish(this); + } + mActiveBrush = "eraser"; +} + Project::~Project() { for (auto brush : mBrushes) { delete brush->val; diff --git a/Sketch3D/private/SketchGUI.cpp b/Sketch3D/private/SketchGUI.cpp new file mode 100644 index 0000000..91c76bb --- /dev/null +++ b/Sketch3D/private/SketchGUI.cpp @@ -0,0 +1,184 @@ +#include "SketchGUI.hpp" +#include "SimpleLayouts.hpp" + +using namespace tp; + +SketchRenderWidget::SketchRenderWidget(Canvas& canvas, Vec2F renderResolution) : + mRenderer(renderResolution) { + mImage = canvas.createImageFromTextId(mRenderer.getBuff()->texId(), mRenderer.getBuff()->getSize()); + mCanvas = &canvas; + + setSizePolicy(SizePolicy::Expanding, SizePolicy::Expanding); + + setDebug("render", {}); +} + +SketchRenderWidget::~SketchRenderWidget() { mCanvas->deleteImageHandle(mImage); } + +void SketchRenderWidget::draw(Canvas& canvas) { + auto area = getArea().relative(); + if (mProject) { + mRenderer.renderToTexture(mProject, area.size); + canvas.drawImage(area, &mImage, 0, 1, 12); + } +} + +void SketchRenderWidget::setProject(Project* project) { mProject = project; } + +void SketchViewportWidget::setProject(Project* project) { + mProject = project; + mProject->mBackgroundColor = { 0.13f, 0.13f, 0.13f, 0.8f }; +} + +void SketchViewportWidget::setColor(const RGBA& color) { + ((PencilBrush*) mProject->mBrushes.get("pencil"))->mCol = color; +} + +void SketchViewportWidget::setRenderWidget(SketchRenderWidget* widget) { addChild(widget); } + +void SketchViewportWidget::process(const EventHandler& events) { + if (!mProject) return; + + auto area = getArea().relative(); + Vec2F pointer = events.getPointer(); + auto crs = events.getPointer(); + + if (!area.isInside(events.getPointer())) return; + + crs.x /= area.z; + crs.y /= area.w; + crs = (crs - 0.5) * 2; + + if (events.isPressed(InputID::MOUSE1)) { + mAction = true; + lockFocus(); + + } else if (events.isReleased(InputID::MOUSE1)) { + mAction = false; + freeFocus(); + } + + mProject->mCamera.setRatio(area.w / area.z); + + if (mAction) { + Vec2F relativePos = ((pointer / area.size) - 0.5f) * 2.f; + Vec2F relativePosPrev = ((mActionPosAbsolutePrev / area.size) - 0.5f) * 2.f; + Vec2F relativeDelta = relativePos - relativePosPrev; + + if (events.isDown(InputID::LEFT_SHIFT)) { + mProject->mCamera.move(relativePos, relativePosPrev); + } else if (events.isDown(InputID::LEFT_ALT)) { + mProject->mCamera.rotate(-relativeDelta.x * halnf(PI), -relativeDelta.y * halnf(PI)); + } else if (events.isDown(InputID::LEFT_CONTROL)) { + halnf factor = pointer.y / mActionPosAbsolutePrev.y; + mProject->mCamera.zoom(factor); + } else { + mProject->sample(events.getPointerPressure(), area.w / area.z, crs); + } + + } else { + mProject->sample(0, area.w / area.z, crs); + } + + mActionPosAbsolutePrev = pointer; +} + +SketchViewportWidget::SketchViewportWidget() { + setLayout(new ToolBarLayout(this)); + + setDebug("viewport", {}); +} + +void SketchViewportWidget::setToolbarWidget(Widget* widget) { + addChild(widget); + widget->bringToFront(); +} + +ToolbarWidget::ToolbarWidget() { + setDirection(false); + setDebug("toolbar", {}); + + addToToolbar(&mFileHover); + addToToolbar(&mPencilHover); + addToToolbar(&mViewHover); + + mFileHover.setText("File"); + mViewHover.setText("View"); + mPencilHover.setText("Brush"); +} + +void ToolbarWidget::addToToolbar(Widget* button) { + button->getLayout()->setMinSize({60, 30}); + getContainer()->addChild(button); +} + +SketchGUI::SketchGUI() : + DockWidget() { + + setCenterWidget(&mViewport); + dockWidget(&mPanel, DockLayout::RIGHT); + toggleWidgetVisibility(DockLayout::RIGHT); + + mPanel.addToMenu(&mControls); + mPanel.setText("Controls"); + mPanel.setArea({ 110, 110, 300, 500 }); + + mControls.setText("Tools"); + mControls.addToMenu(&mSelectPencil); + mControls.addToMenu(&mSelectEraser); + mControls.setArea({ 0, 0, 300, 400 }); + + // brush + { + auto popup = mToolbar.mPencilHover.getPopup(); + popup->addChild(&mColorPicker); + popup->addChild(&mBrushSizeSlider); + + mColorPicker.setArea({10, 10, 300, 300}); + } + + // view + { + auto popup = mToolbar.mViewHover.getPopup(); + popup->addChild(&mToggleSidePanel); + + mToggleSidePanel.setText("Toggle Panel"); + mToggleSidePanel.setAction([this](){ + toggleWidgetVisibility(getSide(&mPanel)); + }); + } + + mSelectPencil.setAction([this](){ mProject->setPencil(); }); + mSelectPencil.setText("Pencil"); + + mSelectEraser.setAction([this](){ mProject->setEraser(); }); + mSelectEraser.setText("Eraser TODO"); +} + +void SketchGUI::createRenderWidget(Canvas* canvas, const Vec2F& renderResolution) { + mRenderWidget = new SketchRenderWidget(*canvas, renderResolution); + + mViewport.setRenderWidget(mRenderWidget); + mViewport.setToolbarWidget(&mToolbar); +} + +void SketchGUI::setProject(Project* project) { + mProject = project; + mViewport.setProject(project); + if (mRenderWidget) mRenderWidget->setProject(project); +} + +SketchGUI::~SketchGUI() { delete mRenderWidget; } + +void SketchGUI::process(const EventHandler& events) { + DockWidget::process(events); + + if (!mProject) return; + + auto pencil = dynamic_cast(mProject->mBrushes.get("pencil")); + + pencil->mCol = mColorPicker.mColorWheel.color; + pencil->mSize = mBrushSizeSlider.val() * 0.2f + 0.001f; +} + +void SketchGUI::draw(Canvas& canvas) { canvas.rect(getArea().relative(), mBackgroundColor, mRounding); } diff --git a/Sketch3D/public/Sketch3D.hpp b/Sketch3D/public/Sketch3D.hpp index 3e8c334..c2d546b 100644 --- a/Sketch3D/public/Sketch3D.hpp +++ b/Sketch3D/public/Sketch3D.hpp @@ -97,6 +97,9 @@ namespace tp { Project(); ~Project(); + void setPencil(); + void setEraser(); + // pos from -1 to 1 (left ot right bottom to top) void sample(halnf pressure, halnf cameraRatio, Vec2F relativeCameraPos); @@ -116,6 +119,7 @@ namespace tp { std::string mType = "equal"; Brush() = default; virtual void sample(Project* proj, Vec2F crs, halnf pressure) {} + virtual void finish(Project* proj) {} virtual void draw(Renderer* render, const Camera* camera) {} virtual ~Brush() = default; }; @@ -125,8 +129,9 @@ namespace tp { PencilBrush(); ~PencilBrush() override; - virtual void sample(Project* proj, Vec2F crs, halnf pressure) override; - virtual void draw(Renderer* render, const Camera* camera) override; + void finish(Project* proj) override; + void sample(Project* proj, Vec2F crs, halnf pressure) override; + void draw(Renderer* render, const Camera* camera) override; private: void ensureReady(Stroke* stroke, const Camera* cam, bool debug = false) const; diff --git a/Sketch3D/public/Sketch3DWidget.hpp b/Sketch3D/public/Sketch3DWidget.hpp deleted file mode 100644 index 9859525..0000000 --- a/Sketch3D/public/Sketch3DWidget.hpp +++ /dev/null @@ -1,169 +0,0 @@ -#pragma once - -#include "Sketch3D.hpp" -#include "Widget.hpp" -#include "DockWidget.hpp" -#include "FloatingWidget.hpp" - -namespace tp { - - class Sketch3DWidget : public Widget { - public: - Sketch3DWidget(Canvas& canvas, Vec2F renderResolution) : - mRenderer(renderResolution) { - mImage = canvas.createImageFromTextId(mRenderer.getBuff()->texId(), mRenderer.getBuff()->getSize()); - mCanvas = &canvas; - - mProject.mBackgroundColor = { 0.13f, 0.13f, 0.13f, 1.f }; - } - - ~Sketch3DWidget() { mCanvas->deleteImageHandle(mImage); } - - void process(const EventHandler& events) override { - auto area = getArea().relative(); - - Vec2F pointer = events.getPointer(); - auto crs = events.getPointer(); - - if (!area.isInside(events.getPointer())) { - return; - } - - crs.x /= area.z; - crs.y /= area.w; - crs = (crs - 0.5) * 2; - - if (events.isPressed(InputID::MOUSE1)) { - mAction = true; - } else if (events.isReleased(InputID::MOUSE1)) { - mAction = false; - } - - Vec2F relativePos = ((pointer / area.size) - 0.5f) * 2.f; - Vec2F relativePosPrev = ((mActionPosAbsolutePrev / area.size) - 0.5f) * 2.f; - Vec2F relativeDelta = relativePos - relativePosPrev; - - mProject.mCamera.setRatio(area.w / area.z); - - switch (mMode) { - case Mode::MOVE: - { - if (mAction) mProject.mCamera.move(relativePos, relativePosPrev); - break; - } - case Mode::ZOOM: - { - halnf factor = pointer.y / mActionPosAbsolutePrev.y; - if (mAction) mProject.mCamera.zoom(factor); - break; - } - case Mode::ROTATE: - { - if (mAction) mProject.mCamera.rotate(-relativeDelta.x * halnf(PI), -relativeDelta.y * halnf(PI)); - break; - } - case Mode::DRAW: - { - mProject.sample(events.getPointerPressure(), area.w / area.z, crs); - break; - } - default: break; - } - - mActionPosAbsolutePrev = pointer; - } - - void draw(Canvas& canvas) override { - auto area = getArea().relative(); - mRenderer.renderToTexture(&mProject, area.size); - canvas.drawImage(area, &mImage, 0, 1, 12); - } - - void setColor(const RGBA& color) { ((PencilBrush*) mProject.mBrushes.get("pencil"))->mCol = color; } - - [[nodiscard]] bool processesEvents() const override { return true; } - - public: - enum class Mode { MOVE, ROTATE, ZOOM, DRAW, NONE } mMode = Mode::NONE; - - Vec2F mActionPosAbsolutePrev = { 0, 0 }; - bool mAction = false; - - private: - Renderer mRenderer; - Project mProject; - Canvas::ImageHandle mImage; - Canvas* mCanvas = nullptr; - }; - - class Sketch3DGUI : public DockWidget { - public: - explicit Sketch3DGUI(Canvas& canvas, Vec2F renderResolution) : - DockWidget(), - mViewport(canvas, renderResolution) { - - mControls.addToMenu(&mDrawButton); - mControls.addToMenu(&mMoveButton); - mControls.addToMenu(&mRotateButton); - mControls.addToMenu(&mZoomButton); - - mColorPicker.addToMenu(&mRed); - mColorPicker.addToMenu(&mGreen); - mColorPicker.addToMenu(&mBlue); - - setCenterWidget(&mViewport); - - mPanel.addToMenu(&mControls); - mPanel.addToMenu(&mColorPicker); - - // addChild(&mPanel); - dockWidget(&mPanel, DockLayout::RIGHT); - - mDrawButton.setAction([this]() { mViewport.mMode = Sketch3DWidget::Mode::DRAW; }); - mMoveButton.setAction([this]() { mViewport.mMode = Sketch3DWidget::Mode::MOVE; }); - mRotateButton.setAction([this]() { mViewport.mMode = Sketch3DWidget::Mode::ROTATE; }); - mZoomButton.setAction([this]() { mViewport.mMode = Sketch3DWidget::Mode::ZOOM; }); - - mDrawButton.setText("Draw"); - mMoveButton.setText("Pan"); - mRotateButton.setText("Orbit"); - mZoomButton.setText("Zoom"); - - mPanel.setText("Controls"); - mControls.setText("Tools"); - mColorPicker.setText("Color"); - - mPanel.setArea({110, 110, 300, 500 }); - mControls.setArea({0, 0, 300, 400 }); - mColorPicker.setArea({0, 0, 300, 300 }); - - } - - void process(const EventHandler& events) override { - DockWidget::process(events); - - mViewport.setColor(RGBA(mRed.val(), mGreen.val(), mBlue.val(), 1.f)); - } - - void draw(Canvas& canvas) override { canvas.rect(getArea().relative(), mBackgroundColor, mRounding); } - - private: - Sketch3DWidget mViewport; - - FloatingMenu mPanel; - - FloatingMenu mControls; - ButtonWidget mDrawButton; - ButtonWidget mMoveButton; - ButtonWidget mRotateButton; - ButtonWidget mZoomButton; - - FloatingMenu mColorPicker; - SliderWidget mRed; - SliderWidget mGreen; - SliderWidget mBlue; - - RGBA mBackgroundColor; - halnf mRounding = 0; - }; -} \ No newline at end of file diff --git a/Sketch3D/public/SketchGUI.hpp b/Sketch3D/public/SketchGUI.hpp new file mode 100644 index 0000000..f4770b2 --- /dev/null +++ b/Sketch3D/public/SketchGUI.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include "Sketch3D.hpp" +#include "Widget.hpp" +#include "DockWidget.hpp" +#include "FloatingWidget.hpp" +#include "ColorPickerWidget.hpp" + +namespace tp { + + class SketchRenderWidget : public Widget { + public: + SketchRenderWidget(Canvas& canvas, Vec2F renderResolution); + ~SketchRenderWidget() override; + + void draw(Canvas& canvas) override; + void setProject(Project* project); + + private: + Renderer mRenderer; + Project* mProject = nullptr; + Canvas::ImageHandle mImage; + Canvas* mCanvas = nullptr; + }; + + class SketchViewportWidget : public Widget { + public: + SketchViewportWidget(); + + void setProject(Project* project); + void setColor(const RGBA& color); + + void setRenderWidget(SketchRenderWidget* widget); + void setToolbarWidget(Widget* widget); + + void process(const EventHandler& events) override; + [[nodiscard]] bool processesEvents() const override { return true; } + + private: + Project* mProject = nullptr; + + public: + Vec2F mActionPosAbsolutePrev = { 0, 0 }; + bool mAction = false; + }; + + + class ToolbarWidget : public ScrollableWidget { + public: + ToolbarWidget(); + + private: + void addToToolbar(Widget* button); + + public: + HoverPopupTriggerWidget mFileHover; + HoverPopupTriggerWidget mPencilHover; + HoverPopupTriggerWidget mViewHover; + }; + + class SketchGUI : public DockWidget { + public: + SketchGUI(); + void createRenderWidget(Canvas* canvas, const Vec2F& renderResolution); + void setProject(Project* project); + ~SketchGUI() override; + + void process(const EventHandler& events) override; + void draw(Canvas& canvas) override; + + private: + Project* mProject = nullptr; + + SketchViewportWidget mViewport; + ToolbarWidget mToolbar; + + SketchRenderWidget* mRenderWidget = nullptr; + + FloatingMenu mPanel; + + RGBPickerWidget mColorPicker; + + ButtonWidget mToggleSidePanel; + + ButtonWidget mSelectPencil; + ButtonWidget mSelectEraser; + + SliderWidget mBrushSizeSlider; + + FloatingMenu mControls; + + RGBA mBackgroundColor; + halnf mRounding = 0; + }; +} diff --git a/TODO b/TODO index e14c5f7..f13e92e 100644 --- a/TODO +++ b/TODO @@ -1,13 +1,18 @@ Widgets: - Replace Old Widgets!! + Basic scrollable widget + Collapsing menus + + Icons + Toolbar widget + Fix focus locking mechanism + Hover PopUps + Color Picker Optimize: Make visual parameters static Make get-areas fetch cache and not animation objects New Widgets: - Collapsing menu - Sliders Check Boxes Drop-Downs Toaster notifications diff --git a/Widgets/CMakeLists.txt b/Widgets/CMakeLists.txt index 126d59f..0589f17 100644 --- a/Widgets/CMakeLists.txt +++ b/Widgets/CMakeLists.txt @@ -10,8 +10,8 @@ target_link_libraries(${PROJECT_NAME} PUBLIC Math Graphics Imgui) ### -------------------------- Applications -------------------------- ### -add_executable(Widgets2Example examples/Example.cpp) -target_link_libraries(Widgets2Example ${PROJECT_NAME} ${GLEW_LIB}) -target_include_directories(Widgets2Example PUBLIC ../Externals/glfw/include ${GLEW_INCLUDE_DIR}) +add_executable(WidgetsExample examples/Example.cpp) +target_link_libraries(WidgetsExample ${PROJECT_NAME} ${GLEW_LIB}) +target_include_directories(WidgetsExample 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/Example.cpp b/Widgets/examples/Example.cpp index 288d1b1..d0e7b31 100644 --- a/Widgets/examples/Example.cpp +++ b/Widgets/examples/Example.cpp @@ -4,6 +4,7 @@ #include "RootWidget.hpp" #include "FloatingWidget.hpp" #include "DockWidget.hpp" +#include "ColorPickerWidget.hpp" #include "ScrollableLayout.hpp" @@ -13,7 +14,7 @@ using namespace tp; class Example : public WidgetApplication { public: Example() { - exampleScrolling(); + exampleColorPicker(); } void exampleAll() { @@ -36,6 +37,15 @@ public: setRoot(&dock); } + void exampleColorPicker() { + static DockWidget dock; + static RGBPickerWidget colorPicker; + + dock.dockWidget(&colorPicker, DockLayout::RIGHT); + + setRoot(&dock); + } + void exampleBasic() { static Widget widget; static LabelWidget label; @@ -50,40 +60,44 @@ public: } void exampleScrolling() { - static Widget widget; - static Widget content; + static ScrollableWidget widget; static ButtonWidget buttons[10]; - static ScrollableBarWidget scrollBar; + + widget.setDirection(false); setRoot(&widget); - widget.addChild(&scrollBar); - widget.addChild(&content); - for (auto& button : buttons) { - content.addChild(&button); + widget.getContainer()->addChild(&button); } - widget.setLayout(new ScrollableLayout(&widget)); - - RootWidget::setWidgetArea(content, { 333, 333, 522, 522 }); + RootWidget::setWidgetArea(*widget.getContainer(), { 333, 333, 522, 522 }); } void examplePopup() { static Widget root; + static Widget popup; + static ButtonWidget closeButton; static ButtonWidget openButton; + static ButtonWidget buttons[3]; + + for (auto& button : buttons) { + popup.addChild(&button); + } + + popup.addChild(&closeButton); root.addChild(&openButton); ((BasicLayout*)root.getLayout())->setLayoutPolicy(LayoutPolicy::Passive); openButton.setAction([&](){ - closeButton.setArea({ 50, 50, 100, 100 }); - openButton.openPopup(&closeButton); + popup.setArea({ 50, 50, 300, 300 }); + openButton.openPopup(&popup); }); closeButton.setAction([&](){ - closeButton.closePopup(&closeButton); + closeButton.closePopup(&popup); }); openButton.setText("open"); diff --git a/Widgets/private/RootWidget.cpp b/Widgets/private/RootWidget.cpp index 1203b58..41697b6 100644 --- a/Widgets/private/RootWidget.cpp +++ b/Widgets/private/RootWidget.cpp @@ -2,12 +2,14 @@ #include "RootWidget.hpp" #include "BasicLayout.hpp" -#include "OverlayLayout.hpp" +#include "SimpleLayouts.hpp" #include using namespace tp; +#define SAMPLE(label, scope) gDebugWidget.##label.addSample(TimerWrapper([&]() scope ).exec()); + WidgetLayout* WidgetManagerInterface::defaultLayout(Widget* widget) { return new BasicLayout(widget); } @@ -35,21 +37,23 @@ void RootWidget::setRootWidget(Widget* widget) { void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) { mRoot.setArea(screenArea); - if (!gDebugWidget.update(this, *events)) return; + gDebugWidget.mUpdManager.addSample(TimerWrapper([&]() { + // construct hierarchy tree of widgets to process + mUpdateManager.updateTreeToProcess(&mRoot); - // construct hierarchy tree of widgets to process - mUpdateManager.updateTreeToProcess(&mRoot); + updateAnimations(); + updateAreaCache(&mRoot, true); - updateAnimations(); - updateAreaCache(&mRoot, true); + // update all events and call all event processing callbacks + mUpdateManager.processWidgets(&mRoot, *events); - // update all events and call all event processing callbacks - mUpdateManager.processWidgets(&mRoot, *events); + updateAreaCache(&mRoot, true); + }).exec()); - updateAreaCache(&mRoot, true); - - // update widget sizes base on individual size policies - mLayoutManager.adjust(&mRoot); + gDebugWidget.mLayManager.addSample(TimerWrapper([&]() { + // update widget sizes base on individual size policies + mLayoutManager.adjust(&mRoot); + }).exec()); updateAreaCache(&mRoot, false); @@ -67,13 +71,9 @@ bool RootWidget::needsUpdate() const { void RootWidget::drawFrame(Canvas& canvas) { canvas.rect(mRoot.getAreaT(), RGBA(0, 0, 0, 1)); - drawRecursion(canvas, &mRoot, { 0, 0 }); - - gDebugWidget.drawDebug(this, canvas); } - void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos) { if (!active->mFlags.get(ENABLED)) return; @@ -81,7 +81,9 @@ void RootWidget::drawRecursion(Canvas& canvas, Widget* active, const Vec2F& pos) canvas.pushClamp({ pos, active->getArea().size }); if (canvas.getClampedArea().size.length2() > EPSILON) { - active->draw(canvas); + if (active->getArea().sizeVecW() > active->getArea().pos) { + active->draw(canvas); + } for (auto child = active->mDepthOrder.lastNode(); child; child = child->prev) { drawRecursion(canvas, child->data, pos + child->data->getArea().pos); @@ -152,4 +154,3 @@ void RootWidget::closePopup(Widget* widget) { void RootWidget::lockFocus(Widget* widget) { mUpdateManager.lockFocus(widget); } void RootWidget::freeFocus(Widget* widget) { mUpdateManager.freeFocus(widget); } -bool RootWidget::isDebug() const { return gDebugWidget.isDebug(); } diff --git a/Widgets/private/WidgetApplication.cpp b/Widgets/private/WidgetApplication.cpp index 9f0e99d..70e5aa0 100644 --- a/Widgets/private/WidgetApplication.cpp +++ b/Widgets/private/WidgetApplication.cpp @@ -5,6 +5,58 @@ using namespace tp; void WidgetApplication::debugUI() { - ImGui::Text("Proc ms : %i", (int) mProcTime); - ImGui::SameLine(); ImGui::Text("Draw ms : %i", (int) mDrawTime); + // pass } + +void WidgetApplication::setRoot(Widget* widget) { + mRootWidget.setRootWidget(widget); + + tp::RootWidget::setWidgetArea(*widget, {{}, mWindow->getSize()}); +} + +void WidgetApplication::processFrame(EventHandler* eventHandler, halnf deltaTime) { + const auto area = RectF({ 0, 0 }, mWindow->getSize()); + + gDebugWidget.update(&mRootWidget, *eventHandler); + + if (gDebugWidget.isDebug()) { + mGuiArea = area.splitByFactorHL(mDebugSplitFactor); + mDebugArea = area.splitByFactorHR(mDebugSplitFactor); + } else { + mGuiArea = area; + } + + if (gDebugWidget.isFrozen()) return; + + Timer timer; + mRootWidget.processFrame(eventHandler, mGuiArea); + gDebugWidget.mProcTime.addSample(timer.timePassed()); +} + +void WidgetApplication::drawFrame(Canvas* canvas) { + Timer timer; + mRootWidget.drawFrame(*canvas); + gDebugWidget.mDrawTime.addSample(timer.timePassed()); + + if (gDebugWidget.isDebug()) { + ImGui::SetNextWindowPos(ImVec2(mDebugArea.x, mDebugArea.y)); + ImGui::SetNextWindowSize(ImVec2(mDebugArea.z, mDebugArea.w)); + + if (ImGui::Begin( + "Existing Window Name", + nullptr, + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDecoration + )) { + + drawDebug(); + debugUI(); + + gDebugWidget.drawDebug(&mRootWidget, *canvas); + } + ImGui::End(); + } +} + +bool WidgetApplication::forceNewFrame() { + return mRootWidget.needsUpdate(); +} \ No newline at end of file diff --git a/Widgets/private/layouts/DockLayout.cpp b/Widgets/private/layouts/DockLayout.cpp index e4c60d7..62f3023 100644 --- a/Widgets/private/layouts/DockLayout.cpp +++ b/Widgets/private/layouts/DockLayout.cpp @@ -61,6 +61,8 @@ void DockLayout::toggleWidgetVisibility(Side side) { void DockLayout::calculateSideAreas() { auto startArea = getArea().relative(); + if (startArea.size.x <= 0 || startArea.size.y <= 0) return; + for (auto& sideWidget : mSideWidgets) { const auto side = sideWidget.order; @@ -99,11 +101,13 @@ void DockLayout::calculateResizeHandles() { RectF area = getArea().relative(); for (auto& sideWidget : mSideWidgets) { + if (!sideWidget.widget) continue; + auto& sideSize = sideWidget.absoluteSize; auto& resizeHandle = sideWidget.resizeHandle; if (resizeHandle.end < mSideSizePadding * 2) { - sideSize = resizeHandle.end / 2.f; + // sideSize = resizeHandle.end / 2.f; } else { sideSize = clamp(sideSize, resizeHandle.start + mSideSizePadding, resizeHandle.end - mSideSizePadding); } diff --git a/Widgets/private/layouts/OverlayLayout.cpp b/Widgets/private/layouts/OverlayLayout.cpp deleted file mode 100644 index e30ce16..0000000 --- a/Widgets/private/layouts/OverlayLayout.cpp +++ /dev/null @@ -1,29 +0,0 @@ - -#include "OverlayLayout.hpp" -#include "Widget.hpp" - - -using namespace tp; - -void OverlayLayout::updateLayout(bool vertical) { - if (vertical) return; - - if (children().empty()) return; - - for (auto child : children()) { - child->getLayout()->setArea(getArea().relative()); - } - - /* -if (children().empty()) return; - -auto first = children().front(); - -first->getLayout()->setArea(getArea().relative()); - -for (auto child : children()) { - if (child == first) continue; - child->getLayout()->setArea(getArea().relative()); -} - */ -} \ No newline at end of file diff --git a/Widgets/private/layouts/ScrollableLayout.cpp b/Widgets/private/layouts/ScrollableLayout.cpp index 43f9c39..dd2a1cf 100644 --- a/Widgets/private/layouts/ScrollableLayout.cpp +++ b/Widgets/private/layouts/ScrollableLayout.cpp @@ -30,9 +30,9 @@ void ScrollableLayout::updateWidgetRects(const RectF& area, Widget* content, Scr auto sizeFactor = (area.size[dir] / content->getLayout()->getArea().size[dir]); auto splitFactor = 1 - ((sizeFactor > 1.f) ? 0 : mScrollerSize / area.size[!dir]); - auto holderArea = area.splitByFactorHL(splitFactor); + auto holderArea = dir ? area.splitByFactorHL(splitFactor) : area.splitByFactorVT(splitFactor); auto contentArea = content->getLayout()->getArea(); - auto scrollerArea = area.splitByFactorHR(splitFactor); + auto scrollerArea = dir ? area.splitByFactorHR(splitFactor) : area.splitByFactorVB(splitFactor); scroller->updateSizeFactor(sizeFactor); diff --git a/Widgets/private/layouts/SimpleLayouts.cpp b/Widgets/private/layouts/SimpleLayouts.cpp new file mode 100644 index 0000000..c65ea62 --- /dev/null +++ b/Widgets/private/layouts/SimpleLayouts.cpp @@ -0,0 +1,32 @@ + +#include "SimpleLayouts.hpp" +#include "Widget.hpp" + + +using namespace tp; + +void OverlayLayout::updateLayout(bool vertical) { + if (vertical) return; + + if (children().empty()) return; + + for (auto child : children()) { + child->getLayout()->setArea(getArea().relative()); + } +} + +void ToolBarLayout::updateLayout(bool vertical) { + if (vertical) return; + + if (children().size() != 2) return; + + auto toolbar = children().back(); + auto content = children().front(); + + auto factor = mToolBarHeight / getArea().w; + + auto area = getArea().relative(); + + toolbar->getLayout()->setArea(area.splitByFactorVT(factor)); + content->getLayout()->setArea(area); +} \ No newline at end of file diff --git a/Widgets/private/managers/DebugManager.cpp b/Widgets/private/managers/DebugManager.cpp index 0e390fe..921f7e1 100644 --- a/Widgets/private/managers/DebugManager.cpp +++ b/Widgets/private/managers/DebugManager.cpp @@ -3,28 +3,26 @@ #include "BasicLayout.hpp" #include "imgui.h" +#include "implot.h" + #include using namespace tp; DebugManager tp::gDebugWidget; -#define LIST_SIZE { -FLT_MIN, 150 } +#define LIST_SIZE \ + { -FLT_MIN, 150 } -bool DebugManager::update(RootWidget* rootWidget, EventHandler& events) { +void DebugManager::update(RootWidget* rootWidget, EventHandler& events) { mRootWidget = rootWidget; events.setEnableKeyEvents(true); if (events.isPressed(InputID::D)) mDebug = !mDebug; if (mDebug) { - auto area = rootWidget->mRoot.getAreaT(); - area.size -= { 400, 0 }; - rootWidget->mRoot.setArea(area); - events.setEnableKeyEvents(true); if (events.isPressed(InputID::K)) mDebugStopProcessing = !mDebugStopProcessing; - if (mDebugStopProcessing) return false; if (auto widget = rootWidget->mUpdateManager.mInFocusWidget) { if (auto lay = dynamic_cast(widget->getLayout())) { @@ -49,8 +47,6 @@ bool DebugManager::update(RootWidget* rootWidget, EventHandler& events) { if (events.isPressed(InputID::L)) mLayBreakpoints.insert(breakWidget); } } - - return true; } void DebugManager::drawDebug(RootWidget* rootWidget, Canvas& canvas) { @@ -60,20 +56,24 @@ void DebugManager::drawDebug(RootWidget* rootWidget, Canvas& canvas) { return; } + drawPerformance(); + // ImGui::Checkbox("Draw debug", &mDebug); - ImGui::SameLine(); ImGui::Text("To Toggle processing press k"); + ImGui::SameLine(); + ImGui::Text("To Toggle processing press k"); auto& upd = rootWidget->mUpdateManager; - recursiveDraw(canvas, &rootWidget->mRoot, { 0, 0 }, 0); - ImGui::Text("Triggered: %i", (int) upd.mTriggeredWidgets.size()); - ImGui::SameLine(); ImGui::Text("Processing: %i", upd.mDebugWidgetsToProcess); + ImGui::SameLine(); + ImGui::Text("Processing: %i", upd.mDebugWidgetsToProcess); ImGui::Checkbox("Stop processing", &mDebugStopProcessing); - ImGui::SameLine(); ImGui::Checkbox("Force new frames", &mDebugRedrawAlways); - ImGui::SameLine(); ImGui::Checkbox("Slow-mo", &mSlowMotion); + ImGui::SameLine(); + ImGui::Checkbox("Force new frames", &mDebugRedrawAlways); + ImGui::SameLine(); + ImGui::Checkbox("Detailed", &mDetailed); if (upd.mInFocusWidget) { ImGui::Text("Under cursor"); @@ -87,17 +87,21 @@ void DebugManager::drawDebug(RootWidget* rootWidget, Canvas& canvas) { } } - ImGui::Text("Triggered"); - { - if (ImGui::BeginListBox("##triggered", LIST_SIZE)) { - for (auto widget : upd.mTriggeredWidgets) { - widgetMenu(widget.first); + if (mDetailed) { + ImGui::Text("Triggered"); + { + if (ImGui::BeginListBox("##triggered", LIST_SIZE)) { + for (auto widget : upd.mTriggeredWidgets) { + widgetMenu(widget.first); + } + ImGui::EndListBox(); } - ImGui::EndListBox(); } - } - drawLayoutOrder(); + drawLayoutOrder(); + + recursiveDraw(canvas, &rootWidget->mRoot, { 0, 0 }, 0); + } } void DebugManager::recursiveDraw(Canvas& canvas, Widget* active, const Vec2F& pos, int depthOrder) { @@ -174,4 +178,14 @@ void DebugManager::drawLayoutOrder() { } ImGui::EndListBox(); } +} + +void DebugManager::drawPerformance() { + if (ImPlot::BeginPlot("Per Frame Time")) { + ImPlot::PlotLine("Proc ms", mProcTime.get(), mProcTime.size()); + ImPlot::PlotLine("Draw ms", mDrawTime.get(), mDrawTime.size()); + ImPlot::PlotLine("UPD ms", mUpdManager.get(), mUpdManager.size()); + ImPlot::PlotLine("LAY ms", mLayManager.get(), mLayManager.size()); + ImPlot::EndPlot(); + } } \ No newline at end of file diff --git a/Widgets/private/managers/LayoutManager.cpp b/Widgets/private/managers/LayoutManager.cpp index 749b7e6..83f51ba 100644 --- a/Widgets/private/managers/LayoutManager.cpp +++ b/Widgets/private/managers/LayoutManager.cpp @@ -33,7 +33,11 @@ void LayoutManager::findDependencies(Widget* root) { Widget::dfs(root, [this](Widget* widget) { mDepGraph.insert({ widget, {} }); }); for (auto& [widget, deps] : mDepGraph) { + if (!widget->isUpdate()) continue; + for (auto child : widget->mChildren) { + // if (!child->isUpdate()) continue; + mDepGraph.insert({ child, {} }); if (auto depOrder = getLayoutOrder(widget->getLayout(), child->getLayout())) { diff --git a/Widgets/private/managers/UpdateManager.cpp b/Widgets/private/managers/UpdateManager.cpp index 6898ee3..d1af2cd 100644 --- a/Widgets/private/managers/UpdateManager.cpp +++ b/Widgets/private/managers/UpdateManager.cpp @@ -76,10 +76,19 @@ void UpdateManager::handleFocusChanges(Widget* root, EventHandler& events) { mInFocusWidget = nullptr; - if (!mFocusLockWidget) { - findFocusWidget(root, &mInFocusWidget, events.getPointer()); - } else { - mInFocusWidget = mFocusLockWidget; + findFocusWidget(root, &mInFocusWidget, events.getPointer()); + + if (mFocusLockWidget) { + bool hasLockedWidget = false; + for (auto iter = mInFocusWidget; iter; iter = iter->mParent) { + if (iter == mFocusLockWidget) { + hasLockedWidget = true; + break; + } + } + if (!hasLockedWidget) { + mInFocusWidget = mFocusLockWidget; + } } // if (mInFocusWidget == prevFocus) return; diff --git a/Widgets/private/widgets/ColorPickerWidget.cpp b/Widgets/private/widgets/ColorPickerWidget.cpp new file mode 100644 index 0000000..1f5c018 --- /dev/null +++ b/Widgets/private/widgets/ColorPickerWidget.cpp @@ -0,0 +1,22 @@ +#include "ColorPickerWidget.hpp" + +using namespace tp; + +void RGBPickerWidget::process(const EventHandler& events) { + if (events.isPressed(InputID::MOUSE1)) { + mColorWheel.fromPoint(getArea().relative(), events.getPointer()); + lockFocus(); + } + + if (events.isReleased(InputID::MOUSE1)) { + freeFocus(); + } + + if (events.getPointerDelta().length() > EPSILON && events.isDown(InputID::MOUSE1)) { + mColorWheel.fromPoint(getArea().relative(), events.getPointer()); + } +} + +void RGBPickerWidget::draw(Canvas& canvas) { + canvas.colorWheel(getArea().relative(), mColorWheel); +} diff --git a/Widgets/private/widgets/DockWidget.cpp b/Widgets/private/widgets/DockWidget.cpp index 565d087..b75a35c 100644 --- a/Widgets/private/widgets/DockWidget.cpp +++ b/Widgets/private/widgets/DockWidget.cpp @@ -46,6 +46,8 @@ void DockWidget::toggleWidgetVisibility(DockLayout::Side side) { widget->bringToBack(); layout()->toggleWidgetVisibility(side); + + widget->triggerWidgetUpdate("dock visibility changed"); } } @@ -80,9 +82,11 @@ void DockWidget::process(const EventHandler& events) { if (events.isPressed(InputID::MOUSE1)) { if (layout()->startResize(events.getPointer())) { triggerWidgetUpdate("dock layout resizing"); + lockFocus(); } } else if (events.isReleased(InputID::MOUSE1)) { layout()->endResize(); + freeFocus(); } if (layout()->isResizing()) { diff --git a/Widgets/private/widgets/ScrollableWidget.cpp b/Widgets/private/widgets/ScrollableWidget.cpp index 6497947..e67f0fa 100644 --- a/Widgets/private/widgets/ScrollableWidget.cpp +++ b/Widgets/private/widgets/ScrollableWidget.cpp @@ -1,4 +1,6 @@ #include "ScrollableWidget.hpp" +#include "ScrollableLayout.hpp" +#include "BasicLayout.hpp" using namespace tp; @@ -84,4 +86,21 @@ void ScrollableBarWidget::updateHandleRect() { const RectF& ScrollableBarWidget::getHandleRect() const { return mHandleRect; -} \ No newline at end of file +} + +ScrollableWidget::ScrollableWidget() { + addChild(&mScroller); + addChild(&mContent); + + setLayout(new ScrollableLayout(this)); + + mContent.setSizePolicy(SizePolicy::Minimal, SizePolicy::Minimal); +} + +void ScrollableWidget::setDirection(bool direction) { + if (auto lay = dynamic_cast(mContent.getLayout())) { + lay->setLayoutPolicy(direction ? LayoutPolicy::Vertical : LayoutPolicy::Horizontal); + } + + mScroller.setDirection(direction); +} diff --git a/Widgets/private/widgets/SimpleWidgets.cpp b/Widgets/private/widgets/SimpleWidgets.cpp index cdd2c33..3407fa4 100644 --- a/Widgets/private/widgets/SimpleWidgets.cpp +++ b/Widgets/private/widgets/SimpleWidgets.cpp @@ -127,4 +127,48 @@ RectF SliderWidget::getHandleArea() const { area.size.x = mHandleSize; return area; -} \ No newline at end of file +} + +void PopupWidget::open(Widget* parent, const RectF& at) { + mParentArea = parent->getArea().relative(); + mParentArea.pos = at.pos * -1; + mParentArea.shrinkFromCenter(-10, true); + + setArea(at); + parent->openPopup(this); +} + +void PopupWidget::process(const EventHandler& events) { + bool insideParent = mParentArea.isInside(events.getPointer()); + bool insideMenu = getArea().relative().isInside(events.getPointer()); + + if (!(insideMenu || insideParent)) { + closePopup(this); + } +} + +void PopupWidget::draw(Canvas& canvas) { + canvas.rect(getArea().relative(), col, rounding); +} + +void HoverPopupTriggerWidget::mouseEnter() { + auto area = getArea().relative(); + auto size = Vec2F(300, 400); + + if (mDirection == Right) { + auto popupArea = RectF{ area.p4() + Vec2F{ gap, 0.f }, size }; + mPopup.open(this, popupArea); + } else { + auto popupArea = RectF{ area.p2() + Vec2F{ 0.f, gap }, size }; + mPopup.open(this, popupArea); + } +} + +PopupWidget* HoverPopupTriggerWidget::getPopup() { + return &mPopup; +} + +void HoverPopupTriggerWidget::draw(Canvas& canvas) { + canvas.rect(getArea().relative(), col, rounding); + LabelWidget::draw(canvas); +} diff --git a/Widgets/public/Layout.hpp b/Widgets/public/Layout.hpp index 2b437fe..b878d32 100644 --- a/Widgets/public/Layout.hpp +++ b/Widgets/public/Layout.hpp @@ -61,7 +61,7 @@ namespace tp { protected: Vec2 mSizePolicy = { SizePolicy::Fixed, SizePolicy::Fixed }; - Vec2F mMinSize = { 50, 50 }; + Vec2F mMinSize = { 30, 30 }; Vec2F mMaxSize = { FLT_MAX / 2, FLT_MAX / 2 }; private: diff --git a/Widgets/public/RootWidget.hpp b/Widgets/public/RootWidget.hpp index 75f5dbd..d66b1b0 100644 --- a/Widgets/public/RootWidget.hpp +++ b/Widgets/public/RootWidget.hpp @@ -18,8 +18,6 @@ namespace tp { void setRootWidget(Widget* widget); static void setWidgetArea(Widget& widget, const RectF& rect); - [[nodiscard]] bool isDebug() const; - // Graphic Application Interface public: void processFrame(EventHandler* events, const RectF& screenArea); diff --git a/Widgets/public/Widget.hpp b/Widgets/public/Widget.hpp index 6c3ca28..c44d718 100644 --- a/Widgets/public/Widget.hpp +++ b/Widgets/public/Widget.hpp @@ -59,6 +59,7 @@ namespace tp { void setSizePolicy(SizePolicy x, SizePolicy y); void setEnabled(bool val) { mFlags.set(ENABLED, val); } + [[nodiscard]] bool getEnabled() const { return mFlags.get(ENABLED); } WidgetLayout* getLayout(); [[nodiscard]] const WidgetLayout* getLayout() const; diff --git a/Widgets/public/WidgetApplication.hpp b/Widgets/public/WidgetApplication.hpp index 3548c6e..8f6c697 100644 --- a/Widgets/public/WidgetApplication.hpp +++ b/Widgets/public/WidgetApplication.hpp @@ -9,41 +9,24 @@ namespace tp { public: WidgetApplication() = default; - void setRoot(Widget* widget) { - mRootWidget.setRootWidget(widget); - } + void setRoot(Widget* widget); private: - void processFrame(EventHandler* eventHandler, halnf deltaTime) override { - const auto rec = RectF({ 0, 0 }, mWindow->getSize()); + void processFrame(EventHandler* eventHandler, halnf deltaTime) override; - Timer timer; - mRootWidget.processFrame(eventHandler, rec); - mProcTime = timer.timePassed(); - } - - void drawFrame(Canvas* canvas) override { - Timer timer; - mRootWidget.drawFrame(*canvas); - mDrawTime = timer.timePassed(); - - if (mRootWidget.isDebug()) { - drawDebug(); - debugUI(); - } - } - - bool forceNewFrame() override { - return mRootWidget.needsUpdate(); - } + void drawFrame(Canvas* canvas) override; + bool forceNewFrame() override ; private: void debugUI(); private: - time_ms mProcTime = 0; - time_ms mDrawTime = 0; + halnf mDebugSplitFactor = 0.7; RootWidget mRootWidget; + + private: + RectF mGuiArea; + RectF mDebugArea; }; } \ No newline at end of file diff --git a/Widgets/public/layouts/DockLayout.hpp b/Widgets/public/layouts/DockLayout.hpp index e5b49ab..47de346 100644 --- a/Widgets/public/layouts/DockLayout.hpp +++ b/Widgets/public/layouts/DockLayout.hpp @@ -96,7 +96,7 @@ namespace tp { bool mResizing = false; Widget* mCenterWidget = nullptr; - RectF mCenterArea {}; + RectF mCenterArea { 0, 0, 10, 10 }; // Parameters const halnf mHandleSplitFactor = 0.3; diff --git a/Widgets/public/layouts/OverlayLayout.hpp b/Widgets/public/layouts/SimpleLayouts.hpp similarity index 51% rename from Widgets/public/layouts/OverlayLayout.hpp rename to Widgets/public/layouts/SimpleLayouts.hpp index e674327..2a16ae1 100644 --- a/Widgets/public/layouts/OverlayLayout.hpp +++ b/Widgets/public/layouts/SimpleLayouts.hpp @@ -15,4 +15,19 @@ namespace tp { void pickRect(bool vertical) override {} void clampRect() override {} }; + + class ToolBarLayout : public WidgetLayout { + friend class DebugManager; + + public: + explicit ToolBarLayout(Widget* widget) : WidgetLayout(widget) {} + + void updateLayout(bool vertical) override; + + void pickRect(bool vertical) override {} + void clampRect() override {} + + private: + halnf mToolBarHeight = 55; + }; } \ No newline at end of file diff --git a/Widgets/public/mangers/DebugManager.hpp b/Widgets/public/mangers/DebugManager.hpp index 938fd6f..0b4ce92 100644 --- a/Widgets/public/mangers/DebugManager.hpp +++ b/Widgets/public/mangers/DebugManager.hpp @@ -5,12 +5,56 @@ #include namespace tp { + class DebugTimeline { + enum { + STEP = 10, + LEN = 200, + }; + + public: + DebugTimeline() = default; + + void addSample(time_ms time) { + samples[end] = time; + start++; + end++; + shift(); + } + + [[nodiscard]] int size() const { + return LEN - STEP; + } + + [[nodiscard]] const time_ms* get() const { + return samples + start; + } + + private: + void shift() { + if (end >= LEN) { + for (ualni i = STEP; i < LEN; i++) { + samples[i - STEP] = samples[i]; + } + start = 0; + end = LEN - STEP; + } + } + + private: + ualni start = 0; + ualni end = LEN - STEP; + + time_ms samples[LEN + 1] {}; + }; + class DebugManager { public: DebugManager() = default; - bool isRedrawAlways() const { return mDebugRedrawAlways; } - bool update(RootWidget* rootWidget, EventHandler& events); + [[nodiscard]] bool isFrozen() const { return mDebugStopProcessing; } + [[nodiscard]] bool isRedrawAlways() const { return mDebugRedrawAlways; } + + void update(RootWidget* rootWidget, EventHandler& events); void drawDebug(RootWidget* rootWidget, Canvas& canvas); void checkProcBreakPoints(Widget* widget) { @@ -32,6 +76,7 @@ namespace tp { private: void recursiveDraw(Canvas& canvas, Widget* active, const Vec2F& pos, int depthOrder); + void drawPerformance(); static void widgetMenu(Widget*); void drawLayoutOrder(); @@ -42,10 +87,17 @@ namespace tp { bool mDebug = false; bool mDebugStopProcessing = false; bool mDebugRedrawAlways = false; - bool mSlowMotion = false; + bool mDetailed = false; std::set mProcBreakpoints; std::set mLayBreakpoints; + + public: + DebugTimeline mProcTime; + DebugTimeline mUpdManager; + DebugTimeline mLayManager; + + DebugTimeline mDrawTime; }; extern DebugManager gDebugWidget; diff --git a/Widgets/public/widgets/ColorPickerWidget.hpp b/Widgets/public/widgets/ColorPickerWidget.hpp new file mode 100644 index 0000000..e9d592c --- /dev/null +++ b/Widgets/public/widgets/ColorPickerWidget.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "Widget.hpp" + +namespace tp { + class RGBPickerWidget : public Widget { + public: + RGBPickerWidget() = default; + + void process(const EventHandler& events) override; + void draw(Canvas& canvas) override; + + [[nodiscard]] bool processesEvents() const override { return true; } + + public: + Canvas::ColorWheel mColorWheel; + }; +} \ No newline at end of file diff --git a/Widgets/public/widgets/DockWidget.hpp b/Widgets/public/widgets/DockWidget.hpp index 969f0fe..0e07f2f 100644 --- a/Widgets/public/widgets/DockWidget.hpp +++ b/Widgets/public/widgets/DockWidget.hpp @@ -17,6 +17,8 @@ namespace tp { void drawSide(DockLayout::Side side, Canvas& canvas); public: + DockLayout::Side getSide(Widget* widget) { return layout()->getSideFromWidget(widget); } + void setCenterWidget(Widget* widget); void dockWidget(Widget* widget, DockLayout::Side side); diff --git a/Widgets/public/widgets/ScrollableWidget.hpp b/Widgets/public/widgets/ScrollableWidget.hpp index 13026ca..1903df2 100644 --- a/Widgets/public/widgets/ScrollableWidget.hpp +++ b/Widgets/public/widgets/ScrollableWidget.hpp @@ -20,6 +20,8 @@ namespace tp { [[nodiscard]] bool getDirection() const { return mVertical; } [[nodiscard]] halnf getPosFactor() const { return mPosFactor - mSizeFactor / 2; } + void setDirection(bool direction) { mVertical = direction; } + private: [[nodiscard]] const RectF& getHandleRect() const; void updateHandleRect(); @@ -53,5 +55,13 @@ namespace tp { class ScrollableWidget : public Widget { public: ScrollableWidget(); + + void setDirection(bool direction); + + Widget* getContainer() { return &mContent; } + + private: + Widget mContent; + ScrollableBarWidget mScroller; }; } \ No newline at end of file diff --git a/Widgets/public/widgets/SimpleWidgets.hpp b/Widgets/public/widgets/SimpleWidgets.hpp index b67ba75..b1036cc 100644 --- a/Widgets/public/widgets/SimpleWidgets.hpp +++ b/Widgets/public/widgets/SimpleWidgets.hpp @@ -71,7 +71,7 @@ namespace tp { void draw(Canvas& canvas) override; [[nodiscard]] bool processesEvents() const override { return true; } - [[nodiscard]] bool needsNextFrame() const override { return mState != SLIDING; } + [[nodiscard]] bool needsNextFrame() const override { return mState != IDLE; } [[nodiscard]] halnf val() const { return mFactor; } @@ -86,9 +86,55 @@ namespace tp { halnf mHandleSize = 20; halnf mRounding = 5; - RGBA mColorActive = { 0.5f, 0.5f, 0.5f, 1.f }; - RGBA mColorHovered = { 0.3f, 0.3f, 0.3f, 1.f }; - RGBA mColorIdle = { 0.2f, 0.2f, 0.2f, 1.f }; - RGBA mColorBG = { 0.08f, 0.08f, 0.08f, 1.0f }; + RGBA mColorActive = { 0.99f, 0.99f, 0.99f, 1.f }; + RGBA mColorHovered = { 0.9f, 0.9f, 0.9f, 1.f }; + RGBA mColorIdle = { 0.8f, 0.8f, 0.8f, 1.f }; + RGBA mColorBG = { 0.3f, 0.3f, 0.3f, 1.0f }; + }; + + class PopupWidget : public Widget { + public: + PopupWidget() = default; + + public: + void process(const EventHandler& events) override; + void draw(Canvas& canvas) override; + + void open(Widget* parent, const RectF& at); + [[nodiscard]] bool processesEvents() const override { return true; } + + private: + RectF mParentArea {}; + RGBA col = RGBA(0.03f, 0.03f, 0.03f, 0.9f); + halnf rounding = 5; + halnf borders = 2; + }; + + class HoverPopupTriggerWidget : public LabelWidget { + public: + enum ExpandDirection { + Right, + Bottom, + }; + + public: + HoverPopupTriggerWidget() = default; + + [[nodiscard]] bool processesEvents() const override { return true; } + void mouseEnter() override; + + void draw(Canvas& canvas) override; + + PopupWidget* getPopup(); + void setDirection(ExpandDirection dir) { mDirection = dir; } + + private: + PopupWidget mPopup; + + private: + ExpandDirection mDirection = Bottom; + RGBA col = RGBA(0.03f, 0.03f, 0.03f, 0.07f); + halnf rounding = 5; + halnf gap = 7; }; } \ No newline at end of file