Initial Commit

This commit is contained in:
Yousef Khadadeh 2025-07-06 20:43:53 +03:00
commit 6088fc29ed
978 changed files with 353647 additions and 0 deletions

View file

@ -0,0 +1,196 @@
#include "BrushTool.hpp"
#include <Helpers/ConvertVec.hpp>
#include "../TimePoint.hpp"
#include "../DrawComponents/DrawBrushStroke.hpp"
#include "../GUIStuff/GUIManager.hpp"
#include "../SharedTypes.hpp"
#include "../Server/CommandList.hpp"
#include "DrawingProgram.hpp"
#include "../MainProgram.hpp"
#include <chrono>
#define VEL_SMOOTH_MIN 0.6
#define VEL_SMOOTH_MAX 1.0
#define MINIMUM_DISTANCE_TO_NEXT_POINT 0.002f
BrushTool::BrushTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
controls.intermediateItem = std::make_shared<DrawBrushStroke>();
}
void BrushTool::reset_tool() {
commit_stroke();
controls.isDrawing = false;
}
bool BrushTool::extensive_point_checking(const Vector2f& newPoint) {
auto& points = controls.intermediateItem->d.points;
if(points.size() >= 1 && (newPoint - points[points.size() - 1].pos).norm() < MINIMUM_DISTANCE_TO_NEXT_POINT)
return false;
if(points.size() >= 2 && (newPoint - points[points.size() - 2].pos).norm() < MINIMUM_DISTANCE_TO_NEXT_POINT)
return false;
if(points.size() >= 3 && (newPoint - points[points.size() - 3].pos).norm() < MINIMUM_DISTANCE_TO_NEXT_POINT)
return false;
return true;
}
bool BrushTool::extensive_point_checking_back(const Vector2f& newPoint) {
auto& points = controls.intermediateItem->d.points;
if(points.size() >= 2 && (newPoint - points[points.size() - 2].pos).norm() < MINIMUM_DISTANCE_TO_NEXT_POINT)
return false;
if(points.size() >= 3 && (newPoint - points[points.size() - 3].pos).norm() < MINIMUM_DISTANCE_TO_NEXT_POINT)
return false;
if(points.size() >= 4 && (newPoint - points[points.size() - 4].pos).norm() < MINIMUM_DISTANCE_TO_NEXT_POINT)
return false;
return true;
}
void BrushTool::tool_update() {
if(drawP.controls.cursorHoveringOverCanvas)
drawP.world.main.input.hideCursor = true;
if(!controls.isDrawing)
controls.penSmoothingData.clear();
float averageScreenSize = (drawP.world.main.window.size.x() + drawP.world.main.window.size.y()) / 2.0f;
float minVel = 0.2f * averageScreenSize;
float maxVel = 3.5f * averageScreenSize;
while(!controls.velocitySmoothingData.empty() && (std::chrono::steady_clock::now() - controls.velocitySmoothingData.front().t) > std::chrono::milliseconds(200))
controls.velocitySmoothingData.pop_front();
controls.velocitySmoothingData.emplace_back(
drawP.world.main.input.mouse.move.norm() / drawP.world.main.deltaTime,
std::chrono::steady_clock::now()
);
if(drawP.world.main.input.pen.isDown && drawP.controls.leftClickHeld && drawP.world.main.toolbar.tabletOptions.pressureAffectsBrushWidth) {
while(!controls.penSmoothingData.empty() && (std::chrono::steady_clock::now() - controls.penSmoothingData.front().t) > std::chrono::milliseconds(static_cast<int>(drawP.world.main.toolbar.tabletOptions.smoothingSamplingTime * 1000.0f)))
controls.penSmoothingData.pop_front();
controls.penSmoothingData.emplace_back(
drawP.world.main.input.pen.pressure,
std::chrono::steady_clock::now()
);
float averagePressure = 0.0f;
for(auto& [width, t] : controls.penSmoothingData)
averagePressure += width;
averagePressure /= controls.penSmoothingData.size();
controls.penWidth = averagePressure;
}
else if(drawP.world.main.toolbar.velocityAffectsBrushWidth) {
float averageVel = 0.0f;
for(auto& [width, t] : controls.velocitySmoothingData)
averageVel += width;
averageVel /= controls.velocitySmoothingData.size();
controls.penWidth = std::clamp<float>((averageVel - minVel) / (maxVel - minVel), VEL_SMOOTH_MIN, VEL_SMOOTH_MAX);
}
else
controls.penWidth = 1.0f;
bool isPenDown = false;
if(drawP.world.main.input.pen.isDown && drawP.controls.leftClickHeld)
isPenDown = controls.penWidth != 0.0;
else if(drawP.controls.leftClickHeld)
isPenDown = true;
if(isPenDown) {
if(!controls.isDrawing) {
controls.isDrawing = true;
controls.intermediateItem = std::make_shared<DrawBrushStroke>();
DrawBrushStrokePoint p;
p.pos = drawP.world.main.input.mouse.pos;
p.width = drawP.controls.relativeWidth * controls.penWidth;
controls.prevPointUnaltered = p.pos;
controls.intermediateItem->d.points.emplace_back(p);
controls.intermediateItem->d.color = drawP.controls.foregroundColor;
controls.intermediateItem->d.hasRoundCaps = controls.hasRoundCaps;
controls.intermediateItem->coords = drawP.world.drawData.cam.c;
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
controls.intermediateItem->temp_update(drawP);
uint64_t placement = drawP.components.client_list().size();
controls.intermediateID = drawP.components.client_insert(placement, controls.intermediateItem);
controls.intermediateItem->client_send_place(drawP, placement);
// NOTE: The data isnt finallized at this point, so a BVH isn't generated for an object that you undo while youre drawing (this isnt just for brush strokes)
// The fix for now is to generate a BVH for an object when you redo it
drawP.add_undo_place_component(placement, controls.intermediateItem);
controls.addedTemporaryPoint = false;
}
else {
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
DrawBrushStrokePoint p;
p.pos = controls.intermediateItem->coords.get_mouse_pos(drawP.world);
p.width = drawP.controls.relativeWidth * controls.penWidth;
if(!controls.addedTemporaryPoint) {
if(extensive_point_checking(p.pos)) {
controls.intermediateItem->d.points.emplace_back(p);
controls.addedTemporaryPoint = true;
}
else
controls.intermediateItem->d.points.back().width = std::max(controls.intermediateItem->d.points.back().width, p.width);
}
if(controls.addedTemporaryPoint) {
const DrawBrushStrokePoint& prevP = controls.intermediateItem->d.points[controls.intermediateItem->d.points.size() - 2];
float distToPrev = (p.pos - prevP.pos).norm();
if(extensive_point_checking_back(p.pos)) {
controls.intermediateItem->d.points.back().pos = p.pos;
controls.intermediateItem->d.points.back().width = std::max(controls.intermediateItem->d.points.back().width, p.width);
controls.intermediateItem->d.points[controls.intermediateItem->d.points.size() - 2].width = std::max(controls.intermediateItem->d.points[controls.intermediateItem->d.points.size() - 2].width, p.width);
}
if((!controls.drawingMinimumRelativeToSize && distToPrev >= 10.0) || (controls.drawingMinimumRelativeToSize && distToPrev >= drawP.controls.relativeWidth * DRAW_MINIMUM_LIMIT)) {
controls.intermediateItem->d.points.back() = p;
controls.addedTemporaryPoint = false;
controls.intermediateItem->client_send_update_temp(drawP, controls.intermediateID);
if(controls.midwayInterpolation) {
if(controls.intermediateItem->d.points.size() != 2) // Don't interpolate the first point
controls.intermediateItem->d.points[controls.intermediateItem->d.points.size() - 2].pos = (controls.prevPointUnaltered + p.pos) * 0.5;
controls.prevPointUnaltered = p.pos;
}
}
controls.intermediateItem->temp_update(drawP);
}
}
}
else if(controls.isDrawing) {
controls.isDrawing = false;
commit_stroke();
}
}
void BrushTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("brush tool");
t.gui.text_label_centered("Brush");
t.gui.checkbox_field("hasroundcaps", "Round Caps", &controls.hasRoundCaps);
t.gui.slider_scalar_field("relwidth", "Size", &drawP.controls.relativeWidth, 3.0f, 40.0f);
//t.gui.checkbox_field("drawminrel", "Drawing Min Relative To Size", &controls.drawingMinimumRelativeToSize);
//t.gui.checkbox_field("midwayinterp", "Midway Interpolation", &controls.midwayInterpolation);
t.gui.pop_id();
}
void BrushTool::commit_stroke() {
if(controls.intermediateItem) {
controls.intermediateItem->client_send_update_final(drawP, controls.intermediateID);
controls.intermediateItem->final_update(drawP);
controls.intermediateItem = nullptr;
}
}
void BrushTool::draw(SkCanvas* canvas, const DrawData& drawData) {
if(!drawData.main->toolbar.io->hoverObstructed) {
SkPaint linePaint;
linePaint.setColor4f(drawP.world.main.canvasTheme.toolFrontColor);
linePaint.setStroke(true);
linePaint.setStrokeWidth(0.0f);
float width;
if(controls.isDrawing)
width = drawP.controls.relativeWidth * controls.penWidth * 0.5f;
else
width = drawP.controls.relativeWidth * 0.5f;
Vector2f pos = drawData.main->input.mouse.pos;
canvas->drawCircle(pos.x(), pos.y(), width, linePaint);
}
}

View file

@ -0,0 +1,47 @@
#pragma once
#include "../SharedTypes.hpp"
#include <Helpers/Hashes.hpp>
#include <include/core/SkPath.h>
#include <include/core/SkVertices.h>
#include <Helpers/SCollision.hpp>
#include <deque>
#include <any>
#include <include/core/SkCanvas.h>
class DrawingProgram;
class DrawBrushStroke;
struct DrawData;
class BrushTool {
public:
BrushTool(DrawingProgram& initDrawP);
void gui_toolbox();
void tool_update();
void reset_tool();
void draw(SkCanvas* canvas, const DrawData& drawData);
private:
void commit_stroke();
bool extensive_point_checking_back(const Vector2f& newPoint);
bool extensive_point_checking(const Vector2f& newPoint);
struct BrushToolControls {
float penWidth = 1.0f;
bool addedTemporaryPoint = false;
struct SmoothingPoint {
float val;
std::chrono::steady_clock::time_point t;
};
std::deque<SmoothingPoint> velocitySmoothingData;
std::deque<SmoothingPoint> penSmoothingData;
std::shared_ptr<DrawBrushStroke> intermediateItem;
ServerClientID intermediateID;
bool isDrawing = false;
bool hasRoundCaps = true;
bool drawingMinimumRelativeToSize = true;
bool midwayInterpolation = true;
Vector2f prevPointUnaltered = {0, 0};
} controls;
DrawingProgram& drawP;
};

View file

