Size lock doesn't get removed when zooming in/out too much

This commit is contained in:
Yousef Khadadeh 2026-01-29 20:33:22 +03:00
parent 8a4d9a4f07
commit cf28a62e18
7 changed files with 168 additions and 59 deletions

View file

@ -5,32 +5,83 @@
#include "Helpers/FixedPoint.hpp"
#include <optional>
#include "Helpers/Logger.hpp"
#include "Tools/DrawingProgramToolBase.hpp"
float ToolConfiguration::get_relative_width(DrawingProgram& drawP, const WorldScalar& camInverseScale, float relativeWidthLocal) const {
auto& lockedCameraScale = drawP.controls.lockedCameraScale;
float relativeWidth = globalConf.useGlobalRelativeWidth ? globalConf.relativeWidth : relativeWidthLocal;
if(lockedCameraScale.has_value()) {
float lockMultiplier = static_cast<float>(WorldMultiplier(lockedCameraScale.value()) / WorldMultiplier(camInverseScale));
if(lockMultiplier < 0.001f) {
Logger::get().log("USERINFO", "Zoomed out too much, size unlocked");
lockedCameraScale = std::nullopt;
return relativeWidth;
}
else if(lockMultiplier > 1000.0f) {
Logger::get().log("USERINFO", "Zoomed in too much, size unlocked");
lockedCameraScale = std::nullopt;
return relativeWidth;
}
return relativeWidth * lockMultiplier;
float& ToolConfiguration::get_stroke_size_relative_width_ref(DrawingProgramToolType toolType) {
if(globalConf.useGlobalRelativeWidth)
return globalConf.relativeWidth;
switch(toolType) {
case DrawingProgramToolType::LINE:
return lineDraw.relativeWidth;
case DrawingProgramToolType::BRUSH:
return brush.relativeWidth;
case DrawingProgramToolType::ELLIPSE:
return ellipseDraw.relativeWidth;
case DrawingProgramToolType::RECTANGLE:
return rectDraw.relativeWidth;
case DrawingProgramToolType::ERASER:
return eraser.relativeWidth;
default:
return globalConf.relativeWidth;
}
return relativeWidth;
return globalConf.relativeWidth;
}
void ToolConfiguration::relative_width_gui(DrawingProgram& drawP, const char* label, float* relativeWidthLocal) {
const float& ToolConfiguration::get_stroke_size_relative_width_ref(DrawingProgramToolType toolType) const {
if(globalConf.useGlobalRelativeWidth)
return globalConf.relativeWidth;
switch(toolType) {
case DrawingProgramToolType::LINE:
return lineDraw.relativeWidth;
case DrawingProgramToolType::BRUSH:
return brush.relativeWidth;
case DrawingProgramToolType::ELLIPSE:
return ellipseDraw.relativeWidth;
case DrawingProgramToolType::RECTANGLE:
return rectDraw.relativeWidth;
case DrawingProgramToolType::ERASER:
return eraser.relativeWidth;
default:
return globalConf.relativeWidth;
}
return globalConf.relativeWidth;
}
std::pair<std::optional<float>, ToolConfiguration::RelativeWidthFailCode> ToolConfiguration::get_relative_width_from_value(DrawingProgram& drawP, const WorldScalar& camInverseScale, float relativeWidth) const {
auto& lockedCameraScale = drawP.controls.lockedCameraScale;
if(lockedCameraScale.has_value()) {
float lockMultiplier = static_cast<float>(WorldMultiplier(lockedCameraScale.value()) / WorldMultiplier(camInverseScale));
if(lockMultiplier < 0.001f)
return {std::nullopt, RelativeWidthFailCode::TOO_ZOOMED_OUT};
else if(lockMultiplier > 1000.0f)
return {std::nullopt, RelativeWidthFailCode::TOO_ZOOMED_IN};
return {relativeWidth * lockMultiplier, RelativeWidthFailCode::SUCCESS};
}
return {relativeWidth, RelativeWidthFailCode::SUCCESS};
}
std::pair<std::optional<float>, ToolConfiguration::RelativeWidthFailCode> ToolConfiguration::get_relative_width_stroke_size(DrawingProgram& drawP, const WorldScalar& camInverseScale) const {
return get_relative_width_from_value(drawP, camInverseScale, get_stroke_size_relative_width_ref(drawP.drawTool->get_type()));
}
void ToolConfiguration::print_relative_width_fail_message(RelativeWidthFailCode failCode) {
switch(failCode) {
case RelativeWidthFailCode::SUCCESS:
break;
case RelativeWidthFailCode::TOO_ZOOMED_IN:
Logger::get().log("USERINFO", "Zoomed in too much! Unlock size or zoom out");
break;
case RelativeWidthFailCode::TOO_ZOOMED_OUT:
Logger::get().log("USERINFO", "Zoomed out too much! Unlock size or zoom in");
break;
}
}
void ToolConfiguration::relative_width_gui(DrawingProgram& drawP, const char* label) {
auto& gui = drawP.world.main.toolbar.gui;
auto& lockedCameraScale = drawP.controls.lockedCameraScale;
gui.slider_scalar_field<float>("relstrokewidth", label, globalConf.useGlobalRelativeWidth ? &globalConf.relativeWidth : relativeWidthLocal, 3.0f, 40.0f);
if(gui.text_button_wide("lock brush size", lockedCameraScale.has_value() ? "Unlock Brush Size" : "Lock Brush Size")) {
gui.slider_scalar_field<float>("relstrokewidth", label, &get_stroke_size_relative_width_ref(drawP.drawTool->get_type()), 3.0f, 40.0f);
if(gui.text_button_wide("lock brush size", lockedCameraScale.has_value() ? "Unlock Size" : "Lock Size to Zoom")) {
if(lockedCameraScale.has_value())
lockedCameraScale = std::nullopt;
else

View file

@ -56,8 +56,18 @@ class ToolConfiguration {
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GlobalConfig, useGlobalRelativeWidth, foregroundColor, backgroundColor, relativeWidth)
} globalConf;
float get_relative_width(DrawingProgram& drawP, const WorldScalar& camInverseScale, float relativeWidthLocal) const;
void relative_width_gui(DrawingProgram& drawP, const char* label, float* relativeWidthLocal);
enum class RelativeWidthFailCode {
SUCCESS,
TOO_ZOOMED_IN,
TOO_ZOOMED_OUT
};
float& get_stroke_size_relative_width_ref(DrawingProgramToolType toolType);
const float& get_stroke_size_relative_width_ref(DrawingProgramToolType toolType) const;
std::pair<std::optional<float>, RelativeWidthFailCode> get_relative_width_from_value(DrawingProgram& drawP, const WorldScalar& camInverseScale, float relativeWidth) const;
std::pair<std::optional<float>, RelativeWidthFailCode> get_relative_width_stroke_size(DrawingProgram& drawP, const WorldScalar& camInverseScale) const;
void print_relative_width_fail_message(RelativeWidthFailCode failCode);
void relative_width_gui(DrawingProgram& drawP, const char* label);
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ToolConfiguration, brush, eraser, ellipseDraw, rectDraw, eyeDropper, lineDraw, screenshot, globalConf)
};

View file

@ -90,13 +90,21 @@ void BrushTool::tool_update() {
if(isPenDown) {
if(!objInfoBeingEdited) {
if(drawP.layerMan.is_a_layer_being_edited()) {
if(drawP.controls.leftClick && drawP.layerMan.is_a_layer_being_edited()) {
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
if(!relativeWidthResult.first.has_value()) {
if(drawP.controls.leftClick)
drawP.world.main.toolConfig.print_relative_width_fail_message(relativeWidthResult.second);
return;
}
float width = relativeWidthResult.first.value() * penWidth;
CanvasComponentContainer* newBrushStrokeContainer = new CanvasComponentContainer(drawP.world.netObjMan, CanvasComponentType::BRUSHSTROKE);
BrushStrokeCanvasComponent& newBrushStroke = static_cast<BrushStrokeCanvasComponent&>(newBrushStrokeContainer->get_comp());
BrushStrokeCanvasComponentPoint p;
p.pos = drawP.world.main.input.mouse.pos;
p.width = toolConfig.get_relative_width(drawP, drawP.world.drawData.cam.c.inverseScale, toolConfig.brush.relativeWidth) * penWidth;
p.width = width;
prevPointUnaltered = p.pos;
newBrushStroke.d.points->emplace_back(p);
newBrushStroke.d.color = toolConfig.globalConf.foregroundColor;
@ -110,10 +118,11 @@ void BrushTool::tool_update() {
NetworkingObjects::NetObjOwnerPtr<CanvasComponentContainer>& containerPtr = objInfoBeingEdited->obj;
BrushStrokeCanvasComponent& brushStroke = static_cast<BrushStrokeCanvasComponent&>(containerPtr->get_comp());
auto& brushPoints = *brushStroke.d.points;
float width = toolConfig.get_relative_width_stroke_size(drawP, containerPtr->coords.inverseScale).first.value() * penWidth;
BrushStrokeCanvasComponentPoint p;
p.pos = containerPtr->coords.get_mouse_pos(drawP.world);
p.width = toolConfig.get_relative_width(drawP, containerPtr->coords.inverseScale, toolConfig.brush.relativeWidth) * penWidth;
p.width = width;
if(!addedTemporaryPoint) {
if(extensive_point_checking(brushStroke, p.pos)) {
@ -132,7 +141,7 @@ void BrushTool::tool_update() {
brushPoints.back().width = std::max(brushPoints.back().width, p.width);
brushPoints[brushPoints.size() - 2].width = std::max(brushPoints[brushPoints.size() - 2].width, p.width);
}
if((!drawingMinimumRelativeToSize && distToPrev >= 10.0) || (drawingMinimumRelativeToSize && distToPrev >= toolConfig.get_relative_width(drawP, containerPtr->coords.inverseScale, toolConfig.brush.relativeWidth) * BrushStrokeCanvasComponent::DRAW_MINIMUM_LIMIT)) {
if((!drawingMinimumRelativeToSize && distToPrev >= 10.0) || (drawingMinimumRelativeToSize && distToPrev >= width * BrushStrokeCanvasComponent::DRAW_MINIMUM_LIMIT)) {
brushPoints.back() = p;
addedTemporaryPoint = false;
@ -167,7 +176,7 @@ void BrushTool::gui_toolbox() {
t.gui.push_id("brush tool");
t.gui.text_label_centered("Brush");
t.gui.checkbox_field("hasroundcaps", "Round Caps", &drawP.world.main.toolConfig.brush.hasRoundCaps);
drawP.world.main.toolConfig.relative_width_gui(drawP, "Size", &drawP.world.main.toolConfig.brush.relativeWidth);
drawP.world.main.toolConfig.relative_width_gui(drawP, "Size");
t.gui.pop_id();
}
@ -183,16 +192,19 @@ bool BrushTool::prevent_undo_or_redo() {
void BrushTool::draw(SkCanvas* canvas, const DrawData& drawData) {
if(!drawData.main->toolbar.io->hoverObstructed) {
SkPaint linePaint;
linePaint.setColor4f(drawP.world.canvasTheme.get_tool_front_color());
linePaint.setStroke(true);
linePaint.setStrokeWidth(0.0f);
float width = drawP.world.main.toolConfig.get_relative_width(drawP, drawP.world.drawData.cam.c.inverseScale, drawP.world.main.toolConfig.brush.relativeWidth);
if(objInfoBeingEdited)
width *= penWidth * 0.5f;
else
width *= 0.5f;
Vector2f pos = drawData.main->input.mouse.pos;
canvas->drawCircle(pos.x(), pos.y(), width, linePaint);
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
if(relativeWidthResult.first.has_value()) {
SkPaint linePaint;
linePaint.setColor4f(drawP.world.canvasTheme.get_tool_front_color());
linePaint.setStroke(true);
linePaint.setStrokeWidth(0.0f);
float width = relativeWidthResult.first.value();
if(objInfoBeingEdited)
width *= penWidth * 0.5f;
else
width *= 0.5f;
Vector2f pos = drawData.main->input.mouse.pos;
canvas->drawCircle(pos.x(), pos.y(), width, linePaint);
}
}
}

View file

@ -27,7 +27,7 @@ void EllipseDrawTool::gui_toolbox() {
if(t.gui.radio_button_field("outlineonly", "Outline only", fillStrokeMode == 1)) fillStrokeMode = 1;
if(t.gui.radio_button_field("filloutline", "Fill and Outline", fillStrokeMode == 2)) fillStrokeMode = 2;
if(fillStrokeMode == 1 || fillStrokeMode == 2)
toolConfig.relative_width_gui(drawP, "Outline Size", &toolConfig.ellipseDraw.relativeWidth);
toolConfig.relative_width_gui(drawP, "Outline Size");
t.gui.pop_id();
}
@ -51,13 +51,21 @@ void EllipseDrawTool::tool_update() {
if(!objInfoBeingEdited) {
if(drawP.controls.leftClick && drawP.layerMan.is_a_layer_being_edited()) {
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
if(!relativeWidthResult.first.has_value()) {
if(drawP.controls.leftClick)
drawP.world.main.toolConfig.print_relative_width_fail_message(relativeWidthResult.second);
return;
}
float width = relativeWidthResult.first.value();
CanvasComponentContainer* newContainer = new CanvasComponentContainer(drawP.world.netObjMan, CanvasComponentType::ELLIPSE);
EllipseCanvasComponent& newEllipse = static_cast<EllipseCanvasComponent&>(newContainer->get_comp());
startAt = drawP.world.main.input.mouse.pos;
newEllipse.d.strokeColor = toolConfig.globalConf.foregroundColor;
newEllipse.d.fillColor = toolConfig.globalConf.backgroundColor;
newEllipse.d.strokeWidth = toolConfig.get_relative_width(drawP, drawP.world.drawData.cam.c.inverseScale, toolConfig.ellipseDraw.relativeWidth);
newEllipse.d.strokeWidth = width;
newEllipse.d.p1 = startAt;
newEllipse.d.p2 = startAt;
newEllipse.d.p2 = ensure_points_have_distance(newEllipse.d.p1, newEllipse.d.p2, MINIMUM_DISTANCE_BETWEEN_BOUNDS);

View file

@ -16,7 +16,7 @@ void EraserTool::gui_toolbox() {
Toolbar& t = drawP.world.main.toolbar;
t.gui.push_id("eraser tool");
t.gui.text_label_centered("Eraser");
drawP.world.main.toolConfig.relative_width_gui(drawP, "Size", &drawP.world.main.toolConfig.eraser.relativeWidth);
drawP.world.main.toolConfig.relative_width_gui(drawP, "Size");
t.gui.text_label("Erase from:");
if(t.gui.radio_button_field("layer edited", "Layer being edited", drawP.controls.layerSelector == DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED))
drawP.controls.layerSelector = DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED;
@ -44,9 +44,16 @@ void EraserTool::tool_update() {
drawP.world.main.input.hideCursor = true;
if(drawP.controls.leftClickHeld) {
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
if(!relativeWidthResult.first.has_value()) {
if(drawP.controls.leftClick)
drawP.world.main.toolConfig.print_relative_width_fail_message(relativeWidthResult.second);
return;
}
float width = relativeWidthResult.first.value() * 0.5f;
const Vector2f& prevMousePos = drawP.controls.leftClick ? drawP.world.main.input.mouse.pos : drawP.world.main.input.mouse.lastPos;
SCollision::ColliderCollection<float> cC;
float width = drawP.world.main.toolConfig.get_relative_width(drawP, drawP.world.drawData.cam.c.inverseScale, drawP.world.main.toolConfig.eraser.relativeWidth);
SCollision::generate_wide_line(cC, prevMousePos, drawP.world.main.input.mouse.pos, width * 2.0f, true);
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
@ -90,18 +97,21 @@ bool EraserTool::prevent_undo_or_redo() {
void EraserTool::draw(SkCanvas* canvas, const DrawData& drawData) {
if(!drawData.main->toolbar.io->hoverObstructed) {
SkColor4f c = drawP.world.canvasTheme.get_tool_front_color();
c.fA = 0.5f;
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
if(relativeWidthResult.first.has_value()) {
float width = relativeWidthResult.first.value() * 0.5f;
SkColor4f c = drawP.world.canvasTheme.get_tool_front_color();
c.fA = 0.5f;
SkPaint linePaint;
linePaint.setColor4f(c);
linePaint.setStyle(SkPaint::kStroke_Style);
linePaint.setStrokeCap(SkPaint::kRound_Cap);
float width = drawP.world.main.toolConfig.get_relative_width(drawP, drawP.world.drawData.cam.c.inverseScale, drawP.world.main.toolConfig.eraser.relativeWidth);
linePaint.setStrokeWidth(width * 2.0f);
SkPathBuilder erasePath;
erasePath.moveTo(convert_vec2<SkPoint>(drawData.main->input.mouse.lastPos));
erasePath.lineTo(convert_vec2<SkPoint>(drawData.main->input.mouse.pos));
canvas->drawPath(erasePath.detach(), linePaint);
SkPaint linePaint;
linePaint.setColor4f(c);
linePaint.setStyle(SkPaint::kStroke_Style);
linePaint.setStrokeCap(SkPaint::kRound_Cap);
linePaint.setStrokeWidth(width * 2.0f);
SkPathBuilder erasePath;
erasePath.moveTo(convert_vec2<SkPoint>(drawData.main->input.mouse.lastPos));
erasePath.lineTo(convert_vec2<SkPoint>(drawData.main->input.mouse.pos));
canvas->drawPath(erasePath.detach(), linePaint);
}
}
}

View file

@ -21,7 +21,7 @@ void LineDrawTool::gui_toolbox() {
auto& toolConfig = drawP.world.main.toolConfig;
t.gui.push_id("rect draw tool");
t.gui.text_label_centered("Draw Line");
toolConfig.relative_width_gui(drawP, "Size", &toolConfig.lineDraw.relativeWidth);
toolConfig.relative_width_gui(drawP, "Size");
t.gui.checkbox_field("hasroundcaps", "Round Caps", &toolConfig.lineDraw.hasRoundCaps);
t.gui.pop_id();
}
@ -45,12 +45,20 @@ void LineDrawTool::tool_update() {
auto& toolConfig = drawP.world.main.toolConfig;
if(!objInfoBeingEdited) {
if(drawP.controls.leftClick && drawP.layerMan.is_a_layer_being_edited()) {
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
if(!relativeWidthResult.first.has_value()) {
if(drawP.controls.leftClick)
drawP.world.main.toolConfig.print_relative_width_fail_message(relativeWidthResult.second);
return;
}
float width = relativeWidthResult.first.value();
CanvasComponentContainer* newBrushStrokeContainer = new CanvasComponentContainer(drawP.world.netObjMan, CanvasComponentType::BRUSHSTROKE);
BrushStrokeCanvasComponent& newBrushStroke = static_cast<BrushStrokeCanvasComponent&>(newBrushStrokeContainer->get_comp());
BrushStrokeCanvasComponentPoint p;
p.pos = drawP.world.main.input.mouse.pos;
p.width = toolConfig.get_relative_width(drawP, drawP.world.drawData.cam.c.inverseScale, toolConfig.lineDraw.relativeWidth);
p.width = width;
newBrushStroke.d.points->emplace_back(p);
p.pos = ensure_points_have_distance(p.pos, p.pos, 1.0f);
newBrushStroke.d.points->emplace_back(p);

View file

@ -26,7 +26,7 @@ void RectDrawTool::gui_toolbox() {
if(t.gui.radio_button_field("outlineonly", "Outline only", fillStrokeMode == 1)) fillStrokeMode = 1;
if(t.gui.radio_button_field("filloutline", "Fill and Outline", fillStrokeMode == 2)) fillStrokeMode = 2;
if(fillStrokeMode == 1 || fillStrokeMode == 2)
toolConfig.relative_width_gui(drawP, "Outline Size", &toolConfig.rectDraw.relativeWidth);
toolConfig.relative_width_gui(drawP, "Outline Size");
t.gui.pop_id();
}
@ -52,14 +52,24 @@ void RectDrawTool::tool_update() {
if(!objInfoBeingEdited) {
if(drawP.controls.leftClick && drawP.layerMan.is_a_layer_being_edited()) {
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
auto relativeRadiusWidthResult = drawP.world.main.toolConfig.get_relative_width_from_value(drawP, drawP.world.drawData.cam.c.inverseScale, relativeRadiusWidth);
if(!relativeWidthResult.first.has_value() || !relativeRadiusWidthResult.first.has_value()) {
if(drawP.controls.leftClick)
drawP.world.main.toolConfig.print_relative_width_fail_message(relativeWidthResult.second);
return;
}
float width = relativeWidthResult.first.value();
float radiusWidth = relativeRadiusWidthResult.first.value();
CanvasComponentContainer* newContainer = new CanvasComponentContainer(drawP.world.netObjMan, CanvasComponentType::RECTANGLE);
RectangleCanvasComponent& newRectangle = static_cast<RectangleCanvasComponent&>(newContainer->get_comp());
startAt = drawP.world.main.input.mouse.pos;
newRectangle.d.strokeColor = toolConfig.globalConf.foregroundColor;
newRectangle.d.fillColor = toolConfig.globalConf.backgroundColor;
newRectangle.d.cornerRadius = relativeRadiusWidth;
newRectangle.d.strokeWidth = toolConfig.get_relative_width(drawP, drawP.world.drawData.cam.c.inverseScale, toolConfig.rectDraw.relativeWidth);
newRectangle.d.cornerRadius = radiusWidth;
newRectangle.d.strokeWidth = width;
newRectangle.d.p1 = startAt;
newRectangle.d.p2 = startAt;
newRectangle.d.p2 = ensure_points_have_distance(newRectangle.d.p1, newRectangle.d.p2, MINIMUM_DISTANCE_BETWEEN_BOUNDS);