From 55b15e0ec1eb7e8b0ae2d291a5a5fb3040a47e9f Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sat, 6 Sep 2025 19:06:30 +0300 Subject: [PATCH] Started work on grid creation menu --- CMakeLists.txt | 1 + data/icons/grid.svg | 79 +++++++++++++++ data/icons/trash.svg | 90 +++++++++++++++++ src/DrawData.hpp | 1 + src/DrawingProgram/ScreenshotTool.cpp | 22 +++-- src/GUIStuff/Elements/TextBox.hpp | 20 +++- src/GUIStuff/GUIManager.cpp | 8 +- src/GUIStuff/GUIManager.hpp | 8 +- src/GridManager.cpp | 134 +++++-------------------- src/GridManager.hpp | 43 +++----- src/MainProgram.cpp | 4 +- src/MainProgram.hpp | 2 - src/Toolbar.cpp | 111 ++++++++++++++++++--- src/Toolbar.hpp | 7 ++ src/World.cpp | 17 ++-- src/World.hpp | 4 + src/WorldGrid.cpp | 137 ++++++++++++++++++++++++++ src/WorldGrid.hpp | 26 +++++ 18 files changed, 533 insertions(+), 181 deletions(-) create mode 100644 data/icons/grid.svg create mode 100644 data/icons/trash.svg create mode 100644 src/WorldGrid.cpp create mode 100644 src/WorldGrid.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 59dfe79..3ddd7de 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ set(sources "src/main.cpp" "src/DrawData.cpp" "src/FontData.cpp" "src/GridManager.cpp" + "src/WorldGrid.cpp" "src/TimePoint.cpp" "src/ConnectionManager.cpp" "src/InputManager.cpp" diff --git a/data/icons/grid.svg b/data/icons/grid.svg new file mode 100644 index 0000000..759bbf9 --- /dev/null +++ b/data/icons/grid.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + diff --git a/data/icons/trash.svg b/data/icons/trash.svg new file mode 100644 index 0000000..90afd2e --- /dev/null +++ b/data/icons/trash.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/DrawData.hpp b/src/DrawData.hpp index 631154b..9d4fce1 100644 --- a/src/DrawData.hpp +++ b/src/DrawData.hpp @@ -14,6 +14,7 @@ struct DrawData { MainProgram* main; bool dontUseDrawProgCache = false; bool clampDrawBetween = true; + bool drawGrids = true; WorldScalar clampDrawMinimum; WorldScalar clampDrawMaximum; WorldScalar mipMapLevelOne; diff --git a/src/DrawingProgram/ScreenshotTool.cpp b/src/DrawingProgram/ScreenshotTool.cpp index dd1b1a8..3bb7e41 100644 --- a/src/DrawingProgram/ScreenshotTool.cpp +++ b/src/DrawingProgram/ScreenshotTool.cpp @@ -203,6 +203,15 @@ void ScreenshotTool::take_screenshot(const std::filesystem::path& filePath) { SkRect canvasBounds = SkRect::MakeLTRB(0.0f, 0.0f, canvasSize.x(), canvasSize.y()); std::unique_ptr canvas = SkSVGCanvas::Make(canvasBounds, &out, SkSVGCanvas::kConvertTextToPaths_Flag | SkSVGCanvas::kNoPrettyXML_Flag); take_screenshot_svg(canvas.get(), controls.transparentBackground); + canvas = nullptr; // Ensure that SVG is completely written into the stream + #ifdef __EMSCRIPTEN__ + auto skData = out.detachAsData(); + emscripten_browser_file::download( + "screenshot.svg", + "image/svg+xml", + std::string_view((const char*)skData->bytes(), skData->size()) + ); + #endif } } @@ -232,16 +241,16 @@ void ScreenshotTool::take_screenshot_svg(SkCanvas* canvas, bool transparentBackg drawP.world.drawData.cam.set_viewing_area(canvasSize); drawP.world.drawData.refresh_draw_optimizing_values(); - GridManager::GridType oldGridType = drawP.world.main.grid.gridType; + bool oldDrawGrids = drawP.world.drawData.drawGrids; drawP.world.main.takingScreenshot = true; drawP.world.main.transparentBackground = transparentBackground; - drawP.world.main.grid.gridType = GridManager::GRIDTYPE_NONE; + drawP.world.drawData.drawGrids = false; drawP.world.drawData.dontUseDrawProgCache = true; drawP.world.main.draw(canvas); drawP.world.main.takingScreenshot = false; drawP.world.main.transparentBackground = false; - drawP.world.main.grid.gridType = oldGridType; + drawP.world.drawData.drawGrids = oldDrawGrids; drawP.world.drawData = d; } @@ -271,17 +280,16 @@ void ScreenshotTool::take_screenshot_area_hw(const sk_sp& surface, Sk drawP.world.drawData.cam.set_viewing_area(sectionImageSize.cast()); drawP.world.drawData.refresh_draw_optimizing_values(); - GridManager::GridType oldGridType = drawP.world.main.grid.gridType; + bool oldDrawGrids = drawP.world.drawData.drawGrids; drawP.world.main.takingScreenshot = true; drawP.world.main.transparentBackground = transparentBackground; - if(!controls.displayGrid) - drawP.world.main.grid.gridType = GridManager::GRIDTYPE_NONE; + drawP.world.drawData.drawGrids = controls.displayGrid; drawP.world.drawData.dontUseDrawProgCache = true; drawP.world.main.draw(canvas); drawP.world.main.takingScreenshot = false; drawP.world.main.transparentBackground = false; - drawP.world.main.grid.gridType = oldGridType; + drawP.world.drawData.drawGrids = oldDrawGrids; drawP.world.drawData = d; SkImageInfo aaImgInfo = SkImageInfo::Make(sectionImageSize.x(), sectionImageSize.y(), kRGBA_8888_SkColorType, kPremul_SkAlphaType); diff --git a/src/GUIStuff/Elements/TextBox.hpp b/src/GUIStuff/Elements/TextBox.hpp index 061a416..f7094c0 100644 --- a/src/GUIStuff/Elements/TextBox.hpp +++ b/src/GUIStuff/Elements/TextBox.hpp @@ -6,7 +6,7 @@ namespace GUIStuff { template class TextBox : public Element { public: - void update(UpdateInputData& io, T* newData, const std::function(const std::string&)>& newFromStr, const std::function newToStr, bool newSingleLine, const std::function& elemUpdate) { + void update(UpdateInputData& io, T* newData, const std::function(const std::string&)>& newFromStr, const std::function newToStr, bool newSingleLine, bool updateEveryEdit, const std::function& elemUpdate) { data = newData; fromStr = newFromStr; toStr = newToStr; @@ -18,7 +18,7 @@ template class TextBox : public Element { CLAY({ .layout = { - .sizing = {.width = CLAY_SIZING_GROW(static_cast(io.fontSize * 2)), .height = CLAY_SIZING_GROW(0)} + .sizing = {.width = CLAY_SIZING_GROW(static_cast(io.fontSize * 2)), .height = CLAY_SIZING_GROW(static_cast(io.fontSize))} }, .custom = { .customData = this } }) { @@ -59,12 +59,14 @@ template class TextBox : public Element { cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); else cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kLeft, cur.pos)); + update_on_edit(updateEveryEdit); } if(io.key.del) { if(cur.selectionBeginPos != cur.selectionEndPos) cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); else cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kRight, cur.pos)); + update_on_edit(updateEveryEdit); } if(io.key.paste) { if(cur.selectionBeginPos != cur.selectionEndPos) @@ -76,6 +78,7 @@ template class TextBox : public Element { } else cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, io.clipboard.textInFunc()); + update_on_edit(updateEveryEdit); } if(io.key.enter) { if(singleLine) { @@ -91,6 +94,7 @@ template class TextBox : public Element { cur.pos.fParagraphIndex++; cur.pos.fTextByteIndex = 0; cur.selectionBeginPos = cur.selectionEndPos = cur.pos; + update_on_edit(updateEveryEdit); } } if(io.key.copy) { @@ -100,6 +104,7 @@ template class TextBox : public Element { io.clipboard.textOut = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos); if(cur.selectionBeginPos != cur.selectionEndPos) cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); + update_on_edit(updateEveryEdit); } if(io.key.selectAll) { cur.selectionEndPos.fParagraphIndex = textbox.lineCount() == 0 ? 0 : textbox.lineCount() - 1; @@ -112,6 +117,7 @@ template class TextBox : public Element { if(cur.selectionBeginPos != cur.selectionEndPos) cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos); cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, io.textInput); + update_on_edit(updateEveryEdit); } io.acceptingTextInput = true; @@ -178,6 +184,16 @@ template class TextBox : public Element { //oldData = T(); } + void update_on_edit(bool updateEveryEdit) { + if(updateEveryEdit) { + std::optional dataToAssign = fromStr(textbox.get_string()); + if(dataToAssign) { + *data = dataToAssign.value(); + oldData = *data; + } + } + } + T* data = nullptr; T oldData; diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index dfbfad4..f410965 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -317,7 +317,7 @@ void GUIManager::input_color_component_255(const std::string& id, float* val, co std::stringstream ss; ss << convertTo255; return ss.str(); - }, true, elemUpdate); + }, true, false, elemUpdate); pop_id(); } @@ -329,7 +329,7 @@ void GUIManager::input_text(const std::string& id, std::string* val, const std:: }, [&](const std::string& str) { return str; - }, true, elemUpdate); + }, true, true, elemUpdate); pop_id(); } @@ -414,7 +414,7 @@ void GUIManager::input_scalar(const std::string& id, uint8_t* val, uint8_t min, }, [&](const uint8_t& a) { return std::to_string(static_cast(a)); - }, true, elemUpdate); + }, true, false, elemUpdate); pop_id(); } @@ -429,7 +429,7 @@ void GUIManager::input_path(const std::string& id, std::filesystem::path* val, s }, [&](const std::filesystem::path& a) { return a.string(); - }, true, elemUpdate); + }, true, false, elemUpdate); pop_id(); } diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index 0aef6f6..d05360a 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -107,9 +107,9 @@ class GUIManager { return toRet; } - template void input_generic(const std::string& id, T* val, const std::function(const std::string&)>& fromStr, const std::function& toStr, bool singleLine, const std::function& elemUpdate = nullptr) { + template void input_generic(const std::string& id, T* val, const std::function(const std::string&)>& fromStr, const std::function& toStr, bool singleLine, bool updateEveryEdit, const std::function& elemUpdate = nullptr) { push_id(id); - insert_element>()->update(*io, val, fromStr, toStr, singleLine, elemUpdate); + insert_element>()->update(*io, val, fromStr, toStr, singleLine, updateEveryEdit, elemUpdate); pop_id(); } @@ -135,7 +135,7 @@ class GUIManager { else ss << *val; return ss.str(); - }, true, elemUpdate); + }, true, false, elemUpdate); } void input_scalar(const std::string& id, uint8_t* val, uint8_t min, uint8_t max, int decimalPrecision, const std::function& elemUpdate); @@ -184,7 +184,7 @@ class GUIManager { } return ss.str(); }, - true, elemUpdate); + true, false, elemUpdate); } template void color_picker(const std::string& id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { diff --git a/src/GridManager.cpp b/src/GridManager.cpp index eefc11a..5a9fd30 100644 --- a/src/GridManager.cpp +++ b/src/GridManager.cpp @@ -1,118 +1,34 @@ #include "GridManager.hpp" -#include "MainProgram.hpp" -#include -#include -#include -#define GRID_CACHE_SIZE 10.0f -#define GRID_CACHE_SIZE_INT 10 -#define GRID_DOT_SIZE 0.5f +GridManager::GridManager(World& w): + world(w) {} -// NOTE: Skia shaders return premultiplied alpha colors - -const char* squareDotGridSkSl = -R"V( -uniform vec4 pointColor; - -vec4 main(float2 fragCoord) { - float m1 = step(mod(fragCoord.x + 0.25, 10.0), 0.5); - float m2 = step(mod(fragCoord.y + 0.25, 10.0), 0.5); - float fin = m1 * m2; - - vec4 pointColorUnmultiplied = vec4(pointColor.rgb, 1.0); - - return pointColorUnmultiplied * pointColor.a * fin; -})V"; - -const char* circleDotGridSkSl = -R"V( -uniform vec4 pointColor; - -vec4 main(float2 fragCoord) { - float2 modCoord = mod(fragCoord + float2(5.0), float2(10.0)); - float fin = step(distance(float2(5.0), modCoord), 0.25); - - vec4 pointColorUnmultiplied = vec4(pointColor.rgb, 1.0); - - return pointColorUnmultiplied * pointColor.a * fin; -})V"; - -GridManager::GridManager(MainProgram& initMain): - main(initMain) -{ - auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(circleDotGridSkSl)); - if(!err.isEmpty()) { - std::cout << "[GridManager::GridManager] Circle dot shader construction error\n"; - std::cout << err.c_str() << std::endl; - } - circleDotEffect = effect; - - auto [effect2, err2] = SkRuntimeEffect::MakeForShader(SkString(squareDotGridSkSl)); - if(!err2.isEmpty()) { - std::cout << "[GridManager::GridManager] Square dot shader construction error\n"; - std::cout << err2.c_str() << std::endl; - } - squareDotEffect = effect2; +void GridManager::init_client_callbacks() { } -sk_sp GridManager::get_shader(const sk_sp& runtimeEffect, const SkColor4f& pointColor) { - SkRuntimeShaderBuilder builder(runtimeEffect); - builder.uniform("pointColor") = pointColor; - sk_sp s = builder.makeShader(); - return s; +void GridManager::add_grid(const std::string& name) { + WorldGrid g; + grids[name] = g; + changed = true; +} + +void GridManager::remove_grid(const std::string& name) { + grids.erase(name); + changed = true; +} + +const std::vector& GridManager::sorted_names() { + if(changed) { + sortedNames.clear(); + for(auto& [k, v] : grids) + sortedNames.emplace_back(k); + std::sort(sortedNames.begin(), sortedNames.end()); + changed = false; + } + return sortedNames; } void GridManager::draw(SkCanvas* canvas, const DrawData& drawData) { - if(gridType == GRIDTYPE_NONE) - return; - if(oldWindowSize != main.window.size.cast()) { - gridPath = SkPath(); - float maxDim = std::max(main.window.size.x(), main.window.size.y()) * 2.0f; - for(float i = 0.0f; i < maxDim; i += GRID_CACHE_SIZE) { - gridPath.moveTo(i, -maxDim); - gridPath.lineTo(i, maxDim); - gridPath.moveTo(-maxDim, i); - gridPath.lineTo(maxDim, i); - } - for(float i = 0.0f; i > -maxDim; i -= GRID_CACHE_SIZE) { - gridPath.moveTo(i, -maxDim); - gridPath.lineTo(i, maxDim); - gridPath.moveTo(-maxDim, i); - gridPath.lineTo(maxDim, i); - } - oldWindowSize = main.window.size.cast(); - } - float alteredZoom = static_cast(((size / WorldScalar(GRID_CACHE_SIZE_INT)) / drawData.cam.c.inverseScale)); - if(alteredZoom > 0.5f) { - Vector2f fracCamPos; - - fracCamPos.x() = static_cast((drawData.cam.c.pos.x() % size) / drawData.cam.c.inverseScale); - fracCamPos.y() = static_cast((drawData.cam.c.pos.y() % size) / drawData.cam.c.inverseScale); - - SkPaint linePaint; - if(gridType == GRIDTYPE_CIRCLEDOT || gridType == GRIDTYPE_SQUAREDOT) { - SkColor4f pointColor = main.world->canvasTheme.toolFrontColor; - pointColor.fA = std::clamp(alteredZoom / 20.0f, 0.0f, 0.4f); - linePaint.setShader(get_shader(gridType == 1 ? circleDotEffect : squareDotEffect, pointColor)); - } - else if(gridType == GRIDTYPE_LINE) { - linePaint.setColor4f(main.world->canvasTheme.toolFrontColor); - linePaint.setStyle(SkPaint::kStroke_Style); - linePaint.setStrokeWidth(0.0f); - linePaint.setAlphaf(std::clamp(alteredZoom / 35.0f, 0.0f, 0.3f)); - } - - canvas->save(); - - canvas->rotate(-drawData.cam.c.rotation * 180.0 / std::numbers::pi); - canvas->translate(-fracCamPos.x(), -fracCamPos.y()); - canvas->scale(alteredZoom, alteredZoom); - - if(gridType == GRIDTYPE_CIRCLEDOT || gridType == GRIDTYPE_SQUAREDOT) - canvas->drawPaint(linePaint); - else if(gridType == GRIDTYPE_LINE) - canvas->drawPath(gridPath, linePaint); - - canvas->restore(); - } + for(auto& [str, g] : grids) + g.draw(*this, canvas, drawData); } diff --git a/src/GridManager.hpp b/src/GridManager.hpp index f12b3e1..e40321d 100644 --- a/src/GridManager.hpp +++ b/src/GridManager.hpp @@ -1,41 +1,20 @@ #pragma once -#include -#include -#include "DrawData.hpp" -#include -#include -#include +#include "WorldGrid.hpp" -using namespace Eigen; - -class MainProgram; +class World; class GridManager { public: - GridManager(MainProgram& initMain); + GridManager(World& w); + void init_client_callbacks(); + void add_grid(const std::string& name); + void remove_grid(const std::string& name); void draw(SkCanvas* canvas, const DrawData& drawData); + const std::vector& sorted_names(); - enum GridType : unsigned { - GRIDTYPE_NONE = 0, - GRIDTYPE_CIRCLEDOT, - GRIDTYPE_SQUAREDOT, - GRIDTYPE_LINE - }; - - GridType gridType = GRIDTYPE_CIRCLEDOT; + std::unordered_map grids; + World& world; private: - sk_sp get_shader(const sk_sp& runtimeEffect, const SkColor4f& pointColor); - Vector2f oldWindowSize{0.0f, 0.0f}; - WorldScalar size{50000000}; - SkPath gridPath; - sk_sp circleDotEffect; - sk_sp squareDotEffect; - MainProgram& main; + bool changed = true; + std::vector sortedNames; }; - -NLOHMANN_JSON_SERIALIZE_ENUM(GridManager::GridType, { - {GridManager::GRIDTYPE_NONE, "None"}, - {GridManager::GRIDTYPE_CIRCLEDOT, "Circle Dot"}, - {GridManager::GRIDTYPE_SQUAREDOT, "Square Dot"}, - {GridManager::GRIDTYPE_LINE, "Line"} -}) diff --git a/src/MainProgram.cpp b/src/MainProgram.cpp index 54dedac..3a0b7b4 100755 --- a/src/MainProgram.cpp +++ b/src/MainProgram.cpp @@ -36,8 +36,7 @@ #include MainProgram::MainProgram(): - toolbar(*this), - grid(*this) + toolbar(*this) { displayName = Random::get().alphanumeric_str(10); @@ -229,7 +228,6 @@ void MainProgram::load_config() { void MainProgram::draw(SkCanvas* canvas) { canvas->clear(transparentBackground ? SkColor4f{0.0f, 0.0f, 0.0f, 0.0f} : world->canvasTheme.backColor); - grid.draw(canvas, world->drawData); world->draw(canvas); toolbar.draw(canvas); } diff --git a/src/MainProgram.hpp b/src/MainProgram.hpp index 3f5833f..a933343 100644 --- a/src/MainProgram.hpp +++ b/src/MainProgram.hpp @@ -1,6 +1,5 @@ #pragma once #include "DrawData.hpp" -#include "GridManager.hpp" #include #include #include "InputManager.hpp" @@ -66,7 +65,6 @@ class MainProgram { FontData fonts; TimePoint deltaTime; Toolbar toolbar; - GridManager grid; std::shared_ptr world; std::ofstream* logFile; diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index 51cc84c..c20fe20 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -4,7 +4,6 @@ #include "MainProgram.hpp" #include "GUIStuff/Elements/Element.hpp" #include "GUIStuff/GUIManager.hpp" -#include "GridManager.hpp" #include "InputManager.hpp" #include "World.hpp" #include @@ -212,7 +211,6 @@ nlohmann::json Toolbar::get_config_json() { toRet["jumpTransitionTime"] = jumpTransitionTime; toRet["dragZoomSpeed"] = dragZoomSpeed; toRet["scrollZoomSpeed"] = scrollZoomSpeed; - toRet["gridType"] = main.grid.gridType; toRet["guiFontSize"] = io->fontSize; toRet["showPerformance"] = showPerformance; toRet["displayName"] = main.displayName; @@ -263,7 +261,6 @@ void Toolbar::set_config_json(const nlohmann::json& j) { j.at("dragZoomSpeed").get_to(dragZoomSpeed); j.at("scrollZoomSpeed").get_to(scrollZoomSpeed); j.at("guiScale").get_to(guiScale); - j.at("gridType").get_to(main.grid.gridType); j.at("guiFontSize").get_to(io->fontSize); j.at("showPerformance").get_to(showPerformance); j.at("useNativeFilePicker").get_to(useNativeFilePicker); @@ -390,6 +387,7 @@ void Toolbar::top_toolbar() { global_log(); bool menuPopUpJustOpen = false; bool bookmarkMenuPopUpJustOpen = false; + bool gridMenuPopUpJustOpen = false; if(gui.svg_icon_button("Main Menu Button", "data/icons/menu.svg", menuPopUpOpen)) { menuPopUpOpen = true; menuPopUpJustOpen = true; @@ -401,6 +399,14 @@ void Toolbar::top_toolbar() { gui.tab_list("file tab list", tabNames, main.worldIndex, closedTab); if(main.world->network_being_used() && gui.svg_icon_button("Player List Toggle Button", "data/icons/list.svg", playerMenuOpen)) playerMenuOpen = !playerMenuOpen; + if(gui.svg_icon_button("Grids Button", "data/icons/grid.svg", gridMenu.popupOpen)) { + if(gridMenu.popupOpen) + gridMenu.popupOpen = false; + else { + gridMenu.popupOpen = true; + gridMenuPopUpJustOpen = true; + } + } if(gui.svg_icon_button("Menu Undo Button", "data/icons/undo.svg")) main.world->undo_with_checks(); if(gui.svg_icon_button("Menu Redo Button", "data/icons/redo.svg")) @@ -417,6 +423,8 @@ void Toolbar::top_toolbar() { if(closedTab) main.set_tab_to_close(closedTab.value()); + if(gridMenu.popupOpen) + grid_menu(gridMenuPopUpJustOpen); if(bookMenu.popupOpen) bookmark_menu(bookmarkMenuPopUpJustOpen); if(menuPopUpOpen) { @@ -532,6 +540,87 @@ If you like this app, consider downloading the native version for your system)") } } +void Toolbar::grid_menu(bool justOpened) { + gui.push_id("grid menu"); + CLAY({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) }, + .padding = CLAY_PADDING_ALL(io->theme->padding1), + .childGap = io->theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = convert_vec4(io->theme->backColor1), + .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), + .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT}, + .border = {.color = convert_vec4(io->theme->backColor2), .width = CLAY_BORDER_OUTSIDE(io->theme->windowBorders1)} + }) { + gui.obstructing_window(); + gui.text_label_centered("Grids"); + float entryHeight = 25.0f; + if(main.world->gridMan.sorted_names().empty()) + gui.text_label_centered("No grids yet..."); + gui.scroll_bar_many_entries_area("grid menu entries", entryHeight, main.world->gridMan.sorted_names().size(), [&](size_t i, bool isListHovered) { + const std::string& gridName = main.world->gridMan.sorted_names()[i]; + bool selectedEntry = gridName == gridMenu.currentSelectedGrid; + CLAY({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(entryHeight)}, + .childGap = 2, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + .backgroundColor = selectedEntry ? convert_vec4(io->theme->backColor1) : convert_vec4(io->theme->backColor2) + }) { + gui.text_label(gridName); + if(Clay_Hovered() && io->mouse.leftClick && isListHovered) { + gridMenu.currentSelectedGrid = gridName; + gridMenu.newName = gridName; + } + } + }); + bool gridExists = std::find(main.world->gridMan.sorted_names().begin(), main.world->gridMan.sorted_names().end(), gridMenu.newName) != main.world->gridMan.sorted_names().end(); + gui.left_to_right_line_layout([&]() { + gui.input_text("grid text input", &gridMenu.newName); + if(gridExists) { + if(gui.svg_icon_button("grid remove button", "data/icons/trash.svg", false, 25.0f)) { + main.world->gridMan.remove_grid(gridMenu.newName); + gridMenu.newName.clear(); + gridExists = false; + } + } + else { + if(gui.svg_icon_button("grid add button", "data/icons/plus.svg", false, 25.0f) && !gridMenu.newName.empty()) + main.world->gridMan.add_grid(gridMenu.newName); + } + }); + bool dropdownHover = false; + if(gridExists) { + auto gridFoundIt = main.world->gridMan.grids.find(gridMenu.newName); + if(gridFoundIt != main.world->gridMan.grids.end()) { + WorldGrid& g = gridFoundIt->second; + size_t typeSelected = static_cast(g.gridType); + std::vector listOfGridTypes = { + "Circle Points", + "Square Points", + "Square Lines", + "Ruled" + }; + gui.left_to_right_line_layout([&]() { + gui.text_label("Type"); + gui.dropdown_select("filepicker select type", &typeSelected, listOfGridTypes, 200.0f, [&]() { + dropdownHover = Clay_Hovered(); + }); + }); + g.gridType = static_cast(typeSelected); + } + } + if(io->mouse.leftClick && !Clay_Hovered() && !justOpened && !dropdownHover) + gridMenu.popupOpen = false; + } + gui.pop_id(); +} + void Toolbar::bookmark_menu(bool justOpened) { gui.push_id("bookmark menu"); CLAY({ @@ -575,19 +664,21 @@ void Toolbar::bookmark_menu(bool justOpened) { } } }); - bool serverExists = std::find(main.world->bMan.sorted_names().begin(), main.world->bMan.sorted_names().end(), bookMenu.newName) != main.world->bMan.sorted_names().end(); + bool bookmarkExists = std::find(main.world->bMan.sorted_names().begin(), main.world->bMan.sorted_names().end(), bookMenu.newName) != main.world->bMan.sorted_names().end(); gui.left_to_right_line_layout([&]() { gui.input_text("bookmark text input", &bookMenu.newName); - if(gui.svg_icon_button("bookmark add button", "data/icons/plus.svg", false, 25.0f) && !bookMenu.newName.empty()) - main.world->bMan.add_bookmark(bookMenu.newName); - if(serverExists) { + if(bookmarkExists) { if(gui.svg_icon_button("bookmark jump button", "data/icons/jump.svg", false, 25.0f)) main.world->bMan.jump_to_bookmark(bookMenu.newName); - if(gui.svg_icon_button("bookmark remove button", "data/icons/close.svg", false, 25.0f)) { + if(gui.svg_icon_button("bookmark remove button", "data/icons/trash.svg", false, 25.0f)) { main.world->bMan.remove_bookmark(bookMenu.newName); bookMenu.newName.clear(); } } + else { + if(gui.svg_icon_button("bookmark add button", "data/icons/plus.svg", false, 25.0f) && !bookMenu.newName.empty()) + main.world->bMan.add_bookmark(bookMenu.newName); + } }); if(io->mouse.leftClick && !Clay_Hovered() && !justOpened) bookMenu.popupOpen = false; @@ -1173,10 +1264,6 @@ void Toolbar::options_menu() { } case GSETTINGS_APPEARANCE: { gui.push_id("appearance settings"); - if(gui.radio_button_field("grid type no grid", "No Grid", (main.grid.gridType == GridManager::GRIDTYPE_NONE))) main.grid.gridType = GridManager::GRIDTYPE_NONE; - if(gui.radio_button_field("grid type circle grid", "Circle Dot Grid", (main.grid.gridType == GridManager::GRIDTYPE_CIRCLEDOT))) main.grid.gridType = GridManager::GRIDTYPE_CIRCLEDOT; - if(gui.radio_button_field("grid type square grid", "Square Dot Grid", (main.grid.gridType == GridManager::GRIDTYPE_SQUAREDOT))) main.grid.gridType = GridManager::GRIDTYPE_SQUAREDOT; - if(gui.radio_button_field("grid type line grid", "Line Grid", (main.grid.gridType == GridManager::GRIDTYPE_LINE))) main.grid.gridType = GridManager::GRIDTYPE_LINE; gui.input_scalar_field("GUI Scale", "GUI Scale", &guiScale, 0.5f, 3.0f, 1); gui.input_scalar_field("GUI Font Size", "GUI Font Size", &io->fontSize, 10, 30); gui.text_label("Note: Changing font size may break UI in some cases."); diff --git a/src/Toolbar.hpp b/src/Toolbar.hpp index fe69b56..d96e402 100644 --- a/src/Toolbar.hpp +++ b/src/Toolbar.hpp @@ -87,6 +87,7 @@ class Toolbar { void chat_box(); void global_log(); void top_toolbar(); + void grid_menu(bool justOpened); void bookmark_menu(bool justOpened); void drawing_program_gui(); void options_menu(); @@ -168,6 +169,12 @@ class Toolbar { std::string newName; } bookMenu; + struct GridMenu { + bool popupOpen = false; + std::string currentSelectedGrid; + std::string newName; + } gridMenu; + bool useNativeFilePicker = true; struct FilePicker { bool isOpen = false; diff --git a/src/World.cpp b/src/World.cpp index cf42740..e44df5d 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -24,7 +24,8 @@ World::World(MainProgram& initMain, OpenWorldInfo& worldInfo): main(initMain), rMan(*this), drawProg(*this), - bMan(*this) + bMan(*this), + gridMan(*this) { set_canvas_background_color(main.defaultCanvasBackgroundColor); displayName = main.displayName; @@ -360,11 +361,7 @@ WorldScalar World::calculate_zoom_from_uniform_zoom(WorldScalar uniformZoom, Wor return uniformZoom * ((a1 < a2) ? a1 : a2); } -void World::draw(SkCanvas* canvas) { - drawData.refresh_draw_optimizing_values(); - - drawProg.draw(canvas, drawData); - +void World::draw_other_player_cursors(SkCanvas* canvas, const DrawData& drawData) { for(auto& [id, data] : clients) { if(drawData.clampDrawMaximum > data.camCoords.inverseScale && drawData.clampDrawMinimum < data.camCoords.inverseScale) { canvas->save(); @@ -396,3 +393,11 @@ void World::draw(SkCanvas* canvas) { } } } + +void World::draw(SkCanvas* canvas) { + drawData.refresh_draw_optimizing_values(); + if(drawData.drawGrids) + gridMan.draw(canvas, drawData); + drawProg.draw(canvas, drawData); + draw_other_player_cursors(canvas, drawData); +} diff --git a/src/World.hpp b/src/World.hpp index 00cb7b2..de1c9f7 100644 --- a/src/World.hpp +++ b/src/World.hpp @@ -6,6 +6,7 @@ #include "DrawingProgram/DrawingProgram.hpp" #include "Toolbar.hpp" #include "SharedTypes.hpp" +#include "GridManager.hpp" #include class MainProgram; @@ -47,6 +48,7 @@ class World { DrawingProgram drawProg; BookmarkManager bMan; ConnectionManager con; + GridManager gridMan; std::deque chatMessages; @@ -101,6 +103,8 @@ class World { void init_client_callbacks(); void set_name(const std::string& n); + void draw_other_player_cursors(SkCanvas* canvas, const DrawData& drawData); + ClientPortionID nextClientID = 0; WorldVec mousePreviousWorldVec = {0, 0}; WorldVec mouseWorldMove = {0, 0}; diff --git a/src/WorldGrid.cpp b/src/WorldGrid.cpp new file mode 100644 index 0000000..5fdfc62 --- /dev/null +++ b/src/WorldGrid.cpp @@ -0,0 +1,137 @@ +#include "WorldGrid.hpp" +#include "GridManager.hpp" +#include "MainProgram.hpp" + +sk_sp WorldGrid::circlePointEffect; +sk_sp WorldGrid::squarePointEffect; +sk_sp WorldGrid::squareLinesEffect; +sk_sp WorldGrid::ruledEffect; +Vector2f WorldGrid::oldWindowSize = Vector2f{0.0f, 0.0f}; + +#define GRID_CACHE_SIZE 10.0f +#define GRID_CACHE_SIZE_INT 10 + +// NOTE: Skia shaders return premultiplied alpha colors + +const char* circlePointShaderCode = R"V( +uniform vec4 gridColor; +uniform float gridScale; + +vec4 main(float2 fragCoord) { + float circleRadius = min(5.0 / gridScale, 0.25); + float2 modCoord = mod(fragCoord + float2(5.0), float2(10.0)); + float fin = step(distance(float2(5.0), modCoord), circleRadius); + + vec4 pointColorUnmultiplied = vec4(gridColor.rgb, 1.0); + + return pointColorUnmultiplied * gridColor.a * fin; +})V"; + +const char* squarePointShaderCode = R"V( +uniform vec4 gridColor; +uniform float gridScale; + +vec4 main(float2 fragCoord) { + float squareSize = min(10.0 / gridScale, 0.5); + float gridUnit = 1.0 / gridScale; + float m1 = step(mod(fragCoord.x + gridUnit + squareSize * 0.5, 10.0), squareSize); + float m2 = step(mod(fragCoord.y + gridUnit + squareSize * 0.5, 10.0), squareSize); + float fin = m1 * m2; + + vec4 pointColorUnmultiplied = vec4(gridColor.rgb, 1.0); + + return pointColorUnmultiplied * gridColor.a * fin; +})V"; + +const char* squareLinesShaderCode = R"V( +uniform vec4 gridColor; +uniform float gridScale; + +vec4 main(float2 fragCoord) { + float gridUnit = 1.0 / gridScale; + float m1 = step(mod(fragCoord.x + gridUnit, 10.0), gridUnit); + float m2 = step(mod(fragCoord.y + gridUnit, 10.0), gridUnit); + float fin = max(m1, m2); + + vec4 pointColorUnmultiplied = vec4(gridColor.rgb, 1.0); + + return pointColorUnmultiplied * gridColor.a * fin; +})V"; + +const char* ruledShaderCode = R"V( +uniform vec4 gridColor; +uniform float gridScale; + +vec4 main(float2 fragCoord) { + float gridUnit = 1.0 / gridScale; + float fin = step(mod(fragCoord.y + gridUnit, 10.0), gridUnit); + + vec4 pointColorUnmultiplied = vec4(gridColor.rgb, 1.0); + + return pointColorUnmultiplied * gridColor.a * fin; +})V"; + +sk_sp WorldGrid::compile_effect_shader_init(const char* shaderName, const char* shaderCode) { + auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(shaderCode)); + if(!err.isEmpty()) { + std::cout << "[GridManager::compile_effect_shader_init] " << shaderName << " shader construction error\n"; + std::cout << err.c_str() << std::endl; + throw std::runtime_error("Shader Compile Failure"); + } + return effect; +} + +sk_sp WorldGrid::get_shader(GridType gType, const SkColor4f& gridColor, float gridScale) { + sk_sp runtimeEffect; + switch(gType) { + case GridType::CIRCLE_POINTS: + runtimeEffect = circlePointEffect; + break; + case GridType::SQUARE_POINTS: + runtimeEffect = squarePointEffect; + break; + case GridType::SQUARE_LINES: + runtimeEffect = squareLinesEffect; + break; + case GridType::RULED: + runtimeEffect = ruledEffect; + break; + } + SkRuntimeShaderBuilder builder(runtimeEffect); + builder.uniform("gridColor") = gridColor; + builder.uniform("gridScale") = gridScale; + sk_sp s = builder.makeShader(); + return s; +} + +void WorldGrid::draw(GridManager& gMan, SkCanvas* canvas, const DrawData& drawData) { + if(!circlePointEffect) { + circlePointEffect = compile_effect_shader_init("Circle Point", circlePointShaderCode); + squarePointEffect = compile_effect_shader_init("Square Point", squarePointShaderCode); + squareLinesEffect = compile_effect_shader_init("Square Lines", squareLinesShaderCode); + ruledEffect = compile_effect_shader_init("Ruled", ruledShaderCode); + } + + float alteredZoom = static_cast(((size / WorldScalar(GRID_CACHE_SIZE_INT)) / drawData.cam.c.inverseScale)); + if(alteredZoom > 0.5f) { + Vector2f fracCamPos; + + fracCamPos.x() = static_cast((drawData.cam.c.pos.x() % size) / drawData.cam.c.inverseScale); + fracCamPos.y() = static_cast((drawData.cam.c.pos.y() % size) / drawData.cam.c.inverseScale); + + SkPaint linePaint; + SkColor4f pointColor = gMan.world.canvasTheme.toolFrontColor; + pointColor.fA = std::clamp(alteredZoom / 20.0f, 0.0f, 0.4f); + linePaint.setShader(get_shader(gridType, pointColor, alteredZoom)); + + canvas->save(); + + canvas->rotate(-drawData.cam.c.rotation * 180.0 / std::numbers::pi); + canvas->translate(-fracCamPos.x(), -fracCamPos.y()); + canvas->scale(alteredZoom, alteredZoom); + + canvas->drawPaint(linePaint); + + canvas->restore(); + } +} diff --git a/src/WorldGrid.hpp b/src/WorldGrid.hpp new file mode 100644 index 0000000..938faf7 --- /dev/null +++ b/src/WorldGrid.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include "DrawData.hpp" +#include + +class GridManager; + +class WorldGrid { + public: + void draw(GridManager& gMan, SkCanvas* canvas, const DrawData& drawData); + enum class GridType : unsigned { + CIRCLE_POINTS = 0, + SQUARE_POINTS, + SQUARE_LINES, + RULED + } gridType = GridType::CIRCLE_POINTS; + WorldScalar size{50000000}; + private: + static sk_sp get_shader(GridType gType, const SkColor4f& gridColor, float gridScale); + static sk_sp compile_effect_shader_init(const char* shaderName, const char* shaderCode); + static Vector2f oldWindowSize; + static sk_sp circlePointEffect; + static sk_sp squarePointEffect; + static sk_sp squareLinesEffect; + static sk_sp ruledEffect; +};