@ -0,0 +1,578 @@
#include "DrawingProgram.hpp"
#include "Helpers/Hashes.hpp"
#include "Helpers/MathExtras.hpp"
#include "Helpers/SCollision.hpp"
#include <include/core/SkPaint.h>
#include <include/core/SkVertices.h>
#include "../DrawCamera.hpp"
#include "../DrawComponents/DrawComponent.hpp"
#include "EllipseDrawTool.hpp"
#include "InkDropperTool.hpp"
#include "../InputManager.hpp"
#include "../SharedTypes.hpp"
#include "../Server/CommandList.hpp"
#include <optional>
#include <ranges>
#include <Helpers/ConvertVec.hpp>
#include <cereal/types/vector.hpp>
#include <iostream>
#include "../DrawComponents/DrawImage.hpp"
#include <include/core/SkImage.h>
#include <include/core/SkSurface.h>
#include "../World.hpp"
#include "../MainProgram.hpp"
#include <Helpers/Logger.hpp>
#include <execution>
#ifdef __APPLE__
#define POOLSTL_STD_SUPPLEMENT
#include <poolstl.hpp>
#endif
DrawingProgram::DrawingProgram(World& initWorld):
world(initWorld),
components([&](){ return world.get_new_id(); }),
brushTool(*this),
eraserTool(*this),
rectSelectTool(*this),
rectDrawTool(*this),
ellipseDrawTool(*this),
textBoxTool(*this),
inkDropperTool(*this),
screenshotTool(*this),
editTool(*this),
imageTool(*this)
{
components.updateCallback = [&]() {
if(&world == world.main.world.get())
world.main.drawProgCache.refresh = true;
};
}
void DrawingProgram::init_client_callbacks() {
world.con.client_add_recv_callback(CLIENT_UPDATE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
bool isTemp;
ServerClientID id;
message(isTemp, id);
std::shared_ptr<DrawComponent>* compPtr = components.get_item_by_id(id);
if(!compPtr)
return;
std::shared_ptr<DrawComponent>& comp = *compPtr;
float dur = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - comp->lastUpdateTime).count();
if(dur > CLIENT_DRAWCOMP_DELAY_TIMER_DURATION){
comp->delayedUpdatePtr = nullptr;
message(*comp);
if(isTemp)
comp->temp_update(*this);
else
comp->final_update(*this);
}
else {
comp->delayedUpdatePtr = DrawComponent::allocate_comp_type(comp->get_type());
message(*comp->delayedUpdatePtr);
}
});
world.con.client_add_recv_callback(CLIENT_TRANSFORM_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
bool isTemp;
ServerClientID id;
message(isTemp, id);
std::shared_ptr<DrawComponent>* compPtr = components.get_item_by_id(id);
if(!compPtr)
return;
std::shared_ptr<DrawComponent>& comp = *compPtr;
float dur = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - comp->lastTransformTime).count();
if(dur > CLIENT_DRAWCOMP_DELAY_TIMER_DURATION){
comp->delayedCoordinateSpace = nullptr;
comp->updateDraw = true;
message(comp->coords);
if(!isTemp)
comp->finalize_update(colliderAllocated); // No need to update draw data during transformation
}
else {
comp->delayedCoordinateSpace = std::make_shared<CoordSpaceHelper>();
message(*comp->delayedCoordinateSpace);
}
});
world.con.client_add_recv_callback(CLIENT_PLACE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
uint64_t insertPosition;
DrawComponentType type;
message(insertPosition, type);
ServerClientID newObjectID;
auto newObj = DrawComponent::allocate_comp_type(type);
message(newObjectID, newObj->coords, *newObj);
bool didntExistPreviously = components.server_insert(insertPosition, newObjectID, newObj);
if(didntExistPreviously)
newObj->final_update(*this);
});
world.con.client_add_recv_callback(CLIENT_ERASE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
ServerClientID cToRemove;
message(cToRemove);
components.server_erase(cToRemove);
});
}
void DrawingProgram::free_collider_memory() {
for(auto& c : components.client_list())
c->obj->free_collider();
colliderAllocated = false;
}
void DrawingProgram::allocate_collider_memory() {
for(auto& c : components.client_list())
c->obj->allocate_collider();
colliderAllocated = true;
}
void DrawingProgram::check_all_collisions_base(const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam) {
#ifdef __EMSCRIPTEN__
std::for_each(components.client_list().begin(), components.client_list().end(), [&](auto& c) {
c->obj->globalCollisionCheck = c->obj->collides_with(world.drawData.cam.c, checkAgainstWorld, checkAgainstCam, colliderAllocated);
});
#else
std::for_each(std::execution::par_unseq, components.client_list().begin(), components.client_list().end(), [&](auto& c) {
c->obj->globalCollisionCheck = c->obj->collides_with(world.drawData.cam.c, checkAgainstWorld, checkAgainstCam, colliderAllocated);
});
#endif
}
void DrawingProgram::check_all_collisions(const SCollision::ColliderCollection<WorldScalar>& checkAgainst) {
check_all_collisions_base(checkAgainst, world.drawData.cam.c.world_collider_to_coords<SCollision::ColliderCollection<float>>(checkAgainst));
}
void DrawingProgram::check_all_collisions_transform(const SCollision::ColliderCollection<float>& checkAgainst) {
check_all_collisions_base(world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(checkAgainst), checkAgainst);
}
std::unordered_set<ServerClientID> DrawingProgram::get_used_resources() {
std::unordered_set<ServerClientID> toRet;
for(auto& c : components.client_list())
c->obj->get_used_resources(toRet);
return toRet;
}
void DrawingProgram::toolbar_gui() {
Toolbar& t = world.main.toolbar;
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0)},
.padding = CLAY_PADDING_ALL(t.io->theme->padding1),
.childGap = t.io->theme->childGap1,
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = CLAY_TOP_TO_BOTTOM
},
.backgroundColor = convert_vec4<Clay_Color>(t.io->theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(t.io->theme->windowCorners1),
.border = {.color = convert_vec4<Clay_Color>(t.io->theme->backColor2), .width = CLAY_BORDER_OUTSIDE(t.io->theme->windowBorders1)}
}) {
t.gui.obstructing_window();
if(t.gui.svg_icon_button("Brush Toolbar Button", "data/icons/brush.svg", controls.selectedTool == TOOL_BRUSH)) { controls.selectedTool = TOOL_BRUSH; }
if(t.gui.svg_icon_button("Eraser Toolbar Button", "data/icons/eraser.svg", controls.selectedTool == TOOL_ERASER)) { controls.selectedTool = TOOL_ERASER; }
if(t.gui.svg_icon_button("Text Toolbar Button", "data/icons/text.svg", controls.selectedTool == TOOL_TEXTBOX)) { controls.selectedTool = TOOL_TEXTBOX; }
if(t.gui.svg_icon_button("Ellipse Toolbar Button", "data/icons/circle.svg", controls.selectedTool == TOOL_ELLIPSE)) { controls.selectedTool = TOOL_ELLIPSE; }
if(t.gui.svg_icon_button("Rect Toolbar Button", "data/icons/rectangle.svg", controls.selectedTool == TOOL_RECTANGLE)) { controls.selectedTool = TOOL_RECTANGLE; }
if(t.gui.svg_icon_button("RectSelect Toolbar Button", "data/icons/rectselect.svg", controls.selectedTool == TOOL_RECTSELECT)) { controls.selectedTool = TOOL_RECTSELECT; }
if(t.gui.svg_icon_button("Edit Toolbar Button", "data/icons/cursor.svg", controls.selectedTool == TOOL_EDIT)) { controls.selectedTool = TOOL_EDIT; }
if(t.gui.svg_icon_button("Inkdropper Toolbar Button", "data/icons/eyedropper.svg", controls.selectedTool == TOOL_INKDROPPER)) { controls.selectedTool = TOOL_INKDROPPER; }
if(t.gui.svg_icon_button("Screenshot Toolbar Button", "data/icons/camera.svg", controls.selectedTool == TOOL_SCREENSHOT)) { controls.selectedTool = TOOL_SCREENSHOT; }
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) {
if(t.gui.color_button("Foreground Color", &controls.foregroundColor, &controls.foregroundColor == t.colorLeft)) {
t.color_selector_left(&controls.foregroundColor == t.colorLeft ? nullptr : &controls.foregroundColor);
}
}
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) {
if(t.gui.color_button("Background Color", &controls.backgroundColor, &controls.backgroundColor == t.colorLeft))
t.color_selector_left(&controls.backgroundColor == t.colorLeft ? nullptr : &controls.backgroundColor);
}
}
}
void DrawingProgram::tool_options_gui() {
Toolbar& t = world.main.toolbar;
float minGUIWidth = controls.selectedTool == TOOL_SCREENSHOT ? 300 : 200;
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_FIT(minGUIWidth), .height = CLAY_SIZING_FIT(0)},
.padding = CLAY_PADDING_ALL(t.io->theme->padding1),
.childGap = t.io->theme->childGap1,
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = CLAY_TOP_TO_BOTTOM
},
.backgroundColor = convert_vec4<Clay_Color>(t.io->theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(t.io->theme->windowCorners1),
.border = {.color = convert_vec4<Clay_Color>(t.io->theme->backColor2), .width = CLAY_BORDER_OUTSIDE(t.io->theme->windowBorders1)}
}) {
t.gui.obstructing_window();
switch(controls.selectedTool) {
case TOOL_BRUSH:
brushTool.gui_toolbox();
break;
case TOOL_ERASER:
eraserTool.gui_toolbox();
break;
case TOOL_RECTSELECT:
rectSelectTool.gui_toolbox();
break;
case TOOL_RECTANGLE:
rectDrawTool.gui_toolbox();
break;
case TOOL_ELLIPSE:
ellipseDrawTool.gui_toolbox();
break;
case TOOL_TEXTBOX:
textBoxTool.gui_toolbox();
break;
case TOOL_INKDROPPER:
inkDropperTool.gui_toolbox();
break;
case TOOL_SCREENSHOT:
screenshotTool.gui_toolbox();
break;
case TOOL_EDIT:
editTool.gui_toolbox();
break;
default:
break;
}
}
}
void DrawingProgram::update() {
if(controls.selectedTool == TOOL_BRUSH && world.main.input.pen.isEraser && !temporaryEraser) {
controls.selectedTool = TOOL_ERASER;
temporaryEraser = true;
}
else if(controls.selectedTool == TOOL_ERASER && !world.main.input.pen.isEraser && temporaryEraser) {
controls.selectedTool = TOOL_BRUSH;
temporaryEraser = false;
}
if(controls.selectedTool != controls.previousSelected) {
controls.previousSelected = controls.selectedTool;
reset_tools();
}
controls.cursorHoveringOverCanvas = !world.main.toolbar.io->hoverObstructed;
controls.leftClick = controls.cursorHoveringOverCanvas && world.main.input.mouse.leftClicks;
if(controls.leftClick) {
controls.leftClickHeld = true;
world.main.toolbar.paintPopupLocation = std::nullopt;
}
if(controls.leftClickHeld && !world.main.input.mouse.leftDown) {
controls.leftClick = false;
controls.leftClickHeld = false;
controls.leftClickReleased = true;
}
if(controls.cursorHoveringOverCanvas && (world.main.input.mouse.rightClicks || world.main.input.pen.buttons[world.main.toolbar.tabletOptions.rightClickButton].pressed)) {
if(world.main.toolbar.paintPopupLocation)
world.main.toolbar.paintPopupLocation = std::nullopt;
else
world.main.toolbar.paintPopupLocation = world.main.input.mouse.pos / world.main.window.scale;
}
controls.previousMouseWorldPos = controls.currentMouseWorldPos;
controls.currentMouseWorldPos = world.get_mouse_world_pos();
drag_drop_update();
if(world.main.input.key(InputManager::KEY_DRAW_TOOL_BRUSH).pressed)
controls.selectedTool = TOOL_BRUSH;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_ERASER).pressed)
controls.selectedTool = TOOL_ERASER;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_RECTSELECT).pressed)
controls.selectedTool = TOOL_RECTSELECT;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_RECTANGLE).pressed)
controls.selectedTool = TOOL_RECTANGLE;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_ELLIPSE).pressed)
controls.selectedTool = TOOL_ELLIPSE;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_TEXTBOX).pressed)
controls.selectedTool = TOOL_TEXTBOX;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_INKDROPPER).pressed)
controls.selectedTool = TOOL_INKDROPPER;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_SCREENSHOT).pressed)
controls.selectedTool = TOOL_SCREENSHOT;
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_EDIT).pressed)
controls.selectedTool = TOOL_EDIT;
else if(world.main.input.key(InputManager::KEY_PASTE).pressed)
rectSelectTool.paste_clipboard();
switch(controls.selectedTool) {
case TOOL_BRUSH:
brushTool.tool_update();
break;
case TOOL_ERASER:
eraserTool.tool_update();
break;
case TOOL_RECTSELECT:
rectSelectTool.tool_update();
break;
case TOOL_RECTANGLE:
rectDrawTool.tool_update();
break;
case TOOL_ELLIPSE:
ellipseDrawTool.tool_update();
break;
case TOOL_TEXTBOX:
textBoxTool.tool_update();
break;
case TOOL_INKDROPPER:
inkDropperTool.tool_update();
break;
case TOOL_SCREENSHOT:
screenshotTool.tool_update();
break;
case TOOL_EDIT:
editTool.tool_update();
break;
default:
break;
};
if(controls.leftClickReleased)
controls.leftClickReleased = false;
for(auto& c : components.client_list()) {
c->obj->check_timers(*this);
c->obj->update(*this);
}
}
void DrawingProgram::drag_drop_update() {
if(controls.cursorHoveringOverCanvas) {
for(auto& droppedItem : world.main.input.droppedItems) {
if(droppedItem.isFile && std::filesystem::is_regular_file(droppedItem.droppedData)) {
ServerClientID imageID = world.rMan.add_resource_file(droppedItem.droppedData);
std::shared_ptr<ResourceDisplay> display = world.rMan.get_display_data(imageID);
Vector2f imTrueDim = display->get_dimensions();
auto img(std::make_shared<DrawImage>());
img->coords = world.drawData.cam.c;
float imWidth = imTrueDim.x() / (imTrueDim.x() + imTrueDim.y());
float imHeight = imTrueDim.y() / (imTrueDim.x() + imTrueDim.y());
Vector2f imDim = Vector2f{world.main.window.size.x() * imWidth, world.main.window.size.x() * imHeight} * display->get_dimension_scale();
Vector2f dropPos = world.main.input.mouse.pos;
// dropPos = droppedItem.pos; // Doesnt work properly with emscripten
img->d.p1 = dropPos - imDim;
img->d.p2 = dropPos + imDim;
img->d.imageID = imageID;
img->initialize_draw_data(*this);
img->finalize_update(colliderAllocated);
uint64_t placement = components.client_list().size();
components.client_insert(placement, img);
img->client_send_place(*this, placement);
add_undo_place_component(placement, img);
}
}
}
}
void DrawingProgram::reset_tools() {
rectSelectTool.reset_tool();
textBoxTool.reset_tool();
brushTool.reset_tool();
rectDrawTool.reset_tool();
ellipseDrawTool.reset_tool();
editTool.reset_tool();
reset_selection();
}
void DrawingProgram::initialize_draw_data(cereal::PortableBinaryInputArchive& a) {
uint64_t compCount;
a(compCount);
for(uint64_t i = 0; i < compCount; i++) {
DrawComponentType t;
ServerClientID id;
a(t, id);
auto newComp = DrawComponent::allocate_comp_type(t);
a(newComp->coords, *newComp);
components.init_emplace_back(id, newComp);
newComp->initialize_draw_data(*this);
newComp->finalize_update(colliderAllocated);
}
}
void DrawingProgram::reset_selection() {
for(auto& c : components.client_list())
c->obj->selected = false;
}
void DrawingProgram::add_undo_place_component(uint64_t placement, const std::shared_ptr<DrawComponent>& comp) {
world.undo.push(UndoManager::UndoRedoPair{
[&, comp]() {
ServerClientID compID;
components.client_erase(comp, compID);
if(compID == ServerClientID{0, 0})
return false;
DrawComponent::client_send_erase(*this, compID);
reset_tools();
return true;
},
[&, comp, placement]() {
if(components.get_id(comp) != ServerClientID{0, 0})
return false;
components.client_insert(placement, comp);
comp->initialize_draw_data(*this); // Method to recover after an undo that happened while drawing a component
comp->finalize_update(colliderAllocated);
comp->client_send_place(*this, placement);
reset_tools();
return true;
}
});
}
void DrawingProgram::add_undo_place_components(uint64_t placement, const std::vector<std::shared_ptr<DrawComponent>>& comps) {
world.undo.push(UndoManager::UndoRedoPair{
[&, comps]() {
std::vector<ServerClientID> compIDs;
for(auto& c : comps) {
ServerClientID compID;
components.client_erase(c, compID);
compIDs.emplace_back(compID);
}
bool toRet = true;
for(auto& compID : compIDs) {
if(compID == ServerClientID{0, 0})
toRet = false;
else
DrawComponent::client_send_erase(*this, compID);
}
reset_tools();
return toRet;
},
[&, comps, placement]() {
for(auto& c : comps)
if(components.get_id(c) != ServerClientID{0, 0})
return false;
for(auto& c : comps | std::views::reverse) {
components.client_insert(placement, c);
c->initialize_draw_data(*this); // Method to recover after an undo that happened while drawing a component
c->finalize_update(colliderAllocated);
c->client_send_place(*this, placement);
}
reset_tools();
return true;
}
});
}
ClientPortionID DrawingProgram::get_max_id(ServerPortionID serverID) {
ClientPortionID maxClientID = 0;
auto& cList = components.client_list();
for(auto& comp : cList) {
if(comp->id.first == serverID)
maxClientID = std::max(maxClientID, comp->id.second);
}
return maxClientID;
}
void DrawingProgram::draw_drag_circle(SkCanvas* canvas, const Vector2f& sPos, const SkColor4f& c, const DrawData& drawData, float radiusMultiplier) {
float constantRadius = DRAG_POINT_RADIUS * radiusMultiplier;
float constantThickness = constantRadius * 0.2f;
canvas->drawCircle(sPos.x(), sPos.y(), constantRadius, SkPaint(c));
SkPaint paintOutline(SkColor4f{0.95f, 0.95f, 0.95f, 1.0f});
paintOutline.setStroke(true);
paintOutline.setStrokeWidth(constantThickness);
canvas->drawCircle(sPos.x(), sPos.y(), constantRadius, paintOutline);
}
void DrawingProgram::write_to_file(cereal::PortableBinaryOutputArchive& a) {
auto& cList = components.client_list();
a((uint64_t)cList.size());
for(size_t i = 0; i < cList.size(); i++) {
a(cList[i]->obj->get_type(), cList[i]->id, cList[i]->obj->coords, *cList[i]->obj);
}
}
void DrawingProgram::draw(SkCanvas* canvas, const DrawData& drawData) {
if(drawData.dontUseDrawProgCache) {
for(auto& c : components.client_list()) {
c->obj->calculate_draw_transform(drawData);
c->obj->draw(canvas, drawData);
}
}
else if(world.main.drawProgCache.refresh)
draw_cache(canvas, drawData, nullptr);
else {
bool caughtFirstUpdated = false;
for(auto& c : components.client_list()) {
if(caughtFirstUpdated) {
c->obj->calculate_draw_transform(drawData);
c->obj->draw(canvas, drawData);
}
else if(c->obj == world.main.drawProgCache.firstCompUpdate) {
canvas->drawImage(world.main.drawProgCache.surface->makeTemporaryImage(), 0, 0);
c->obj->calculate_draw_transform(drawData);
c->obj->draw(canvas, drawData);
caughtFirstUpdated = true;
}
else if(c->obj->updateDraw) {
draw_cache(canvas, drawData, c->obj);
c->obj->calculate_draw_transform(drawData);
c->obj->draw(canvas, drawData);
caughtFirstUpdated = true;
}
c->obj->updateDraw = false;
}
if(!caughtFirstUpdated)
canvas->drawImage(world.main.drawProgCache.surface->makeTemporaryImage(), 0, 0);
}
switch(controls.selectedTool) {
case TOOL_BRUSH:
brushTool.draw(canvas, drawData);
break;
case TOOL_ERASER:
eraserTool.draw(canvas, drawData);
break;
case TOOL_RECTSELECT:
rectSelectTool.draw(canvas, drawData);
break;
case TOOL_RECTANGLE:
rectDrawTool.draw(canvas, drawData);
break;
case TOOL_ELLIPSE:
ellipseDrawTool.draw(canvas, drawData);
break;
case TOOL_TEXTBOX:
textBoxTool.draw(canvas, drawData);
break;
case TOOL_INKDROPPER:
inkDropperTool.draw(canvas, drawData);
break;
case TOOL_SCREENSHOT:
screenshotTool.draw(canvas, drawData);
break;
case TOOL_EDIT:
editTool.draw(canvas, drawData);
break;
default:
break;
}
}
Vector4f* DrawingProgram::get_foreground_color_ptr() {
return &controls.foregroundColor;
}
void DrawingProgram::draw_cache(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<DrawComponent>& lastComp) {
SkCanvas* cacheCanvas = world.main.drawProgCache.canvas;
cacheCanvas->clear(SkColor4f{0, 0, 0, 0});
#ifdef __EMSCRIPTEN__
std::for_each(components.client_list().begin(), components.client_list().end(), [&](auto& c) {
c->obj->calculate_draw_transform(drawData);
});
#else
std::for_each(std::execution::par_unseq, components.client_list().begin(), components.client_list().end(), [&](auto& c) {
c->obj->calculate_draw_transform(drawData);
});
#endif
for(auto& c : components.client_list()) {
if(c->obj == lastComp)
break;
c->obj->draw(cacheCanvas, drawData);
}
canvas->drawImage(world.main.drawProgCache.surface->makeTemporaryImage(), 0, 0);
world.main.drawProgCache.firstCompUpdate = lastComp;
world.main.drawProgCache.refresh = false;
}

