Initial Commit
This commit is contained in:
commit
6088fc29ed
978 changed files with 353647 additions and 0 deletions
196
src/DrawingProgram/BrushTool.cpp
Normal file
196
src/DrawingProgram/BrushTool.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
47
src/DrawingProgram/BrushTool.hpp
Normal file
47
src/DrawingProgram/BrushTool.hpp
Normal 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;
|
||||
};
|
||||
578
src/DrawingProgram/DrawingProgram.cpp
Normal file
578
src/DrawingProgram/DrawingProgram.cpp
Normal 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;
|
||||
}
|
||||
121
src/DrawingProgram/DrawingProgram.hpp
Normal file
121
src/DrawingProgram/DrawingProgram.hpp
Normal 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;
|
||||
};
|
||||
218
src/DrawingProgram/EditTool.cpp
Normal file
218
src/DrawingProgram/EditTool.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/DrawingProgram/EditTool.hpp
Normal file
36
src/DrawingProgram/EditTool.hpp
Normal 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;
|
||||
};
|
||||
156
src/DrawingProgram/EllipseDrawTool.cpp
Normal file
156
src/DrawingProgram/EllipseDrawTool.cpp
Normal 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) {
|
||||
}
|
||||
33
src/DrawingProgram/EllipseDrawTool.hpp
Normal file
33
src/DrawingProgram/EllipseDrawTool.hpp
Normal 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;
|
||||
};
|
||||
89
src/DrawingProgram/EraserTool.cpp
Normal file
89
src/DrawingProgram/EraserTool.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
19
src/DrawingProgram/EraserTool.hpp
Normal file
19
src/DrawingProgram/EraserTool.hpp
Normal 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;
|
||||
};
|
||||
54
src/DrawingProgram/ImageTool.cpp
Normal file
54
src/DrawingProgram/ImageTool.cpp
Normal 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;
|
||||
}
|
||||
23
src/DrawingProgram/ImageTool.hpp
Normal file
23
src/DrawingProgram/ImageTool.hpp
Normal 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;
|
||||
};
|
||||
77
src/DrawingProgram/InkDropperTool.cpp
Normal file
77
src/DrawingProgram/InkDropperTool.cpp
Normal 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) {
|
||||
}
|
||||
17
src/DrawingProgram/InkDropperTool.hpp
Normal file
17
src/DrawingProgram/InkDropperTool.hpp
Normal 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;
|
||||
};
|
||||
157
src/DrawingProgram/RectDrawTool.cpp
Normal file
157
src/DrawingProgram/RectDrawTool.cpp
Normal 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) {
|
||||
}
|
||||
35
src/DrawingProgram/RectDrawTool.hpp
Normal file
35
src/DrawingProgram/RectDrawTool.hpp
Normal 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;
|
||||
};
|
||||
398
src/DrawingProgram/RectSelectTool.cpp
Normal file
398
src/DrawingProgram/RectSelectTool.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/DrawingProgram/RectSelectTool.hpp
Normal file
58
src/DrawingProgram/RectSelectTool.hpp
Normal 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;
|
||||
};
|
||||
356
src/DrawingProgram/ScreenshotTool.cpp
Normal file
356
src/DrawingProgram/ScreenshotTool.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/DrawingProgram/ScreenshotTool.hpp
Normal file
56
src/DrawingProgram/ScreenshotTool.hpp
Normal 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;
|
||||
};
|
||||
292
src/DrawingProgram/TextBoxTool.cpp
Normal file
292
src/DrawingProgram/TextBoxTool.cpp
Normal 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);
|
||||
}
|
||||
36
src/DrawingProgram/TextBoxTool.hpp
Normal file
36
src/DrawingProgram/TextBoxTool.hpp
Normal 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;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue