Started work on grid creation menu

This commit is contained in:
Yousef Khadadeh 2025-09-06 19:06:30 +03:00
parent 57392cdfdd
commit 55b15e0ec1
18 changed files with 533 additions and 181 deletions

View file

@ -14,6 +14,7 @@ struct DrawData {
MainProgram* main;
bool dontUseDrawProgCache = false;
bool clampDrawBetween = true;
bool drawGrids = true;
WorldScalar clampDrawMinimum;
WorldScalar clampDrawMaximum;
WorldScalar mipMapLevelOne;

View file

@ -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<SkCanvas> 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<SkSurface>& surface, Sk
drawP.world.drawData.cam.set_viewing_area(sectionImageSize.cast<float>());
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);

View file

@ -6,7 +6,7 @@ namespace GUIStuff {
template <typename T> class TextBox : public Element {
public:
void update(UpdateInputData& io, T* newData, const std::function<std::optional<T>(const std::string&)>& newFromStr, const std::function<std::string(const T&)> newToStr, bool newSingleLine, const std::function<void()>& elemUpdate) {
void update(UpdateInputData& io, T* newData, const std::function<std::optional<T>(const std::string&)>& newFromStr, const std::function<std::string(const T&)> newToStr, bool newSingleLine, bool updateEveryEdit, const std::function<void()>& elemUpdate) {
data = newData;
fromStr = newFromStr;
toStr = newToStr;
@ -18,7 +18,7 @@ template <typename T> class TextBox : public Element {
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(io.fontSize * 2)), .height = CLAY_SIZING_GROW(0)}
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(io.fontSize * 2)), .height = CLAY_SIZING_GROW(static_cast<float>(io.fontSize))}
},
.custom = { .customData = this }
}) {
@ -59,12 +59,14 @@ template <typename T> 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 <typename T> 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 <typename T> 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 <typename T> 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 <typename T> 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 <typename T> class TextBox : public Element {
//oldData = T();
}
void update_on_edit(bool updateEveryEdit) {
if(updateEveryEdit) {
std::optional<T> dataToAssign = fromStr(textbox.get_string());
if(dataToAssign) {
*data = dataToAssign.value();
oldData = *data;
}
}
}
T* data = nullptr;
T oldData;

View file

@ -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<uint32_t>(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();
}

View file

@ -107,9 +107,9 @@ class GUIManager {
return toRet;
}
template <typename T> void input_generic(const std::string& id, T* val, const std::function<std::optional<T>(const std::string&)>& fromStr, const std::function<std::string(const T&)>& toStr, bool singleLine, const std::function<void()>& elemUpdate = nullptr) {
template <typename T> void input_generic(const std::string& id, T* val, const std::function<std::optional<T>(const std::string&)>& fromStr, const std::function<std::string(const T&)>& toStr, bool singleLine, bool updateEveryEdit, const std::function<void()>& elemUpdate = nullptr) {
push_id(id);
insert_element<TextBox<T>>()->update(*io, val, fromStr, toStr, singleLine, elemUpdate);
insert_element<TextBox<T>>()->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<void()>& elemUpdate);
@ -184,7 +184,7 @@ class GUIManager {
}
return ss.str();
},
true, elemUpdate);
true, false, elemUpdate);
}
template <typename T> void color_picker(const std::string& id, T* val, bool selectAlpha, const std::function<void()>& elemUpdate = nullptr) {

View file

@ -1,118 +1,34 @@
#include "GridManager.hpp"
#include "MainProgram.hpp"
#include <include/core/SkPaint.h>
#include <include/core/SkPathTypes.h>
#include <Helpers/MathExtras.hpp>
#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<SkShader> GridManager::get_shader(const sk_sp<SkRuntimeEffect>& runtimeEffect, const SkColor4f& pointColor) {
SkRuntimeShaderBuilder builder(runtimeEffect);
builder.uniform("pointColor") = pointColor;
sk_sp<SkShader> 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<std::string>& 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<float>()) {
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>();
}
float alteredZoom = static_cast<float>(((size / WorldScalar(GRID_CACHE_SIZE_INT)) / drawData.cam.c.inverseScale));
if(alteredZoom > 0.5f) {
Vector2f fracCamPos;
fracCamPos.x() = static_cast<float>((drawData.cam.c.pos.x() % size) / drawData.cam.c.inverseScale);
fracCamPos.y() = static_cast<float>((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);
}

View file

@ -1,41 +1,20 @@
#pragma once
#include <Eigen/Dense>
#include <include/core/SkCanvas.h>
#include "DrawData.hpp"
#include <include/core/SkPath.h>
#include <include/effects/SkRuntimeEffect.h>
#include <nlohmann/json.hpp>
#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<std::string>& sorted_names();
enum GridType : unsigned {
GRIDTYPE_NONE = 0,
GRIDTYPE_CIRCLEDOT,
GRIDTYPE_SQUAREDOT,
GRIDTYPE_LINE
};
GridType gridType = GRIDTYPE_CIRCLEDOT;
std::unordered_map<std::string, WorldGrid> grids;
World& world;
private:
sk_sp<SkShader> get_shader(const sk_sp<SkRuntimeEffect>& runtimeEffect, const SkColor4f& pointColor);
Vector2f oldWindowSize{0.0f, 0.0f};
WorldScalar size{50000000};
SkPath gridPath;
sk_sp<SkRuntimeEffect> circleDotEffect;
sk_sp<SkRuntimeEffect> squareDotEffect;
MainProgram& main;
bool changed = true;
std::vector<std::string> 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"}
})