View file

@ -0,0 +1,121 @@
#pragma once
#include "../DrawData.hpp"
#include "cereal/archives/portable_binary.hpp"
#include <include/core/SkCanvas.h>
#include <include/core/SkPath.h>
#include <include/core/SkVertices.h>
#include <Helpers/SCollision.hpp>
#include <Helpers/Hashes.hpp>
#include <list>
#include <Helpers/Random.hpp>
#include "BrushTool.hpp"
#include "EraserTool.hpp"
#include "RectSelectTool.hpp"
#include "RectDrawTool.hpp"
#include "EllipseDrawTool.hpp"
#include "TextBoxTool.hpp"
#include "InkDropperTool.hpp"
#include "ScreenshotTool.hpp"
#include "EditTool.hpp"
#include "ImageTool.hpp"
#include "../CollabList.hpp"
class World;
#define DRAG_POINT_RADIUS 10.0f
class DrawingProgram {
public:
DrawingProgram(World& initWorld);
void init_client_callbacks();
void toolbar_gui();
void tool_options_gui();
void update();
void draw(SkCanvas* canvas, const DrawData& drawData);
void initialize_draw_data(cereal::PortableBinaryInputArchive& a);
std::unordered_set<ServerClientID> get_used_resources();
ClientPortionID get_max_id(ServerPortionID serverID);
void write_to_file(cereal::PortableBinaryOutputArchive& a);
World& world;
void free_collider_memory();
void allocate_collider_memory();
bool colliderAllocated = false;
CollabList<std::shared_ptr<DrawComponent>, ServerClientID> components;
Vector4f* get_foreground_color_ptr();
private:
void draw_cache(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<DrawComponent>& lastComp);
void check_all_collisions_base(const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam);
void check_all_collisions(const SCollision::ColliderCollection<WorldScalar>& checkAgainst);
void check_all_collisions_transform(const SCollision::ColliderCollection<float>& checkAgainst);
void add_variable_width_line(SkPath& p, const DrawData& drawData, const WorldVec& p1, const WorldVec& p2, float w1, float w2);
void reset_selection();
void reset_tools();
void drag_drop_update();
void draw_drag_circle(SkCanvas* canvas, const Vector2f& pos, const SkColor4f& c, const DrawData& drawData, float radiusMultiplier = 1.0f);
void add_undo_place_component(uint64_t placement, const std::shared_ptr<DrawComponent>& comp);
void add_undo_place_components(uint64_t placement, const std::vector<std::shared_ptr<DrawComponent>>& comps);
BrushTool brushTool;
EraserTool eraserTool;
RectSelectTool rectSelectTool;
RectDrawTool rectDrawTool;
EllipseDrawTool ellipseDrawTool;
TextBoxTool textBoxTool;
InkDropperTool inkDropperTool;
ScreenshotTool screenshotTool;
EditTool editTool;
ImageTool imageTool;
bool temporaryEraser = false;
enum Tool : int {
TOOL_BRUSH = 0,
TOOL_ERASER,
TOOL_RECTSELECT,
TOOL_RECTANGLE,
TOOL_ELLIPSE,
TOOL_TEXTBOX,
TOOL_INKDROPPER,
TOOL_SCREENSHOT,
TOOL_EDIT
};
struct GlobalControls {
float relativeWidth = 15.0f;
Vector4f foregroundColor{0.9f, 0.9f, 0.9f, 1.0f};
Vector4f backgroundColor{0.0f, 0.0f, 0.0f, 1.0f};
int previousSelected = 0;
WorldVec previousMouseWorldPos = {0, 0};
WorldVec currentMouseWorldPos = {0, 0};
int selectedTool = 0;
bool leftClick = false;
bool leftClickHeld = false;
bool leftClickReleased = false;
bool cursorHoveringOverCanvas = false;
int colorEditing = 0;
} controls;
uint32_t nextID = 0;
friend class InkDropperTool;
friend class RectDrawTool;
friend class EllipseDrawTool;
friend class BrushTool;
friend class EraserTool;
friend class RectSelectTool;
friend class TextBoxTool;
friend class ScreenshotTool;
friend class EditTool;
friend class ImageTool;
};

View file

@ -0,0 +1,218 @@
#include "EditTool.hpp"
#include <chrono>
#include "DrawingProgram.hpp"
#include "../Server/CommandList.hpp"
#include "../MainProgram.hpp"
#include "../DrawData.hpp"
#include "Helpers/MathExtras.hpp"
#include "Helpers/SCollision.hpp"
#include "Helpers/Serializers.hpp"
#include "../DrawComponents/DrawRectangle.hpp"
#include "../DrawComponents/DrawTextBox.hpp"
#include "../InputManager.hpp"
#include "../SharedTypes.hpp"
#include <cereal/types/vector.hpp>
#include <memory>
#include <ranges>
EditTool::EditTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
}
void EditTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
if(controls.isEditing) {
std::shared_ptr<DrawComponent> a = controls.compToEdit.lock();
bool editHappened = false;
if(a) {
switch(a->get_type()) {
case DRAWCOMPONENT_TEXTBOX: {
editHappened = drawP.textBoxTool.edit_gui(std::static_pointer_cast<DrawTextBox>(a));
break;
}
case DRAWCOMPONENT_ELLIPSE: {
editHappened = drawP.ellipseDrawTool.edit_gui(std::static_pointer_cast<DrawEllipse>(a));
break;
}
case DRAWCOMPONENT_RECTANGLE: {
editHappened = drawP.rectDrawTool.edit_gui(std::static_pointer_cast<DrawRectangle>(a));
break;
}
case DRAWCOMPONENT_IMAGE: {
editHappened = drawP.imageTool.edit_gui(std::static_pointer_cast<DrawImage>(a));
break;
}
default: {
break;
}
}
}
if(editHappened) {
a->temp_update(drawP);
a->client_send_update_temp(drawP, drawP.components.get_id(a));
}
}
else {
t.gui.push_id("edit tool");
t.gui.text_label_centered("Edit");
t.gui.pop_id();
}
}
void EditTool::add_point_handle(const HandleData& handle) {
controls.pointHandles.emplace_back(handle);
}
void EditTool::reset_tool() {
auto a = controls.compToEdit.lock();
if(a) {
switch(a->get_type()) {
case DRAWCOMPONENT_TEXTBOX:
drawP.textBoxTool.commit_edit_updates(std::static_pointer_cast<DrawTextBox>(a), controls.prevData);
break;
case DRAWCOMPONENT_ELLIPSE:
drawP.ellipseDrawTool.commit_edit_updates(std::static_pointer_cast<DrawEllipse>(a), controls.prevData);
break;
case DRAWCOMPONENT_RECTANGLE:
drawP.rectDrawTool.commit_edit_updates(std::static_pointer_cast<DrawRectangle>(a), controls.prevData);
break;
case DRAWCOMPONENT_IMAGE:
drawP.imageTool.commit_edit_updates(std::static_pointer_cast<DrawImage>(a), controls.prevData);
break;
default:
break;
}
a->final_update(drawP);
a->client_send_update_final(drawP, drawP.components.get_id(a));
}
controls.compToEdit.reset();
controls.pointHandles.clear();
controls.pointDragging = nullptr;
controls.isEditing = false;
}
void EditTool::edit_start(const std::shared_ptr<DrawComponent>& comp) {
controls.isEditing = true;
switch(comp->get_type()) {
case DRAWCOMPONENT_TEXTBOX: {
drawP.textBoxTool.edit_start(std::static_pointer_cast<DrawTextBox>(comp), controls.prevData);
break;
}
case DRAWCOMPONENT_ELLIPSE: {
drawP.ellipseDrawTool.edit_start(std::static_pointer_cast<DrawEllipse>(comp), controls.prevData);
break;
}
case DRAWCOMPONENT_RECTANGLE: {
drawP.rectDrawTool.edit_start(std::static_pointer_cast<DrawRectangle>(comp), controls.prevData);
break;
}
case DRAWCOMPONENT_IMAGE: {
drawP.imageTool.edit_start(std::static_pointer_cast<DrawImage>(comp), controls.prevData);
break;
}
default: {
controls.isEditing = false;
break;
}
}
if(controls.isEditing) {
controls.compToEdit = comp;
comp->lastUpdateTime = std::chrono::steady_clock::now();
}
}
void EditTool::tool_update() {
using namespace SCollision;
ColliderCollection<float> ci;
ci.circle.emplace_back(Circle<float>{drawP.world.main.input.mouse.pos, 1.0f});
ci.recalculate_bounds();
if(!controls.isEditing) {
if(drawP.controls.leftClick) {
drawP.check_all_collisions_transform(ci);
for(auto& comp : drawP.components.client_list() | std::views::reverse) {
if(comp->obj->globalCollisionCheck) {
edit_start(comp->obj);
break;
}
}
}
}
else {
std::shared_ptr<DrawComponent> a = controls.compToEdit.lock();
a->updateDraw = true;
if(a) {
bool isMovingPoint = false;
bool clickedAway = false;
if(!controls.pointDragging && drawP.controls.leftClick) {
for(HandleData& h : controls.pointHandles) {
if(SCollision::collide(ci, Circle<float>(drawP.world.drawData.cam.c.to_space(a->coords.from_space(*h.p)), DRAG_POINT_RADIUS))) {
controls.pointDragging = &h;
isMovingPoint = true;
break;
}
}
if(!isMovingPoint && !a->collides_with_cam_coords(drawP.world.drawData.cam.c, ci, drawP.colliderAllocated))
clickedAway = true;
}
else if(controls.pointDragging) {
if(drawP.controls.leftClickHeld) {
Vector2f newPos = a->coords.get_mouse_pos(drawP.world);
if(newPos != *controls.pointDragging->p) {
if(controls.pointDragging->min)
newPos = cwise_vec_max(*controls.pointDragging->min, newPos);
if(controls.pointDragging->max)
newPos = cwise_vec_min(*controls.pointDragging->max, newPos);
*controls.pointDragging->p = newPos;
a->temp_update(drawP);
a->client_send_update_temp(drawP, drawP.components.get_id(a));
}
isMovingPoint = true;
}
else
controls.pointDragging = nullptr;
}
a->lastUpdateTime = std::chrono::steady_clock::now();
bool shouldNotReset;
switch(a->get_type()) {
case DRAWCOMPONENT_TEXTBOX: {
shouldNotReset = drawP.textBoxTool.edit_update(std::static_pointer_cast<DrawTextBox>(a));
break;
}
case DRAWCOMPONENT_ELLIPSE: {
shouldNotReset = drawP.ellipseDrawTool.edit_update(std::static_pointer_cast<DrawEllipse>(a));
break;
}
case DRAWCOMPONENT_RECTANGLE: {
shouldNotReset = drawP.rectDrawTool.edit_update(std::static_pointer_cast<DrawRectangle>(a));
break;
}
case DRAWCOMPONENT_IMAGE: {
shouldNotReset = drawP.imageTool.edit_update(std::static_pointer_cast<DrawImage>(a));
break;
}
default: {
shouldNotReset = false;
break;
}
}
if(!shouldNotReset || clickedAway)
reset_tool();
}
else
reset_tool();
}
}
void EditTool::draw(SkCanvas* canvas, const DrawData& drawData) {
if(controls.isEditing) {
std::shared_ptr<DrawComponent> a = controls.compToEdit.lock();
if(a) {
for(HandleData& h : controls.pointHandles)
drawP.draw_drag_circle(canvas, drawData.cam.c.to_space((a->coords.from_space(*h.p))), {0.1f, 0.9f, 0.9f, 1.0f}, drawData);
}
}
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "../DrawData.hpp"
#include "../DrawComponents/DrawEllipse.hpp"
#include <Helpers/SCollision.hpp>
#include <any>
class DrawingProgram;
class EditTool {
public:
struct HandleData {
Vector2f* p;
Vector2f* min;
Vector2f* max;
};
EditTool(DrawingProgram& initDrawP);
void gui_toolbox();
void tool_update();
void reset_tool();
void draw(SkCanvas* canvas, const DrawData& drawData);
void add_point_handle(const HandleData& handle);
void edit_start(const std::shared_ptr<DrawComponent>& comp);
struct EditControls {
std::vector<HandleData> pointHandles;
std::weak_ptr<DrawComponent> compToEdit;
bool isEditing = false;
HandleData* pointDragging = nullptr;
std::any prevData;
} controls;
private:
DrawingProgram& drawP;
};

View file

@ -0,0 +1,156 @@
#include "EllipseDrawTool.hpp"
#include <chrono>
#include "DrawingProgram.hpp"
#include "../Server/CommandList.hpp"
#include "../MainProgram.hpp"
#include "../DrawData.hpp"
#include "Helpers/SCollision.hpp"
#include "Helpers/Serializers.hpp"
#include "../InputManager.hpp"
#include "../SharedTypes.hpp"
#include <cereal/types/vector.hpp>
EllipseDrawTool::EllipseDrawTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
controls.intermediateItem = std::make_shared<DrawEllipse>();
}
void EllipseDrawTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("ellipse draw tool");
t.gui.text_label_centered("Draw Ellipse");
if(t.gui.radio_button_field("fillonly", "Fill only", controls.fillStrokeMode == 0)) controls.fillStrokeMode = 0;
if(t.gui.radio_button_field("outlineonly", "Outline only", controls.fillStrokeMode == 1)) controls.fillStrokeMode = 1;
if(t.gui.radio_button_field("filloutline", "Fill and Outline", controls.fillStrokeMode == 2)) controls.fillStrokeMode = 2;
if(controls.fillStrokeMode == 1 || controls.fillStrokeMode == 2)
t.gui.slider_scalar_field("relstrokewidth", "Outline Size", &drawP.controls.relativeWidth, 3.0f, 40.0f);
t.gui.pop_id();
}
bool EllipseDrawTool::edit_gui(const std::shared_ptr<DrawEllipse>& a) {
DrawEllipse::Data oldData = a->d;
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("edit tool ellipse");
t.gui.text_label_centered("Edit Ellipse");
if(t.gui.radio_button_field("fillonly", "Fill only", a->d.fillStrokeMode == 0)) a->d.fillStrokeMode = 0;
if(t.gui.radio_button_field("outlineonly", "Outline only", a->d.fillStrokeMode == 1)) a->d.fillStrokeMode = 1;
if(t.gui.radio_button_field("filloutline", "Fill and Outline", a->d.fillStrokeMode == 2)) a->d.fillStrokeMode = 2;
if(a->d.fillStrokeMode == 0 || a->d.fillStrokeMode == 2) {
t.gui.left_to_right_line_layout([&]() {
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) {
if(t.gui.color_button("Fill Color", &a->d.fillColor, &a->d.fillColor == t.colorRight))
t.color_selector_right(&a->d.fillColor == t.colorRight ? nullptr : &a->d.fillColor);
}
t.gui.text_label("Fill Color");
});
}
if(a->d.fillStrokeMode == 1 || a->d.fillStrokeMode == 2) {
t.gui.slider_scalar_field("relstrokewidth", "Outline Size", &a->d.strokeWidth, 3.0f, 40.0f);
t.gui.left_to_right_line_layout([&]() {
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) {
if(t.gui.color_button("Outline Color", &a->d.strokeColor, &a->d.strokeColor == t.colorRight))
t.color_selector_right(&a->d.strokeColor == t.colorRight ? nullptr : &a->d.strokeColor);
}
t.gui.text_label("Outline Color");
});
}
t.gui.pop_id();
return a->d != oldData;
}
void EllipseDrawTool::edit_start(const std::shared_ptr<DrawEllipse>& a, std::any& prevData) {
prevData = a->d;
drawP.editTool.add_point_handle({&a->d.p1, nullptr, &a->d.p2});
drawP.editTool.add_point_handle({&a->d.p2, &a->d.p1, nullptr});
}
void EllipseDrawTool::commit_edit_updates(const std::shared_ptr<DrawEllipse>& a, std::any& prevData) {
DrawEllipse::Data pData = std::any_cast<DrawEllipse::Data>(prevData);
DrawEllipse::Data cData = a->d;
drawP.world.undo.push(UndoManager::UndoRedoPair{
[&, a, pData]() {
a->d = pData;
ServerClientID compID = drawP.components.get_id(a);
if(compID != ServerClientID{0, 0})
a->client_send_update_final(drawP, compID);
a->final_update(drawP);
drawP.reset_tools();
return true;
},
[&, a, cData]() {
ServerClientID compID = drawP.components.get_id(a);
a->d = cData;
if(compID != ServerClientID{0, 0})
a->client_send_update_final(drawP, compID);
a->final_update(drawP);
drawP.reset_tools();
return true;
}
});
}
void EllipseDrawTool::reset_tool() {
commit();
controls.drawStage = 0;
}
bool EllipseDrawTool::edit_update(const std::shared_ptr<DrawEllipse>& a) {
return true;
}
void EllipseDrawTool::tool_update() {
switch(controls.drawStage) {
case 0: {
if(drawP.controls.leftClick) {
controls.startAt = drawP.world.main.input.mouse.pos;
controls.intermediateItem = std::make_shared<DrawEllipse>();
controls.intermediateItem->d.strokeColor = drawP.controls.foregroundColor;
controls.intermediateItem->d.fillColor = drawP.controls.backgroundColor;
controls.intermediateItem->d.strokeWidth = drawP.controls.relativeWidth;
controls.intermediateItem->d.p1 = controls.startAt;
controls.intermediateItem->d.p2 = controls.startAt;
controls.intermediateItem->d.fillStrokeMode = static_cast<uint8_t>(controls.fillStrokeMode);
controls.intermediateItem->coords = drawP.world.drawData.cam.c;
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
uint64_t placement = drawP.components.client_list().size();
drawP.components.client_insert(placement, controls.intermediateItem);
controls.intermediateItem->client_send_place(drawP, placement);
drawP.add_undo_place_component(placement, controls.intermediateItem);
controls.intermediateItem->temp_update(drawP);
controls.drawStage = 1;
}
break;
}
case 1: {
if(drawP.controls.leftClickHeld) {
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
Vector2f newPos = controls.intermediateItem->coords.get_mouse_pos(drawP.world);
if(drawP.world.main.input.key(InputManager::KEY_EQUAL_DIMENSIONS).held) {
float height = std::fabs(controls.startAt.y() - newPos.y());
newPos.x() = controls.startAt.x() + (((newPos.x() - controls.startAt.x()) < 0.0f ? -1.0f : 1.0f) * height);
}
controls.intermediateItem->d.p1 = cwise_vec_min(controls.startAt, newPos);
controls.intermediateItem->d.p2 = cwise_vec_max(controls.startAt, newPos);
controls.intermediateItem->client_send_update_temp(drawP, drawP.components.get_id(controls.intermediateItem));
controls.intermediateItem->temp_update(drawP);
}
else {
commit();
controls.drawStage = 0;
}
break;
}
}
}
void EllipseDrawTool::commit() {
if(controls.intermediateItem) {
controls.intermediateItem->client_send_update_final(drawP, drawP.components.get_id(controls.intermediateItem));
controls.intermediateItem->final_update(drawP);
controls.intermediateItem = nullptr;
}
}
void EllipseDrawTool::draw(SkCanvas* canvas, const DrawData& drawData) {
}

View file

@ -0,0 +1,33 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "../DrawData.hpp"
#include "../DrawComponents/DrawEllipse.hpp"
#include <any>
#include <Helpers/SCollision.hpp>
class DrawingProgram;
class EllipseDrawTool {
public:
EllipseDrawTool(DrawingProgram& initDrawP);
void gui_toolbox();
void tool_update();
void reset_tool();
void draw(SkCanvas* canvas, const DrawData& drawData);
void edit_start(const std::shared_ptr<DrawEllipse>& a, std::any& prevData);
void commit_edit_updates(const std::shared_ptr<DrawEllipse>& a, std::any& prevData);
bool edit_update(const std::shared_ptr<DrawEllipse>& a);
bool edit_gui(const std::shared_ptr<DrawEllipse>& a);
private:
void commit();
struct EllipseDrawControls {
Vector2f startAt;
int fillStrokeMode = 1;
int drawStage = 0;
std::shared_ptr<DrawEllipse> intermediateItem;
} controls;
DrawingProgram& drawP;
};

View file

@ -0,0 +1,89 @@
#include "EraserTool.hpp"
#include "DrawingProgram.hpp"
#include "../Server/CommandList.hpp"
#include "../MainProgram.hpp"
#include "../DrawData.hpp"
#include "../SharedTypes.hpp"
#include <ranges>
#include "../DrawCollision.hpp"
EraserTool::EraserTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
}
void EraserTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("eraser tool");
t.gui.text_label_centered("Eraser");
t.gui.slider_scalar_field("relwidth", "Size", &drawP.controls.relativeWidth, 3.0f, 40.0f);
t.gui.pop_id();
}
void EraserTool::tool_update() {
if(drawP.controls.cursorHoveringOverCanvas)
drawP.world.main.input.hideCursor = true;
if(drawP.controls.leftClickHeld) {
const Vector2f& prevMousePos = drawP.controls.leftClick ? drawP.world.main.input.mouse.pos : drawP.world.main.input.mouse.lastPos;
SCollision::ColliderCollection<float> cC;
SCollision::generate_wide_line(cC, prevMousePos, drawP.world.main.input.mouse.pos, drawP.controls.relativeWidth * 2.0f, true);
drawP.check_all_collisions_transform(cC);
drawP.components.client_erase_if([&](uint64_t oldPlacement, const auto& c) {
if(c->obj->globalCollisionCheck) {
erasedComponents.emplace_back(oldPlacement, c->obj);
DrawComponent::client_send_erase(drawP, c->id);
return true;
}
return false;
});
}
else if(!erasedComponents.empty()) {
auto erasedCompVal = erasedComponents;
drawP.world.undo.push(UndoManager::UndoRedoPair{
[&, erasedCompVal]() {
for(auto& comp : erasedCompVal)
if(drawP.components.get_id(comp.second) != ServerClientID{0, 0})
return false;
for(auto& comp : erasedCompVal | std::views::reverse) {
drawP.components.client_insert(comp.first, comp.second);
comp.second->client_send_place(drawP, comp.first);
}
drawP.reset_tools();
return true;
},
[&, erasedCompVal]() {
for(auto& comp : erasedCompVal)
if(drawP.components.get_id(comp.second) == ServerClientID{0, 0})
return false;
for(auto& comp : erasedCompVal) {
ServerClientID compID;
drawP.components.client_erase(comp.second, compID);
DrawComponent::client_send_erase(drawP, compID);
}
drawP.reset_tools();
return true;
}
});
erasedComponents.clear();
}
}
void EraserTool::draw(SkCanvas* canvas, const DrawData& drawData) {
if(!drawData.main->toolbar.io->hoverObstructed) {
SkColor4f c = drawP.world.main.canvasTheme.toolFrontColor;
c.fA = 0.5f;
SkPaint linePaint;
linePaint.setColor4f(c);
linePaint.setStyle(SkPaint::kStroke_Style);
linePaint.setStrokeCap(SkPaint::kRound_Cap);
linePaint.setStrokeWidth(drawP.controls.relativeWidth * 2.0f);
SkPath erasePath;
erasePath.moveTo(convert_vec2<SkPoint>(drawData.main->input.mouse.lastPos));
erasePath.lineTo(convert_vec2<SkPoint>(drawData.main->input.mouse.pos));
canvas->drawPath(erasePath, linePaint);
}
}

View file

@ -0,0 +1,19 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "../DrawData.hpp"
#include "../DrawComponents/DrawComponent.hpp"
class DrawingProgram;
class EraserTool {
public:
EraserTool(DrawingProgram& initDrawP);
void gui_toolbox();
void tool_update();
void draw(SkCanvas* canvas, const DrawData& drawData);
private:
std::vector<std::pair<uint64_t, std::shared_ptr<DrawComponent>>> erasedComponents; // <placement order, object>
DrawingProgram& drawP;
};

View file