View file

@ -36,8 +36,7 @@
#include <Helpers/Networking/NetLibrary.hpp>
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);
}

View file

@ -1,6 +1,5 @@
#pragma once
#include "DrawData.hpp"
#include "GridManager.hpp"
#include <chrono>
#include <include/core/SkCanvas.h>
#include "InputManager.hpp"
@ -66,7 +65,6 @@ class MainProgram {
FontData fonts;
TimePoint deltaTime;
Toolbar toolbar;
GridManager grid;
std::shared_ptr<World> world;
std::ofstream* logFile;

View file

@ -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 <SDL3/SDL_dialog.h>
@ -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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(io->theme->backColor1) : convert_vec4<Clay_Color>(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<size_t>(g.gridType);
std::vector<std::string> 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<WorldGrid::GridType>(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<uint16_t>("GUI Font Size", "GUI Font Size", &io->fontSize, 10, 30);
gui.text_label("Note: Changing font size may break UI in some cases.");

View file

@ -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;

View file

@ -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);
}

View file

@ -6,6 +6,7 @@
#include "DrawingProgram/DrawingProgram.hpp"
#include "Toolbar.hpp"
#include "SharedTypes.hpp"
#include "GridManager.hpp"
#include <filesystem>
class MainProgram;
@ -47,6 +48,7 @@ class World {
DrawingProgram drawProg;
BookmarkManager bMan;
ConnectionManager con;
GridManager gridMan;
std::deque<Toolbar::ChatMessage> 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};

137
src/WorldGrid.cpp Normal file
View file

@ -0,0 +1,137 @@
#include "WorldGrid.hpp"
#include "GridManager.hpp"
#include "MainProgram.hpp"
sk_sp<SkRuntimeEffect> WorldGrid::circlePointEffect;
sk_sp<SkRuntimeEffect> WorldGrid::squarePointEffect;
sk_sp<SkRuntimeEffect> WorldGrid::squareLinesEffect;
sk_sp<SkRuntimeEffect> 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<SkRuntimeEffect> 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<SkShader> WorldGrid::get_shader(GridType gType, const SkColor4f& gridColor, float gridScale) {
sk_sp<SkRuntimeEffect> 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<SkShader> 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<float>(((size / WorldScalar(GRID_CACHE_SIZE_INT)) / drawData.cam.c.inverseScale));
if(alteredZoom > 0.5f) {
Vector2f fracCamPos;
fracCamPos.x() = static_cast<float>((drawData.cam.c.pos.x() % size) / drawData.cam.c.inverseScale);
fracCamPos.y() = static_cast<float>((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();
}
}

26
src/WorldGrid.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "DrawData.hpp"
#include <include/effects/SkRuntimeEffect.h>
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<SkShader> get_shader(GridType gType, const SkColor4f& gridColor, float gridScale);
static sk_sp<SkRuntimeEffect> compile_effect_shader_init(const char* shaderName, const char* shaderCode);
static Vector2f oldWindowSize;
static sk_sp<SkRuntimeEffect> circlePointEffect;
static sk_sp<SkRuntimeEffect> squarePointEffect;
static sk_sp<SkRuntimeEffect> squareLinesEffect;
static sk_sp<SkRuntimeEffect> ruledEffect;
};