@ -0,0 +1,54 @@
#include "ImageTool.hpp"
#include "DrawingProgram.hpp"
#include "../World.hpp"
#include "../Toolbar.hpp"
#include "../MainProgram.hpp"
#include "../ResourceManager.hpp"
#include <fstream>
#ifdef __EMSCRIPTEN__
#include <EmscriptenHelpers/emscripten_browser_file.h>
#endif
ImageTool::ImageTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{}
void ImageTool::edit_start(const std::shared_ptr<DrawImage>& a, std::any& prevData) {
}
void ImageTool::commit_edit_updates(const std::shared_ptr<DrawImage>& a, std::any& prevData) {
}
bool ImageTool::edit_update(const std::shared_ptr<DrawImage>& a) {
return true;
}
bool ImageTool::edit_gui(const std::shared_ptr<DrawImage>& a) {
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("edit tool image");
t.gui.text_label_centered("File Properties");
std::optional<ResourceData> resourceData = drawP.world.rMan.get_resource(a->d.imageID);
if(resourceData) {
t.gui.text_label("Name: " + resourceData->name);
if(t.gui.text_button_wide("file download", "Download file")) {
#ifdef __EMSCRIPTEN__
emscripten_browser_file::download(
resourceData->name,
"application/octet-binary",
*resourceData->data
);
#endif
t.open_file_selector("Download File", {{"Any File", "*"}}, [resourceData](const std::filesystem::path& p, const auto& e) {
std::ofstream f(p, std::ios::binary);
if(f.is_open()) {
f << *resourceData->data;
f.close();
}
}, resourceData->name, true);
}
}
else
t.gui.text_label_centered("Loading resource...");
t.gui.pop_id();
return false;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "../DrawData.hpp"
#include "../DrawComponents/DrawRectangle.hpp"
#include "../DrawComponents/DrawImage.hpp"
#include <Helpers/SCollision.hpp>
#include <any>
class DrawingProgram;
class ImageTool {
public:
ImageTool(DrawingProgram& initDrawP);
void edit_start(const std::shared_ptr<DrawImage>& a, std::any& prevData);
void commit_edit_updates(const std::shared_ptr<DrawImage>& a, std::any& prevData);
bool edit_update(const std::shared_ptr<DrawImage>& a);
bool edit_gui(const std::shared_ptr<DrawImage>& a);
private:
struct ImageControls {
} controls;
DrawingProgram& drawP;
};

View file

@ -0,0 +1,77 @@
#include "InkDropperTool.hpp"
#include "DrawingProgram.hpp"
#include "../Server/CommandList.hpp"
#include "../MainProgram.hpp"
#include "../DrawData.hpp"
#include <include/core/SkAlphaType.h>
#include <include/core/SkCanvas.h>
#include <include/core/SkColor.h>
#include <include/core/SkColorType.h>
#include <include/core/SkRRect.h>
#include <include/core/SkRect.h>
#include <include/core/SkColorSpace.h>
#include <include/core/SkStream.h>
#include <include/core/SkSurface.h>
#ifdef USE_SKIA_BACKEND_GRAPHITE
#include <include/gpu/graphite/Surface.h>
#elif USE_SKIA_BACKEND_GANESH
#include <include/gpu/ganesh/GrDirectContext.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#endif
#include <Helpers/Logger.hpp>
InkDropperTool::InkDropperTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
}
void InkDropperTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("Color select tool");
t.gui.text_label_centered("Color Select");
t.gui.pop_id();
}
void InkDropperTool::tool_update() {
if(drawP.controls.leftClick) {
SkSurfaceProps props;
SkImageInfo imgInfo = SkImageInfo::MakeN32Premul(drawP.world.main.window.size.x(), drawP.world.main.window.size.y());
#ifdef USE_SKIA_BACKEND_GRAPHITE
sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(drawP.world.main.window.recorder(), imgInfo, skgpu::Mipmapped::kNo, &props);
#elif USE_SKIA_BACKEND_GANESH
sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(drawP.world.main.window.ctx.get(), skgpu::Budgeted::kNo, imgInfo, 0, &props);
#endif
if(!surface) {
Logger::get().log("INFO", "[SkSurfaces::WrapBackendRenderTarget] Ink Dropper Tool could not make surface");
return;
}
SkCanvas* inkDropperCanvas = surface->getCanvas();
if(!inkDropperCanvas) {
Logger::get().log("INFO", "Ink Dropper Tool could not make canvas");
return;
}
int xPos = std::clamp<int>(drawP.world.main.input.mouse.pos.x(), 0, drawP.world.main.window.size.x() - 1);
int yPos = std::clamp<int>(drawP.world.main.input.mouse.pos.y(), 0, drawP.world.main.window.size.y() - 1);
inkDropperCanvas->save();
drawP.world.drawData.dontUseDrawProgCache = true;
drawP.world.main.draw(inkDropperCanvas);
drawP.world.drawData.dontUseDrawProgCache = false;
inkDropperCanvas->restore();
//drawP.world.main.window.ctx->flush();
Vector4<uint8_t> readData;
SkImageInfo readDataInfo = SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
#ifdef USE_SKIA_BACKEND_GRAPHITE
// NOT WORKING IN GRAPHITE YET
//surface->makeImageSnapshot()->readPixels(drawP.world.main.window.ctx.get(), readDataInfo, (void*)readData.data(), 4, xPos, yPos);
#elif USE_SKIA_BACKEND_GANESH
surface->makeImageSnapshot()->readPixels(drawP.world.main.window.ctx.get(), readDataInfo, (void*)readData.data(), 4, xPos, yPos);
#endif
drawP.controls.foregroundColor = readData.cast<float>() / 255.0f;
}
}
void InkDropperTool::draw(SkCanvas* canvas, const DrawData& drawData) {
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "../DrawData.hpp"
#include <Helpers/SCollision.hpp>
class DrawingProgram;
class InkDropperTool {
public:
InkDropperTool(DrawingProgram& initDrawP);
void gui_toolbox();
void tool_update();
void draw(SkCanvas* canvas, const DrawData& drawData);
private:
DrawingProgram& drawP;
};

View file

@ -0,0 +1,157 @@
#include "RectDrawTool.hpp"
#include "DrawingProgram.hpp"
#include "../Server/CommandList.hpp"
#include "../MainProgram.hpp"
#include "../DrawData.hpp"
#include "Helpers/SCollision.hpp"
#include "Helpers/Serializers.hpp"
#include "../SharedTypes.hpp"
#include <cereal/types/vector.hpp>
RectDrawTool::RectDrawTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
controls.intermediateItem = std::make_shared<DrawRectangle>();
}
void RectDrawTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("rect draw tool");
t.gui.text_label_centered("Draw Rectangle");
t.gui.slider_scalar_field("relradiuswidth", "Corner Radius", &controls.relativeRadiusWidth, 0.0f, 40.0f);
if(t.gui.radio_button_field("fillonly", "Fill only", controls.fillStrokeMode == 0)) controls.fillStrokeMode = 0;
if(t.gui.radio_button_field("outlineonly", "Outline only", controls.fillStrokeMode == 1)) controls.fillStrokeMode = 1;
if(t.gui.radio_button_field("filloutline", "Fill and Outline", controls.fillStrokeMode == 2)) controls.fillStrokeMode = 2;
if(controls.fillStrokeMode == 1 || controls.fillStrokeMode == 2)
t.gui.slider_scalar_field("relstrokewidth", "Outline Size", &drawP.controls.relativeWidth, 3.0f, 40.0f);
t.gui.pop_id();
}
bool RectDrawTool::edit_gui(const std::shared_ptr<DrawRectangle>& a) {
DrawRectangle::Data oldData = a->d;
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("edit tool rectangle");
t.gui.text_label_centered("Edit Rectangle");
t.gui.slider_scalar_field("relradiuswidth", "Corner Radius", &a->d.cornerRadius, 0.0f, 40.0f);
if(t.gui.radio_button_field("fillonly", "Fill only", a->d.fillStrokeMode == 0)) a->d.fillStrokeMode = 0;
if(t.gui.radio_button_field("outlineonly", "Outline only", a->d.fillStrokeMode == 1)) a->d.fillStrokeMode = 1;
if(t.gui.radio_button_field("filloutline", "Fill and Outline", a->d.fillStrokeMode == 2)) a->d.fillStrokeMode = 2;
if(a->d.fillStrokeMode == 0 || a->d.fillStrokeMode == 2) {
t.gui.left_to_right_line_layout([&]() {
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) {
if(t.gui.color_button("Fill Color", &a->d.fillColor, &a->d.fillColor == t.colorRight))
t.color_selector_right(&a->d.fillColor == t.colorRight ? nullptr : &a->d.fillColor);
}
t.gui.text_label("Fill Color");
});
}
if(a->d.fillStrokeMode == 1 || a->d.fillStrokeMode == 2) {
t.gui.slider_scalar_field("relstrokewidth", "Outline Size", &a->d.strokeWidth, 3.0f, 40.0f);
t.gui.left_to_right_line_layout([&]() {
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) {
if(t.gui.color_button("Outline Color", &a->d.strokeColor, &a->d.strokeColor == t.colorRight))
t.color_selector_right(&a->d.strokeColor == t.colorRight ? nullptr : &a->d.strokeColor);
}
t.gui.text_label("Outline Color");
});
}
t.gui.pop_id();
return a->d != oldData;
}
void RectDrawTool::reset_tool() {
commit_rectangle();
controls.drawStage = 0;
}
void RectDrawTool::edit_start(const std::shared_ptr<DrawRectangle>& a, std::any& prevData) {
prevData = a->d;
drawP.editTool.add_point_handle({&a->d.p1, nullptr, &a->d.p2});
drawP.editTool.add_point_handle({&a->d.p2, &a->d.p1, nullptr});
}
void RectDrawTool::commit_edit_updates(const std::shared_ptr<DrawRectangle>& a, std::any& prevData) {
DrawRectangle::Data pData = std::any_cast<DrawRectangle::Data>(prevData);
DrawRectangle::Data cData = a->d;
drawP.world.undo.push(UndoManager::UndoRedoPair{
[&, a, pData]() {
a->d = pData;
ServerClientID compID = drawP.components.get_id(a);
if(compID != ServerClientID{0, 0})
a->client_send_update_final(drawP, compID);
a->final_update(drawP);
drawP.reset_tools();
return true;
},
[&, a, cData]() {
ServerClientID compID = drawP.components.get_id(a);
a->d = cData;
if(compID != ServerClientID{0, 0})
a->client_send_update_final(drawP, compID);
a->final_update(drawP);
drawP.reset_tools();
return true;
}
});
}
bool RectDrawTool::edit_update(const std::shared_ptr<DrawRectangle>& a) {
return true;
}
void RectDrawTool::tool_update() {
switch(controls.drawStage) {
case 0: {
if(drawP.controls.leftClick) {
controls.startAt = drawP.world.main.input.mouse.pos;
controls.intermediateItem = std::make_shared<DrawRectangle>();
controls.intermediateItem->d.strokeColor = drawP.controls.foregroundColor;
controls.intermediateItem->d.fillColor = drawP.controls.backgroundColor;
controls.intermediateItem->d.cornerRadius = controls.relativeRadiusWidth;
controls.intermediateItem->d.strokeWidth = drawP.controls.relativeWidth;
controls.intermediateItem->d.p1 = controls.startAt;
controls.intermediateItem->d.p2 = controls.startAt;
controls.intermediateItem->d.fillStrokeMode = static_cast<uint8_t>(controls.fillStrokeMode);
controls.intermediateItem->coords = drawP.world.drawData.cam.c;
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
controls.intermediateItem->temp_update(drawP);
uint64_t placement = drawP.components.client_list().size();
controls.intermediateID = drawP.components.client_insert(placement, controls.intermediateItem);
controls.intermediateItem->client_send_place(drawP, placement);
drawP.add_undo_place_component(placement, controls.intermediateItem);
controls.drawStage = 1;
}
break;
}
case 1: {
if(drawP.controls.leftClickHeld) {
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
Vector2f newPos = controls.intermediateItem->coords.get_mouse_pos(drawP.world);
if(drawP.world.main.input.key(InputManager::KEY_EQUAL_DIMENSIONS).held) {
float height = std::fabs(controls.startAt.y() - newPos.y());
newPos.x() = controls.startAt.x() + (((newPos.x() - controls.startAt.x()) < 0.0f ? -1.0f : 1.0f) * height);
}
controls.intermediateItem->d.p1 = cwise_vec_min(controls.startAt, newPos);
controls.intermediateItem->d.p2 = cwise_vec_max(controls.startAt, newPos);
controls.intermediateItem->client_send_update_temp(drawP, controls.intermediateID);
controls.intermediateItem->temp_update(drawP);
}
else {
commit_rectangle();
controls.drawStage = 0;
}
break;
}
}
}
void RectDrawTool::commit_rectangle() {
if(controls.intermediateItem) {
controls.intermediateItem->client_send_update_final(drawP, controls.intermediateID);
controls.intermediateItem->final_update(drawP);
controls.intermediateItem = nullptr;
}
}
void RectDrawTool::draw(SkCanvas* canvas, const DrawData& drawData) {
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "../DrawData.hpp"
#include "../DrawComponents/DrawRectangle.hpp"
#include <Helpers/SCollision.hpp>
#include <any>
class DrawingProgram;
class RectDrawTool {
public:
RectDrawTool(DrawingProgram& initDrawP);
void gui_toolbox();
void tool_update();
void reset_tool();
void draw(SkCanvas* canvas, const DrawData& drawData);
void edit_start(const std::shared_ptr<DrawRectangle>& a, std::any& prevData);
void commit_edit_updates(const std::shared_ptr<DrawRectangle>& a, std::any& prevData);
bool edit_update(const std::shared_ptr<DrawRectangle>& a);
bool edit_gui(const std::shared_ptr<DrawRectangle>& a);
private:
void commit_rectangle();
struct RectDrawControls {
Vector2f startAt;
float relativeRadiusWidth = 10.0f;
int fillStrokeMode = 1;
int drawStage = 0;
std::shared_ptr<DrawRectangle> intermediateItem;
ServerClientID intermediateID;
} controls;
DrawingProgram& drawP;
};

View file

@ -0,0 +1,398 @@
#include "RectSelectTool.hpp"
#include <chrono>
#include "DrawingProgram.hpp"
#include "../Server/CommandList.hpp"
#include "../MainProgram.hpp"
#include "../DrawData.hpp"
#include "Helpers/FixedPoint.hpp"
#include "Helpers/MathExtras.hpp"
#include "Helpers/SCollision.hpp"
#include "Helpers/Serializers.hpp"
#include "../CoordSpaceHelper.hpp"
#include <cereal/types/vector.hpp>
#include <algorithm>
#include <ranges>
#define ROTATION_POINT_RADIUS_MULTIPLIER 0.7
#define ROTATION_POINTS_DISTANCE 20.0
RectSelectTool::RectSelectTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
}
void RectSelectTool::gui_toolbox() {
drawP.world.main.toolbar.gui.text_label_centered("Rectangle Select");
}
void RectSelectTool::reset_tool() {
if(controls.selectionMode == 3)
commit_transformation();
controls = RectSelectControls();
}
void RectSelectTool::tool_update() {
switch(controls.selectionMode) {
case 0: {
if(drawP.controls.leftClick) {
reset_tool();
controls.coords = drawP.world.drawData.cam.c;
controls.selectStartAt = controls.coords.get_mouse_pos(drawP.world);
controls.rotationPointAngle = 0.0;
controls.selectionMode = 1;
}
break;
}
case 1: {
using namespace SCollision;
Vector2f mPos = controls.coords.get_mouse_pos(drawP.world);
Vector2f newP1 = cwise_vec_min(controls.selectStartAt, mPos);
Vector2f newP2 = cwise_vec_max(controls.selectStartAt, mPos);
controls.newT = triangle_from_rect_points(newP1, newP2);
if(!drawP.controls.leftClickHeld) {
ColliderCollection<float> selectionCollection;
selectionCollection.triangle.emplace_back(controls.newT[0], controls.newT[1], controls.newT[2]);
selectionCollection.triangle.emplace_back(controls.newT[2], controls.newT[3], controls.newT[0]);
drawP.check_all_collisions_transform(selectionCollection);
for(auto& c : drawP.components.client_list())
c->obj->selected = c->obj->globalCollisionCheck;
controls.selectionMode = create_selection_aabb() ? 2 : 0;
controls.rotationCenter = controls.finalSelectionAABB.center();
}
break;
}
case 2: {
Vector2f mPos = drawP.world.main.input.mouse.pos;
bool copyHit = drawP.world.main.input.key(InputManager::KEY_COPY).pressed;
bool cutHit = drawP.world.main.input.key(InputManager::KEY_CUT).pressed;
bool deleteHit = drawP.world.main.input.key(InputManager::KEY_DRAW_DELETE).pressed;
bool unselectHit = drawP.world.main.input.key(InputManager::KEY_DRAW_UNSELECT).pressed;
if(drawP.controls.leftClick) {
if(SCollision::collide(mPos, controls.scaleSelectionCircle)) {
controls.scaleStartAt = controls.finalSelectionAABB.max;
controls.scaleCenterPoint = controls.finalSelectionAABB.center();
set_selected_items();
controls.selectionMode = 4;
}
else if(SCollision::collide(mPos, controls.rotationCenterCircle)) {
controls.moveStartAt = controls.rotationCenter;
controls.selectionMode = 5;
}
else if(SCollision::collide(mPos, controls.rotationPointCircle)) {
set_selected_items();
controls.selectionMode = 6;
}
else if(SCollision::collide(drawP.controls.currentMouseWorldPos, controls.finalSelectionAABB)) {
controls.moveStartAt = drawP.controls.currentMouseWorldPos;
set_selected_items();
controls.selectionMode = 3;
}
else {
controls.selectionMode = 0;
drawP.reset_selection();
}
}
else if(deleteHit || cutHit) {
selection_to_clipboard();
std::vector<std::pair<uint64_t, std::shared_ptr<DrawComponent>>> erasedCompVal; // <placement order, object>
drawP.components.client_erase_if([&](uint64_t oldPlacement, const auto& c) {
if(c->obj->selected) {
erasedCompVal.emplace_back(std::pair<uint64_t, std::shared_ptr<DrawComponent>>{oldPlacement, c->obj});
DrawComponent::client_send_erase(drawP, c->id);
return true;
}
return false;
});
drawP.world.undo.push(UndoManager::UndoRedoPair{
[&, erasedCompVal]() {
for(auto& comp : erasedCompVal)
if(drawP.components.get_id(comp.second) != ServerClientID{0, 0})
return false;
for(auto& comp : erasedCompVal | std::views::reverse) {
drawP.components.client_insert(comp.first, comp.second);
comp.second->client_send_place(drawP, comp.first);
}
drawP.reset_tools();
return true;
},
[&, erasedCompVal]() {
for(auto& comp : erasedCompVal)
if(drawP.components.get_id(comp.second) == ServerClientID{0, 0})
return false;
for(auto& comp : erasedCompVal) {
ServerClientID compID;
drawP.components.client_erase(comp.second, compID);
DrawComponent::client_send_erase(drawP, compID);
}
drawP.reset_tools();
return true;
}
});
controls.selectionMode = 0;
}
else if(copyHit)
selection_to_clipboard();
else if(unselectHit) {
drawP.reset_selection();
controls.selectionMode = 0;
}
break;
}
case 3: {
WorldVec translationVec = drawP.controls.currentMouseWorldPos - controls.moveStartAt;
if(drawP.controls.leftClickHeld) {
for(auto& item : controls.selectedItems) {
auto newCoords = item.oldCoords;
newCoords.translate(translationVec);
item.comp->coords = newCoords;
item.comp->updateDraw = true;
item.comp->client_send_transform_temp(drawP, item.id);
item.comp->lastTransformTime = std::chrono::steady_clock::now();
}
}
else {
commit_transformation();
controls.selectionMode = create_selection_aabb() ? 2 : 0;
controls.rotationCenter = controls.finalSelectionAABB.center();
}
break;
}
case 4: {
WorldVec centerToScaleStart = controls.scaleStartAt - controls.scaleCenterPoint;
Vector2f centerToScaleStartInCamSpace = drawP.world.drawData.cam.c.normalized_dir_to_space(centerToScaleStart.normalized());
Vector2f scaleCenterPointInCamSpace = drawP.world.drawData.cam.c.to_space(controls.scaleCenterPoint);
controls.scaleCurrentPoint = drawP.world.drawData.cam.c.from_space(project_point_on_line(drawP.world.main.input.mouse.pos, scaleCenterPointInCamSpace, (scaleCenterPointInCamSpace + centerToScaleStartInCamSpace).eval()));
WorldVec centerToScaleCurrent = controls.scaleCurrentPoint - controls.scaleCenterPoint;
WorldScalar centerToScaleStartNorm = centerToScaleStart.norm();
WorldScalar centerToScaleCurrentNorm = centerToScaleCurrent.norm();
bool isAnyNumberZero = centerToScaleCurrentNorm == WorldScalar(0) || centerToScaleStartNorm == WorldScalar(0);
if(!isAnyNumberZero) {
bool flipScale = centerToScaleStartNorm < centerToScaleCurrentNorm;
WorldScalar scaleAmount;
if(flipScale)
scaleAmount = centerToScaleCurrentNorm / centerToScaleStartNorm;
else
scaleAmount = centerToScaleStartNorm / centerToScaleCurrentNorm;
if(drawP.controls.leftClickHeld) {
for(auto& item : controls.selectedItems) {
auto newCoords = item.oldCoords;
newCoords.scale_about(controls.scaleCenterPoint, scaleAmount, flipScale);
item.comp->coords = newCoords;
item.comp->client_send_transform_temp(drawP, item.id);
item.comp->updateDraw = true;
item.comp->lastTransformTime = std::chrono::steady_clock::now();
}
}
else {
commit_transformation();
controls.selectionMode = create_selection_aabb() ? 2 : 0;
controls.rotationCenter = controls.finalSelectionAABB.center();
}
}
break;
}
case 5: {
controls.rotationCenter = drawP.controls.currentMouseWorldPos;
if(!drawP.controls.leftClickHeld) {
controls.selectionMode = 2;
}
break;
}
case 6: {
Vector2f rotationPointDiff = drawP.world.main.input.mouse.pos - drawP.world.drawData.cam.c.to_space(controls.rotationCenter);
controls.rotationPointAngle = std::atan2(rotationPointDiff.y(), rotationPointDiff.x());
if(drawP.controls.leftClickHeld) {
for(auto& item : controls.selectedItems) {
auto newCoords = item.oldCoords;
newCoords.rotate_about(controls.rotationCenter, controls.rotationPointAngle);
item.comp->coords = newCoords;
item.comp->updateDraw = true;
item.comp->client_send_transform_temp(drawP, item.id);
item.comp->lastTransformTime = std::chrono::steady_clock::now();
}
}
else {
commit_transformation();
controls.rotationPointAngle = 0.0;
controls.selectionMode = create_selection_aabb() ? 2 : 0;
}
break;
}
}
update_drag_circles();
}
void RectSelectTool::paste_clipboard() {
auto& clipboard = drawP.world.main.clipboard;
if(!clipboard.components.empty()) {
drawP.reset_tools();
drawP.controls.selectedTool = DrawingProgram::TOOL_RECTSELECT;
drawP.controls.previousSelected = DrawingProgram::TOOL_RECTSELECT;
uint64_t allPlacement = drawP.components.client_list().size();
std::vector<std::shared_ptr<DrawComponent>> placedComponents;
WorldVec mousePos = drawP.world.get_mouse_world_pos();
WorldVec moveVec = clipboard.pos - mousePos;
std::unordered_map<ServerClientID, ServerClientID> resourceRemapIDs;
for(auto& r : clipboard.resources)
resourceRemapIDs[r.first] = drawP.world.rMan.add_resource(r.second);
for(auto& c : clipboard.components) {
uint64_t placement = drawP.components.client_list().size();
std::shared_ptr<DrawComponent> newC = c->copy();
newC->remap_resource_ids(resourceRemapIDs);
newC->coords.translate(-moveVec);
newC->selected = true;
drawP.components.client_insert(placement, newC);
newC->final_update(drawP);
newC->client_send_place(drawP, placement);
placedComponents.emplace_back(newC);
}
set_selected_items();
create_selection_aabb();
controls.rotationCenter = controls.finalSelectionAABB.center();
controls.selectionMode = 2;
drawP.add_undo_place_components(allPlacement, placedComponents);
}
}
void RectSelectTool::selection_to_clipboard() {
auto& clipboard = drawP.world.main.clipboard;
clipboard.components.clear();
std::unordered_set<ServerClientID> resourceSet;
for(auto& c : drawP.components.client_list()) {
if(c->obj->selected) {
c->obj->get_used_resources(resourceSet);
clipboard.components.emplace_back(c->obj->copy());
}
}
drawP.world.rMan.copy_resource_set_to_map(resourceSet, clipboard.resources);
clipboard.pos = controls.finalSelectionAABB.center();
}
Vector2f RectSelectTool::get_rotation_point_pos_from_angle(double angle) {
return Vector2f{drawP.world.drawData.cam.c.to_space(controls.rotationCenter) + ROTATION_POINTS_DISTANCE * Vector2f{std::cos(angle), std::sin(angle)}};
}
void RectSelectTool::update_drag_circles() {
controls.scaleSelectionCircle = SCollision::Circle<float>(drawP.world.drawData.cam.c.to_space(controls.scaleCurrentPoint), DRAG_POINT_RADIUS);
controls.rotationCenterCircle = SCollision::Circle<float>(drawP.world.drawData.cam.c.to_space(controls.rotationCenter), DRAG_POINT_RADIUS);
controls.rotationPoint = get_rotation_point_pos_from_angle(controls.rotationPointAngle);
controls.rotationPointCircle = SCollision::Circle<float>(controls.rotationPoint, ROTATION_POINT_RADIUS_MULTIPLIER * DRAG_POINT_RADIUS);
}
bool RectSelectTool::create_selection_aabb() {
controls.coords = drawP.world.drawData.cam.c;
controls.finalSelectionAABB = SCollision::AABB<WorldScalar>();
bool first = true;
for(auto& c : drawP.components.client_list()) {
if(c->obj->selected && c->obj->get_world_bounds()) {
if(first) {
controls.finalSelectionAABB = *c->obj->get_world_bounds();
first = false;
}
else
controls.finalSelectionAABB.include_aabb_in_bounds(*c->obj->get_world_bounds());
}
}
controls.scaleCurrentPoint = controls.finalSelectionAABB.max;
return !first;
}
void RectSelectTool::set_selected_items() {
controls.selectedItems.clear();
for(auto& c : drawP.components.client_list()) {
if(c->obj->selected) {
controls.selectedItems.emplace_back(c->obj, c->id, c->obj->coords);
c->obj->lastTransformTime = std::chrono::steady_clock::now();
}
}
}
void RectSelectTool::commit_transformation() {
std::vector<CoordSpaceHelper> finalCoords;
for(auto& item : controls.selectedItems) {
item.comp->client_send_transform_final(drawP, item.id);
item.comp->finalize_update(drawP.colliderAllocated);
finalCoords.emplace_back(item.comp->coords);
}
auto sItems = controls.selectedItems;
drawP.world.undo.push(UndoManager::UndoRedoPair{
[&, sItems, finalCoords]() {
for(auto& item : sItems) {
item.comp->coords = item.oldCoords;
item.comp->client_send_transform_final(drawP, drawP.components.get_id(item.comp));
item.comp->finalize_update(drawP.colliderAllocated);
}
drawP.reset_tools();
return true;
},
[&, sItems, finalCoords]() {
for(auto [item, finalCoord] : std::views::zip(sItems, finalCoords)) {
item.comp->coords = finalCoord;
item.comp->client_send_transform_final(drawP, drawP.components.get_id(item.comp));
item.comp->finalize_update(drawP.colliderAllocated);
}
drawP.reset_tools();
return true;
}
});
}
void RectSelectTool::draw(SkCanvas* canvas, const DrawData& drawData) {
if(controls.selectionMode != 0) {
canvas->save();
controls.coords.transform_sk_canvas(canvas, drawData);
SkPaint selectionPaint;
selectionPaint.setColor4f({0.3f, 0.6f, 0.9f, 0.4f});
if(controls.selectionMode == 1) {
SkRect r = SkRect::MakeLTRB(controls.newT[0].x(), controls.newT[0].y(), controls.newT[2].x(), controls.newT[2].y());
canvas->drawRect(r, selectionPaint);
canvas->restore();
}
if(controls.selectionMode == 2 || controls.selectionMode == 5) {
auto triArray = triangle_from_rect_points(controls.finalSelectionAABB.min, controls.finalSelectionAABB.max);
std::array<Vector2f, 4> triArray2;
triArray2[0] = controls.coords.to_space(controls.finalSelectionAABB.min);
triArray2[1] = controls.coords.to_space(controls.finalSelectionAABB.top_right());
triArray2[2] = controls.coords.to_space(controls.finalSelectionAABB.bottom_left());
triArray2[3] = controls.coords.to_space(controls.finalSelectionAABB.max);
SkPath a;
a.moveTo(triArray2[0].x(), triArray2[0].y());
a.lineTo(triArray2[1].x(), triArray2[1].y());
a.lineTo(triArray2[2].x(), triArray2[2].y());
a.close();
a.moveTo(triArray2[1].x(), triArray2[1].y());
a.lineTo(triArray2[2].x(), triArray2[2].y());
a.lineTo(triArray2[3].x(), triArray2[3].y());
a.close();
canvas->drawPath(a, selectionPaint);
// SkPaint rotateCirclePaint(SkColor4f{0.9f, 0.5f, 0.1f, 1.0f});
}
canvas->restore();
if(controls.selectionMode == 6 || controls.selectionMode == 5 || controls.selectionMode == 2) {
drawP.draw_drag_circle(canvas, controls.rotationPoint, {0.9f, 0.5f, 0.1f, 1.0f}, drawData, ROTATION_POINT_RADIUS_MULTIPLIER);
drawP.draw_drag_circle(canvas, drawData.cam.c.to_space(controls.rotationCenter), {0.9f, 0.5f, 0.1f, 1.0f}, drawData);
}
if(controls.selectionMode == 4 || controls.selectionMode == 2) {
drawP.draw_drag_circle(canvas, drawData.cam.c.to_space(controls.scaleCurrentPoint), {0.1f, 0.9f, 0.9f, 1.0f}, drawData);
if(controls.selectionMode == 4)
drawP.draw_drag_circle(canvas, drawData.cam.c.to_space(controls.scaleCenterPoint), {0.0f, 0.0f, 0.0f, 0.0f}, drawData);
}
}
}

View file

@ -0,0 +1,58 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "../DrawData.hpp"
#include <Helpers/SCollision.hpp>
#include "../CoordSpaceHelper.hpp"
#include "../DrawComponents/DrawComponent.hpp"
class DrawingProgram;
class RectSelectTool {
public:
RectSelectTool(DrawingProgram& initDrawP);
void gui_toolbox();
void tool_update();
void draw(SkCanvas* canvas, const DrawData& drawData);
void reset_tool();
void paste_clipboard();
private:
struct RectSelectControls {
int selectionMode = 0;
Vector2f selectStartAt;
CoordSpaceHelper coords;
WorldVec moveStartAt;
SCollision::AABB<WorldScalar> finalSelectionAABB;
std::array<Vector2f, 4> newT;
WorldVec scaleStartAt;
WorldVec scaleCenterPoint;
WorldVec scaleCurrentPoint;
WorldVec rotationCenter;
// Temporary, per frame colliders for handles
Vector2f rotationPoint;
SCollision::Circle<float> scaleSelectionCircle;
SCollision::Circle<float> rotationCenterCircle;
SCollision::Circle<float> rotationPointCircle;
double rotationPointAngle;
struct SelectedItemInfo {
std::shared_ptr<DrawComponent> comp;
ServerClientID id;
CoordSpaceHelper oldCoords;
};
std::vector<SelectedItemInfo> selectedItems;
} controls;
void selection_to_clipboard();
Vector2f get_rotation_point_pos_from_angle(double angle);
void update_drag_circles();
void set_selected_items();
bool create_selection_aabb();
void commit_transformation();
DrawingProgram& drawP;
};

View file

@ -0,0 +1,356 @@
#include "ScreenshotTool.hpp"
#include "DrawingProgram.hpp"
#include "../Server/CommandList.hpp"
#include "../MainProgram.hpp"
#include "../DrawData.hpp"
#include "Helpers/SCollision.hpp"
#include <include/core/SkAlphaType.h>
#include <include/core/SkColorType.h>
#include <include/core/SkPaint.h>
#include <include/core/SkPathTypes.h>
#include <include/core/SkStream.h>
#include <include/core/SkSurface.h>
#include <include/core/SkImage.h>
#include <include/core/SkData.h>
#include <include/encode/SkPngEncoder.h>
#include <include/encode/SkWebpEncoder.h>
#include <include/encode/SkJpegEncoder.h>
#include <include/gpu/GpuTypes.h>
#include "../FileHelpers.hpp"
#include <filesystem>
#ifdef USE_SKIA_BACKEND_GRAPHITE
#include <include/gpu/graphite/Surface.h>
#elif USE_SKIA_BACKEND_GANESH
#include <include/gpu/ganesh/GrDirectContext.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#endif
#include "../GridManager.hpp"
#include <Helpers/Logger.hpp>
#include "../MainProgram.hpp"
#ifdef __EMSCRIPTEN__
#include <EmscriptenHelpers/emscripten_browser_file.h>
#endif
#define SECTION_SIZE 4000
#define AA_LEVEL 4
ScreenshotTool::ScreenshotTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
}
void ScreenshotTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("screenshot tool");
t.gui.text_label_centered("Screenshot");
auto oldImgSize = controls.imageSize;
if(controls.selectionMode == 2) {
t.gui.input_scalar_fields("Image Size", "Image Size", &controls.imageSize, 2, 0, 999999999);
t.gui.left_to_right_line_layout([&]() {
t.gui.text_label("Image Type");
t.gui.dropdown_select("image type select", &controls.selectedType, controls.typeSelections);
});
t.gui.checkbox_field("Display Grid", "Display Grid", &controls.displayGrid);
if(controls.selectedType != 0)
t.gui.checkbox_field("Transparent Background", "Transparent Background", &controls.transparentBackground);
if(controls.imageSize.x() != oldImgSize.x())
controls.imageSize.y() = ((double)controls.imageSize.x() / oldImgSize.x()) * controls.imageSize.y();
else if(controls.imageSize.y() != oldImgSize.y())
controls.imageSize.x() = ((double)controls.imageSize.y() / oldImgSize.y()) * controls.imageSize.x();
if(t.gui.text_button_wide("Take Screenshot", "Take Screenshot")) {
controls.selectionMode = 0;
#ifdef __EMSCRIPTEN__
take_screenshot("a" + controls.typeSelections[controls.selectedType]);
#else
// We can't actually use the extension from the callback, so we have to set the extension of choice beforehand
Toolbar::ExtensionFilter setExtensionFilter;
if(controls.selectedType == 0)
setExtensionFilter = {"JPEG", "jpg;jpeg"};
else if(controls.selectedType == 1)
setExtensionFilter = {"PNG", "png"};
else if(controls.selectedType == 2)
setExtensionFilter = {"WebP", "webp"};
t.open_file_selector("Export", {setExtensionFilter}, [setExtensionFilter, w = make_weak_ptr(drawP.world.main.world)](const std::filesystem::path& p, const auto& e) {
auto world = w.lock();
if(world) {
world->drawProg.screenshotTool.controls.screenshotSavePath = force_extension_on_path(p, setExtensionFilter.extensions);
world->drawProg.screenshotTool.controls.setToTakeScreenshot = true;
}
}, "screenshot", true);
#endif
}
}
t.gui.pop_id();
}
void ScreenshotTool::take_screenshot(const std::filesystem::path& filePath) {
if(controls.imageSize.x() <= 0 || controls.imageSize.y() <= 0) {
std::cout << "[ScreenshotTool::take_screenshot] Image size is 0 or negative" << std::endl;
return;
}
std::string ext = filePath.extension().string();
unsigned extType = 0;
if(ext == ".jpg" || ext == ".jpeg")
extType = 0;
else if(ext == ".png")
extType = 1;
else if(ext == ".webp")
extType = 2;
size_t imageByteSize = (size_t)controls.imageSize.x() * (size_t)controls.imageSize.y() * 4;
size_t imageRowSize = 4 * controls.imageSize.x();
SkImageInfo finalImgInfo = SkImageInfo::Make(controls.imageSize.x(), controls.imageSize.y(), kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
std::vector<uint8_t> finalImgRawData(imageByteSize);
SkPixmap finalImgData(finalImgInfo, finalImgRawData.data(), imageRowSize);
SkImageInfo imgInfo = SkImageInfo::MakeN32Premul(drawP.world.main.window.size.x(), drawP.world.main.window.size.y());
#ifdef USE_SKIA_BACKEND_GRAPHITE
sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(drawP.world.main.window.recorder(), imgInfo, skgpu::Mipmapped::kNo, &drawP.world.main.window.defaultMSAASurfaceProps);
#elif USE_SKIA_BACKEND_GANESH
sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(drawP.world.main.window.ctx.get(), skgpu::Budgeted::kNo, imgInfo, drawP.world.main.window.defaultMSAASampleCount, &drawP.world.main.window.defaultMSAASurfaceProps);
#endif
if(!surface) {
Logger::get().log("INFO", "[SkSurfaces::WrapBackendRenderTarget] Screenshot Tool could not make surface");
return;
}
SkCanvas* screenshotCanvas = surface->getCanvas();
if(!screenshotCanvas) {
Logger::get().log("INFO", "Screenshot Tool could not make canvas");
return;
}
for(int i = 0; i < controls.imageSize.x(); i += drawP.world.main.window.size.x())
for(int j = 0; j < controls.imageSize.y(); j += drawP.world.main.window.size.y())
take_screenshot_area_hw(surface, screenshotCanvas, finalImgRawData.data(), controls.imageSize, Vector2i{i, j}, Vector2i{std::min(drawP.world.main.window.size.x(), controls.imageSize.x() - i), std::min(drawP.world.main.window.size.y(), controls.imageSize.y() - j)}, drawP.world.main.window.size, extType != 0 && controls.transparentBackground);
bool success = false;
#ifdef __EMSCRIPTEN__
SkDynamicMemoryWStream out;
#else
SkFILEWStream out((const char*)filePath.c_str());
#endif
if(extType == 0)
success = SkJpegEncoder::Encode(&out, finalImgData, {});
else if(extType == 1)
success = SkPngEncoder::Encode(&out, finalImgData, {});
else if(extType == 2)
success = SkWebpEncoder::Encode(&out, finalImgData, {});
if(!success) {
Logger::get().log("INFO", "[ScreenshotTool::take_screenshot] Could not encode and write screenshot");
return;
}
#ifdef __EMSCRIPTEN__
if(success) {
std::string mimeTypeArray[] = {"image/jpeg", "image/png", "image/webp"};
auto skData = out.detachAsData();
emscripten_browser_file::download(
"screenshot" + controls.typeSelections[extType],
mimeTypeArray[extType],
std::string_view((const char*)skData->bytes(), skData->size())
);
}
#endif
}
void ScreenshotTool::take_screenshot_area_hw(const sk_sp<SkSurface>& surface, SkCanvas* canvas, void* fullImgRawData, const Vector2i& fullImageSize, const Vector2i& sectionImagePos, const Vector2i& sectionImageSize, const Vector2i& canvasSize, bool transparentBackground) {
//std::cout << "[ScreenshotTool::take_screenshot_area_hw] Screenshot Pos: " << "[" << sectionImagePos.x() << " " << sectionImagePos.y() << "]" << std::endl;
float secRectX1 = controls.rectX1 + (controls.rectX2 - controls.rectX1) * (sectionImagePos.x() / (double)fullImageSize.x());
float secRectX2 = controls.rectX1 + (controls.rectX2 - controls.rectX1) * ((sectionImagePos.x() + canvasSize.x()) / (double)fullImageSize.x());
float secRectY1 = controls.rectY1 + (controls.rectY2 - controls.rectY1) * (sectionImagePos.y() / (double)fullImageSize.y());
float secRectY2 = controls.rectY1 + (controls.rectY2 - controls.rectY1) * ((sectionImagePos.y() + canvasSize.y()) / (double)fullImageSize.y());
DrawData d = drawP.world.drawData;
WorldVec topLeft = controls.coords.from_space({secRectX1, secRectY1});
WorldVec topRight = controls.coords.from_space({secRectX2, secRectY1});
WorldVec bottomLeft = controls.coords.from_space({secRectX1, secRectY2});
WorldVec bottomRight = controls.coords.from_space({secRectX2, secRectY2});
WorldVec camCenter = (topLeft + bottomRight) / WorldScalar(2);
WorldVec vectorZoom;
WorldScalar distX = (camCenter - (topLeft + bottomLeft) * WorldScalar(0.5)).norm();
WorldScalar distY = (camCenter - (topLeft + topRight) * WorldScalar(0.5)).norm();
vectorZoom.x() = distX / WorldScalar(canvasSize.x() * 0.5);
vectorZoom.y() = distY / WorldScalar(canvasSize.y() * 0.5);
WorldScalar newInverseScale = (vectorZoom.x() + vectorZoom.y()) * WorldScalar(0.5);
drawP.world.drawData.cam.set_based_on_properties(drawP.world, topLeft, newInverseScale, controls.coords.rotation);
GridManager::GridType oldGridType = drawP.world.main.grid.gridType;
drawP.world.main.takingScreenshot = true;
drawP.world.main.transparentBackground = transparentBackground;
if(!controls.displayGrid)
drawP.world.main.grid.gridType = GridManager::GRIDTYPE_NONE;
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 = d;
drawP.world.drawData.cam.changed();
SkImageInfo aaImgInfo = SkImageInfo::Make(sectionImageSize.x(), sectionImageSize.y(), kRGBA_8888_SkColorType, kPremul_SkAlphaType);
void* fullImgRawDataStartPt = (uint8_t*)fullImgRawData + 4 * (size_t)sectionImagePos.x() + 4 * (size_t)fullImageSize.x() * (size_t)sectionImagePos.y();
SkPixmap aaImgData(aaImgInfo, fullImgRawDataStartPt, fullImageSize.x() * 4);
if(!surface->readPixels(aaImgData, 0, 0))
throw std::runtime_error("[ScreenshotTool::take_screenshot_area_hw] Error copy pixmap");
}
void ScreenshotTool::reset_tool() {
controls.selectionMode = 0;
}
void ScreenshotTool::tool_update() {
if(controls.setToTakeScreenshot) {
controls.setToTakeScreenshot = false;
take_screenshot(controls.screenshotSavePath);
}
switch(controls.selectionMode) {
case 0: {
if(drawP.controls.leftClick) {
controls.rectX1 = controls.rectX2 = drawP.world.main.input.mouse.pos.x();
controls.rectY1 = controls.rectY2 = drawP.world.main.input.mouse.pos.y();
controls.coords = drawP.world.drawData.cam.c;
controls.dragType = 0;
controls.selectionMode = 1;
}
break;
}
case 1: {
Vector2f curPos = controls.coords.get_mouse_pos(drawP.world);
float oldX1 = controls.rectX1;
float oldX2 = controls.rectX2;
float oldY1 = controls.rectY1;
float oldY2 = controls.rectY2;
switch(controls.dragType) {
case 0:
controls.rectX2 = curPos.x();
controls.rectY2 = curPos.y();
break;
case 1:
controls.rectY2 = curPos.y();
break;
case 2:
controls.rectX1 = curPos.x();
controls.rectY2 = curPos.y();
break;
case 3:
controls.rectX1 = curPos.x();
break;
case 4:
controls.rectX1 = curPos.x();
controls.rectY1 = curPos.y();
break;
case 5:
controls.rectY1 = curPos.y();
break;
case 6:
controls.rectX2 = curPos.x();
controls.rectY1 = curPos.y();
break;
case 7:
controls.rectX2 = curPos.x();
break;
}
if(std::fabs(controls.rectX1 - controls.rectX2) > 1e10 || std::fabs(controls.rectY1 - controls.rectY2) > 1e10) { // Limit before screenshot tool starts breaking
controls.rectX1 = oldX1;
controls.rectX2 = oldX2;
controls.rectY1 = oldY1;
controls.rectY2 = oldY2;
}
if((drawP.world.drawData.cam.c.inverseScale << 8) < controls.coords.inverseScale || (drawP.world.drawData.cam.c.inverseScale >> 15) > controls.coords.inverseScale) {
controls.selectionMode = 0;
break;
}
if(!drawP.controls.leftClickHeld) {
controls.selectionMode = 2;
commit_rect();
controls.imageSize.x() = 1000.0;
controls.imageSize.y() = controls.imageSize.x() * (controls.rectY2 - controls.rectY1) / (controls.rectX2 - controls.rectX1);
}
break;
}
case 2: {
commit_rect();
controls.dragType = -1;
if((drawP.world.drawData.cam.c.inverseScale << 8) < controls.coords.inverseScale || (drawP.world.drawData.cam.c.inverseScale >> 15) > controls.coords.inverseScale) {
controls.selectionMode = 0;
break;
}
if(drawP.controls.cursorHoveringOverCanvas)
for(int i = 0; i < 8; i++)
if(SCollision::collide(controls.circles[i], drawP.world.main.input.mouse.pos))
controls.dragType = i;
if(drawP.controls.leftClick) {
if(controls.dragType == -1)
controls.selectionMode = 0;
else
controls.selectionMode = 1;
}
break;
}
}
}
void ScreenshotTool::commit_rect() {
float x1 = controls.rectX1;
float x2 = controls.rectX2;
float y1 = controls.rectY1;
float y2 = controls.rectY2;
controls.rectX1 = std::min(x1, x2);
controls.rectX2 = std::max(x1, x2);
controls.rectY1 = std::min(y1, y2);
controls.rectY2 = std::max(y1, y2);
controls.circles[0] = {drawP.world.drawData.cam.c.to_space(controls.coords.from_space({controls.rectX2 , controls.rectY2})), DRAG_POINT_RADIUS};
controls.circles[1] = {drawP.world.drawData.cam.c.to_space(controls.coords.from_space({(controls.rectX1 + controls.rectX2) * 0.5, controls.rectY2})), DRAG_POINT_RADIUS};
controls.circles[2] = {drawP.world.drawData.cam.c.to_space(controls.coords.from_space({controls.rectX1 , controls.rectY2})), DRAG_POINT_RADIUS};
controls.circles[3] = {drawP.world.drawData.cam.c.to_space(controls.coords.from_space({controls.rectX1, (controls.rectY1 + controls.rectY2) * 0.5})), DRAG_POINT_RADIUS};
controls.circles[4] = {drawP.world.drawData.cam.c.to_space(controls.coords.from_space({controls.rectX1 , controls.rectY1})), DRAG_POINT_RADIUS};
controls.circles[5] = {drawP.world.drawData.cam.c.to_space(controls.coords.from_space({(controls.rectX1 + controls.rectX2) * 0.5, controls.rectY1})), DRAG_POINT_RADIUS};
controls.circles[6] = {drawP.world.drawData.cam.c.to_space(controls.coords.from_space({controls.rectX2 , controls.rectY1})), DRAG_POINT_RADIUS};
controls.circles[7] = {drawP.world.drawData.cam.c.to_space(controls.coords.from_space({controls.rectX2, (controls.rectY1 + controls.rectY2) * 0.5})), DRAG_POINT_RADIUS};
}
void ScreenshotTool::draw(SkCanvas* canvas, const DrawData& drawData) {
if(controls.selectionMode == 1 || controls.selectionMode == 2) {
canvas->save();
controls.coords.transform_sk_canvas(canvas, drawData);
float x1 = std::min(controls.rectX1, controls.rectX2);
float x2 = std::max(controls.rectX1, controls.rectX2);
float y1 = std::min(controls.rectY1, controls.rectY2);
float y2 = std::max(controls.rectY1, controls.rectY2);
SkPoint p1{x1, y1};
SkPoint p2{x2, y2};
SkPath path1;
path1.addRect(p1.x(), p1.y(), p2.x(), p2.y());
path1.setFillType(SkPathFillType::kInverseWinding);
SkPaint paint1;
paint1.setColor4f({0.0f, 0.0f, 0.0f, 0.3f});
canvas->drawPath(path1, paint1);
path1.setFillType(SkPathFillType::kWinding);
SkPaint paint2;
paint2.setColor4f({0.0f, 0.0f, 0.0f, 0.9f});
paint2.setStyle(SkPaint::kStroke_Style);
canvas->drawPath(path1, paint2);
canvas->restore();
if(controls.selectionMode == 2) {
paint2.setStyle(SkPaint::kFill_Style);
for(auto& circ : controls.circles)
drawP.draw_drag_circle(canvas, circ.pos, {0.1f, 0.9f, 0.9f, 1.0f}, drawData, 1.0f);
}
}
}

View file

@ -0,0 +1,56 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "../DrawData.hpp"
#include <Helpers/SCollision.hpp>
#include <filesystem>
#include "../InputManager.hpp"
#include "../CoordSpaceHelper.hpp"
class DrawingProgram;
class ScreenshotTool {
public:
ScreenshotTool(DrawingProgram& initDrawP);
void gui_toolbox();
void tool_update();
void draw(SkCanvas* canvas, const DrawData& drawData);
void reset_tool();
private:
void commit_rect();
void take_screenshot(const std::filesystem::path& filePath);
void take_screenshot_area_hw(const sk_sp<SkSurface>& surface, SkCanvas* canvas, void* fullImgRawData, const Vector2i& fullImageSize, const Vector2i& sectionImagePos, const Vector2i& sectionImageSize, const Vector2i& canvasSize, bool transparentBackground);
//static constexpr std::array<InputManager::SystemCursorType, 8> cursors{
// InputManager::SystemCursorType::SE_RESIZE,
// InputManager::SystemCursorType::S_RESIZE,
// InputManager::SystemCursorType::SW_RESIZE,
// InputManager::SystemCursorType::W_RESIZE,
// InputManager::SystemCursorType::NW_RESIZE,
// InputManager::SystemCursorType::N_RESIZE,
// InputManager::SystemCursorType::NE_RESIZE,
// InputManager::SystemCursorType::E_RESIZE
//};
struct ScreenshotControls {
CoordSpaceHelper coords;
float rectX1;
float rectX2;
float rectY1;
float rectY2;
int selectionMode = 0;
int dragType = 0;
Vector2i imageSize = {0, 0};
std::array<SCollision::Circle<float>, 8> circles;
bool displayGrid = true;
bool transparentBackground = false;
size_t selectedType = 0;
std::vector<std::string> typeSelections = {".jpg", ".png", ".webp"};
std::atomic<bool> setToTakeScreenshot = false;
std::filesystem::path screenshotSavePath;
} controls;
DrawingProgram& drawP;
};

View file

@ -0,0 +1,292 @@
#include "TextBoxTool.hpp"
#include <chrono>
#include "DrawingProgram.hpp"
#include "../Server/CommandList.hpp"
#include "../MainProgram.hpp"
#include "../DrawData.hpp"
#include "Helpers/MathExtras.hpp"
#include "Helpers/SCollision.hpp"
#include "Helpers/Serializers.hpp"
#include "../CollabTextBox/CollabTextBox.hpp"
#include "../SharedTypes.hpp"
#include <cereal/types/vector.hpp>
#include <memory>
#include <ranges>
TextBoxTool::TextBoxTool(DrawingProgram& initDrawP):
drawP(initDrawP)
{
}
void TextBoxTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("textbox tool");
t.gui.text_label_centered("Textbox");
t.gui.slider_scalar_field("Text Relative Size", "Text Size", &controls.textRelativeSize, 5.0f, 100.0f);
t.gui.pop_id();
}
void TextBoxTool::reset_tool() {
commit();
controls.drawStage = 0;
}
bool TextBoxTool::edit_gui(const std::shared_ptr<DrawTextBox>& a) {
DrawTextBox::Data oldData = a->d;
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("edit tool text");
t.gui.text_label_centered("Edit Text");
t.gui.slider_scalar_field("Text Relative Size", "Text Size", &a->d.textSize, 5.0f, 100.0f);
t.gui.left_to_right_line_layout([&]() {
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) {
if(t.gui.color_button("Text Color", &a->d.textColor, &a->d.textColor == t.colorRight))
t.color_selector_right(&a->d.textColor == t.colorRight ? nullptr : &a->d.textColor);
}
t.gui.text_label("Text Color");
});
t.gui.pop_id();
return a->d != oldData;
}
void TextBoxTool::commit_edit_updates(const std::shared_ptr<DrawTextBox>& a, std::any& prevData) {
a->d.editing = false;
DrawTextBox::Data pData = std::any_cast<DrawTextBox::Data>(prevData);
DrawTextBox::Data cData = a->d;
drawP.world.undo.push(UndoManager::UndoRedoPair{
[&, a, pData]() {
a->d = pData;
a->set_textbox_string(pData.currentText);
a->d.editing = false;
ServerClientID compID = drawP.components.get_id(a);
if(compID != ServerClientID{0, 0})
a->client_send_update_final(drawP, compID);
a->final_update(drawP);
drawP.reset_tools();
return true;
},
[&, a, cData]() {
ServerClientID compID = drawP.components.get_id(a);
a->d = cData;
a->set_textbox_string(cData.currentText);
a->d.editing = false;
if(compID != ServerClientID{0, 0})
a->client_send_update_final(drawP, compID);
a->final_update(drawP);
drawP.reset_tools();
return true;
}
});
}
void TextBoxTool::edit_start(const std::shared_ptr<DrawTextBox>& a, std::any& prevData) {
auto& cur = a->d.cursor;
auto& textbox = a->textBox;
Vector2f textSelectPos = a->get_mouse_pos(drawP);
SkIPoint p = convert_vec2<SkIPoint>(textSelectPos.cast<int32_t>());
cur.pos = cur.selectionBeginPos = cur.selectionEndPos = textbox.getPosition(p);
prevData = a->d;
a->d.editing = true;
a->client_send_update_temp(drawP, drawP.components.get_id(a));
drawP.editTool.add_point_handle({&a->d.p1, nullptr, &a->d.p2});
drawP.editTool.add_point_handle({&a->d.p2, &a->d.p1, nullptr});
}
bool TextBoxTool::edit_update(const std::shared_ptr<DrawTextBox>& a) {
SCollision::ColliderCollection<float> mousePointCollection;
mousePointCollection.circle.emplace_back(drawP.world.main.input.mouse.pos, 1.0f);
mousePointCollection.recalculate_bounds();
a->textBox.setWidth(std::abs(a->d.p2.x() - a->d.p1.x()));
a->create_collider(drawP.colliderAllocated);
InputManager& input = drawP.world.main.input;
auto& textbox = a->textBox;
auto& cur = a->d.cursor;
bool moved = false;
bool movedHeld = false;
bool collidesWithBox = false;
if(a->collides_with_cam_coords(drawP.world.drawData.cam.c, mousePointCollection, drawP.colliderAllocated)) {
//drawP.world.main.input.cursorIcon = InputManager::SystemCursorType::TEXT;
collidesWithBox = true;
}
if(drawP.controls.leftClick) {
if(collidesWithBox) {
Vector2f textSelectPos = a->get_mouse_pos(drawP);
SkIPoint p = convert_vec2<SkIPoint>(textSelectPos.cast<int32_t>());
cur.pos = cur.selectionBeginPos = textbox.getPosition(p);
moved = true;
}
}
else if(drawP.controls.leftClickHeld) {
if(collidesWithBox) {
Vector2f textSelectPos = a->get_mouse_pos(drawP);
SkIPoint p = convert_vec2<SkIPoint>(textSelectPos.cast<int32_t>());
cur.pos = cur.selectionBeginPos = textbox.getPosition(p);
}
movedHeld = true;
}
if(input.key(InputManager::KEY_DRAW_UNSELECT).repeat)
reset_tool();
if(input.key(InputManager::KEY_TEXT_LEFT).repeat)
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kLeft, cur.selectionBeginPos);
if(input.key(InputManager::KEY_TEXT_RIGHT).repeat)
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kRight, cur.selectionBeginPos);
if(input.key(InputManager::KEY_TEXT_UP).repeat)
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kUp, cur.selectionBeginPos);
if(input.key(InputManager::KEY_TEXT_DOWN).repeat)
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kDown, cur.selectionBeginPos);
if(input.key(InputManager::KEY_TEXT_HOME).repeat)
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kHome, cur.selectionBeginPos);
moved = moved || input.key(InputManager::KEY_TEXT_DOWN).repeat || input.key(InputManager::KEY_TEXT_UP).repeat || input.key(InputManager::KEY_TEXT_RIGHT).repeat || input.key(InputManager::KEY_TEXT_LEFT).repeat || input.key(InputManager::KEY_TEXT_HOME).repeat;
if(moved && !input.key(InputManager::KEY_TEXT_SHIFT).held && !movedHeld)
cur.selectionEndPos = cur.selectionBeginPos;
if(moved || movedHeld)
update_textbox_network(a);
if(input.key(InputManager::KEY_TEXT_BACKSPACE).repeat) {
edit_text([&]() {
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::kLeft, cur.pos));
}, a);
}
if(input.key(InputManager::KEY_TEXT_DELETE).repeat) {
edit_text([&]() {
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));
}, a);
}
if(input.get_clipboard_paste_happened()) {
edit_text([&]() {
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
std::string clipboard = input.get_clipboard_str();
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, clipboard);
}, a);
}
if(input.key(InputManager::KEY_TEXT_ENTER).repeat) {
edit_text([&]() {
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
cur.pos = textbox.insert(cur.pos, "\n");
cur.pos.fParagraphIndex++;
cur.pos.fTextByteIndex = 0;
cur.selectionBeginPos = cur.selectionEndPos = cur.pos;
}, a);
}
if(input.key(InputManager::KEY_TEXT_COPY).repeat) {
std::string clipboardData = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos);
input.set_clipboard_str(clipboardData);
}
if(input.key(InputManager::KEY_TEXT_CUT).repeat) {
edit_text([&]() {
std::string clipboardData = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos);
input.set_clipboard_str(clipboardData);
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
}, a);
}
if(input.key(InputManager::KEY_TEXT_SELECTALL).repeat) {
cur.selectionEndPos.fParagraphIndex = textbox.lineCount() == 0 ? 0 : textbox.lineCount() - 1;
cur.selectionEndPos.fTextByteIndex = textbox.line(cur.selectionEndPos.fParagraphIndex).size();
cur.pos = cur.selectionEndPos;
cur.selectionBeginPos.fTextByteIndex = 0;
cur.selectionBeginPos.fParagraphIndex = 0;
update_textbox_network(a);
}
if(!input.text.newInput.empty()) {
edit_text([&]() {
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, input.text.newInput);
}, a);
}
input.text.set_accepting_input();
return true;
}
void TextBoxTool::tool_update() {
switch(controls.drawStage) {
case 0: {
if(drawP.controls.leftClick) {
controls.intermediateItem = std::make_shared<DrawTextBox>();
controls.startAt = drawP.world.main.input.mouse.pos;
controls.endAt = controls.startAt;
controls.intermediateItem->d.p1 = controls.intermediateItem->d.p2 = controls.startAt;
controls.intermediateItem->d.textColor = drawP.controls.foregroundColor;
controls.intermediateItem->d.textSize = controls.textRelativeSize;
controls.intermediateItem->d.editing = true;
controls.intermediateItem->coords = drawP.world.drawData.cam.c;
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
uint64_t placement = drawP.components.client_list().size();
drawP.components.client_insert(placement, controls.intermediateItem);
controls.intermediateItem->client_send_place(drawP, placement);
drawP.add_undo_place_component(placement, controls.intermediateItem);
controls.drawStage = 1;
}
break;
}
case 1: {
Vector2f newPos = controls.intermediateItem->coords.get_mouse_pos(drawP.world);
if(drawP.world.main.input.key(InputManager::KEY_EQUAL_DIMENSIONS).held) {
float height = std::fabs(controls.startAt.y() - newPos.y());
newPos.x() = controls.startAt.x() + (((newPos.x() - controls.startAt.x()) < 0.0f ? -1.0f : 1.0f) * height);
}
controls.endAt = newPos;
controls.intermediateItem->d.p1 = cwise_vec_min(controls.endAt, controls.startAt);
controls.intermediateItem->d.p2 = cwise_vec_max(controls.endAt, controls.startAt);
controls.intermediateItem->lastUpdateTime = std::chrono::steady_clock::now();
if(!drawP.controls.leftClickHeld) {
auto i = controls.intermediateItem;
commit();
drawP.reset_tools();
drawP.controls.selectedTool = DrawingProgram::TOOL_EDIT;
drawP.controls.previousSelected = DrawingProgram::TOOL_EDIT;
drawP.editTool.edit_start(i);
}
else {
controls.intermediateItem->client_send_update_temp(drawP, drawP.components.get_id(controls.intermediateItem));
controls.intermediateItem->temp_update(drawP);
}
break;
}
}
}
// Passing a function in, just in case we have to do something before editing the text
void TextBoxTool::edit_text(std::function<void()> toRun, const std::shared_ptr<DrawTextBox>& textBox) {
toRun();
textBox->update_contained_string();
update_textbox_network(textBox);
}
void TextBoxTool::update_textbox_network(const std::shared_ptr<DrawTextBox>& textBox) {
textBox->client_send_update_temp(drawP, drawP.components.get_id(textBox));
textBox->updateDraw = true;
}
void TextBoxTool::commit() {
if(controls.intermediateItem) {
controls.intermediateItem->client_send_update_final(drawP, drawP.components.get_id(controls.intermediateItem));
controls.intermediateItem->final_update(drawP);
//controls.intermediateItem->currentlyUpdating = true; // It remains true, as we are about to start writing into the text box we just placed
controls.intermediateItem = nullptr;
}
}
void TextBoxTool::draw(SkCanvas* canvas, const DrawData& drawData) {
if(controls.intermediateItem)
controls.intermediateItem->draw(canvas, drawData);
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <include/core/SkCanvas.h>
#include "../DrawData.hpp"
#include "../DrawComponents/DrawTextBox.hpp"
#include <Helpers/SCollision.hpp>
#include <any>
class DrawingProgram;
class TextBoxTool {
public:
TextBoxTool(DrawingProgram& initDrawP);
void gui_toolbox();
void tool_update();
void draw(SkCanvas* canvas, const DrawData& drawData);
void reset_tool();
void edit_start(const std::shared_ptr<DrawTextBox>& a, std::any& prevData);
void commit_edit_updates(const std::shared_ptr<DrawTextBox>& a, std::any& prevData);
bool edit_update(const std::shared_ptr<DrawTextBox>& a);
bool edit_gui(const std::shared_ptr<DrawTextBox>& a);
private:
void edit_text(std::function<void()> toRun, const std::shared_ptr<DrawTextBox>& textBox);
void update_textbox_network(const std::shared_ptr<DrawTextBox>& textBox);
void commit();
struct TextBoxControls {
float textRelativeSize = 20.0f;
Vector2f startAt;
Vector2f endAt;
int drawStage = 0;
std::shared_ptr<DrawTextBox> intermediateItem;
} controls;
DrawingProgram& drawP;
};