diff --git a/.gitignore b/.gitignore
index 100b5bd7257804cd7da84baf28ac3fad7abbfcf1..99808bb1b5b5092f39a1e0fe8f9d6da0c6a36362 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,3 +72,8 @@ tags
# YCM code completer
.ycm_*
+
+# Ignore build and install files
+install-prefix/
+bin/
+lib/
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e1cd54d54db39245dcdcadb4916229bc74123b28..2e9d4762dbbca364a800af77a3df524d81706503 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -100,7 +100,6 @@ inkscape:linux:
paths:
- build/
-
appimage:linux:
stage: build
timeout: 3h
@@ -212,6 +211,7 @@ inkscape:windows:
#tests, always run after building
test:linux:
stage: test
+ timeout: 3h
rules:
- *do_not_run_for_schedules
- *run_otherwise
diff --git a/share/ui/toolbar-rect.ui b/share/ui/toolbar-rect.ui
index 69902e957e4c76dc99e4dfe34e9171b2c5c6c9f9..9bea068b08ca35ff87cc65d23698f8df95ebff0e 100644
--- a/share/ui/toolbar-rect.ui
+++ b/share/ui/toolbar-rect.ui
@@ -52,6 +52,20 @@
+
+
+
Height of rectangle
@@ -96,6 +110,20 @@
+
+
+ center
+ True
+ True
+ False
+ Lock Rx and Ry proportions
+
+
+ object-unlocked
+
+
+
+
Vertical radius of rounded corners
diff --git a/src/attributes.cpp b/src/attributes.cpp
index 54cd18c4284084c43b10a3e99a3ead6e15b6f960..99525fe6226419cd2b12f53a018282791843d554 100644
--- a/src/attributes.cpp
+++ b/src/attributes.cpp
@@ -598,6 +598,12 @@ static SPStyleProp const props[] = {
// named view settings
{SPAttr::INKSCAPE_ORIGIN_CORRECTION, "origin-correction"},
{SPAttr::INKSCAPE_Y_AXIS_DOWN, "y-axis-down"},
+
+ // rectangle lock aspect ratio state
+ {SPAttr::INKSCAPE_LOCK_WH, "inkscape:lock-wh"},
+ {SPAttr::INKSCAPE_ASPECT_RATIO_WH, "inkscape:aspect-ratio-wh"},
+ {SPAttr::INKSCAPE_LOCK_RXY, "inkscape:lock-rxy"},
+ {SPAttr::INKSCAPE_ASPECT_RATIO_RXY, "inkscape:aspect-ratio-rxy"},
};
#define n_attrs (sizeof(props) / sizeof(props[0]))
diff --git a/src/attributes.h b/src/attributes.h
index 85eb0b86177a057a3d97ad1c387e7dbd2c9c1794..05b56ec4f9e934ed7fc7fc65e65e1baa431906c9 100644
--- a/src/attributes.h
+++ b/src/attributes.h
@@ -600,8 +600,16 @@ enum class SPAttr {
INKSCAPE_ORIGIN_CORRECTION,
INKSCAPE_Y_AXIS_DOWN,
+
+ // rectangle lock aspect ratio state
+ INKSCAPE_LOCK_WH,
+ INKSCAPE_ASPECT_RATIO_WH,
+ INKSCAPE_LOCK_RXY,
+ INKSCAPE_ASPECT_RATIO_RXY,
+
// sentinel
- SPAttr_SIZE
+ SPAttr_SIZE,
+
};
/**
diff --git a/src/object/sp-rect.cpp b/src/object/sp-rect.cpp
index def363af991feaf73f1f7495b848126e657be7b1..c6dfca44780966008a8572e753f3659ac9e43e89 100644
--- a/src/object/sp-rect.cpp
+++ b/src/object/sp-rect.cpp
@@ -32,7 +32,7 @@
//#define OBJECT_TRACE
SPRect::SPRect() : SPShape()
- ,type(SP_GENERIC_RECT_UNDEFINED)
+ ,type(SP_GENERIC_RECT_UNDEFINED), lock_wh(false), lock_rxy(false)
{
}
@@ -67,6 +67,12 @@ void SPRect::build(SPDocument* doc, Inkscape::XML::Node* repr) {
this->readAttr(SPAttr::RX);
this->readAttr(SPAttr::RY);
+ // Read custom attributes for lock state and aspect ratio
+ this->readAttr(SPAttr::INKSCAPE_LOCK_WH);
+ this->readAttr(SPAttr::INKSCAPE_ASPECT_RATIO_WH);
+ this->readAttr(SPAttr::INKSCAPE_LOCK_RXY);
+ this->readAttr(SPAttr::INKSCAPE_ASPECT_RATIO_RXY);
+
#ifdef OBJECT_TRACE
objectTrace( "SPRect::build", false );
#endif
@@ -132,7 +138,24 @@ void SPRect::set(SPAttr key, gchar const *value) {
this->ry.update( em, ex, h );
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
break;
-
+ case SPAttr::INKSCAPE_LOCK_WH:
+ lock_wh.readOrUnset(value);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+ case SPAttr::INKSCAPE_ASPECT_RATIO_WH:
+ aspect_ratio_wh = value ? g_ascii_strtod(value, nullptr) : 1.0;
+ if (aspect_ratio_wh <= 0) aspect_ratio_wh = 1.0;
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+ case SPAttr::INKSCAPE_LOCK_RXY:
+ lock_rxy.readOrUnset(value);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+ case SPAttr::INKSCAPE_ASPECT_RATIO_RXY:
+ aspect_ratio_rxy = value ? g_ascii_strtod(value, nullptr) : 1.0;
+ if (aspect_ratio_rxy <= 0) aspect_ratio_rxy = 1.0;
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
default:
SPShape::set(key, value);
break;
@@ -223,6 +246,23 @@ Inkscape::XML::Node * SPRect::write(Inkscape::XML::Document *xml_doc, Inkscape::
repr->setAttributeSvgLength("x", this->x);
repr->setAttributeSvgLength("y", this->y);
+
+ // Write custom attributes
+ if (lock_wh) {
+ repr->setAttribute("inkscape:lock-wh", lock_wh ? "true" : "false");
+ repr->setAttributeSvgDouble("inkscape:aspect-ratio-wh", this->aspect_ratio_wh);
+ } else {
+ repr->removeAttribute("inkscape:lock-wh");
+ repr->removeAttribute("inkscape:aspect-ratio-wh");
+ }
+ if (lock_rxy) {
+ repr->setAttribute("inkscape:lock-rxy", lock_rxy ? "true" : "false");
+ repr->setAttributeSvgDouble("inkscape:aspect-ratio-rxy", this->aspect_ratio_rxy);
+ } else {
+ repr->removeAttribute("inkscape:lock-rxy");
+ repr->removeAttribute("inkscape:aspect-ratio-rxy");
+ }
+
// write d=
if (type == SP_GENERIC_PATH) {
set_rect_path_attribute(repr); // include set_shape()
@@ -647,6 +687,38 @@ void SPRect::convert_to_guides() const {
sp_guide_pt_pairs_to_guides(this->document, pts);
}
+void SPRect::setLockWh(bool lock) {
+ lock_wh = lock;
+}
+
+bool SPRect::getLockWh() const {
+ return lock_wh;
+}
+
+void SPRect::setLockRxy(bool lock) {
+ lock_rxy = lock;
+}
+
+bool SPRect::getLockRxy() const {
+ return lock_rxy;
+}
+
+void SPRect::setAspectRatioWh(double ratio) {
+ aspect_ratio_wh = ratio;
+}
+
+double SPRect::getAspectRatioWh() const {
+ return aspect_ratio_wh;
+}
+
+void SPRect::setAspectRatioRxy(double ratio) {
+ aspect_ratio_rxy = ratio;
+}
+
+double SPRect::getAspectRatioRxy() const {
+ return aspect_ratio_rxy;
+}
+
/*
Local Variables:
mode:c++
diff --git a/src/object/sp-rect.h b/src/object/sp-rect.h
index f157e81e10cd8f873f3da83f180187fa88ad91fd..d3e4b2f7639c59fc00795ab057080a09e28b9c6a 100644
--- a/src/object/sp-rect.h
+++ b/src/object/sp-rect.h
@@ -18,6 +18,7 @@
#include <2geom/forward.h>
#include "svg/svg-length.h"
+#include "svg/svg-bool.h"
#include "sp-shape.h"
enum GenericRectType {
@@ -54,6 +55,20 @@ public:
double getVisibleHeight() const;
void setVisibleHeight(double ry);
+ bool getLockWh() const;
+ void setLockWh(bool lock);
+
+ bool getLockRxy() const;
+ void setLockRxy(bool lock);
+
+ double getAspectRatioWh() const;
+ void setAspectRatioWh(double ratio);
+
+ double getAspectRatioRxy() const;
+ void setAspectRatioRxy(double ratio);
+
+
+
void compensateRxRy(Geom::Affine xform);
void build(SPDocument* doc, Inkscape::XML::Node* repr) override;
@@ -79,6 +94,10 @@ public:
SVGLength height;
SVGLength rx;
SVGLength ry;
+ SVGBool lock_wh{false}; // Default false, unset
+ SVGBool lock_rxy{false}; // Default false, unset
+ double aspect_ratio_wh=0.0;
+ double aspect_ratio_rxy=0.0;
private:
static double vectorStretch(Geom::Point p0, Geom::Point p1, Geom::Affine xform);
diff --git a/src/ui/shape-editor-knotholders.cpp b/src/ui/shape-editor-knotholders.cpp
index 77cb2133336e791af341290bf22b512b6361f51b..9b6b6595d556e25dcd04c6b82ba657598cfb939e 100644
--- a/src/ui/shape-editor-knotholders.cpp
+++ b/src/ui/shape-editor-knotholders.cpp
@@ -245,7 +245,15 @@ RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*orig
if (state & GDK_CONTROL_MASK) {
gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
rect->rx = rect->ry = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, temp);
- } else {
+ }else if(rect->getLockRxy()&& rect->ry._set)
+ {
+ double new_rx = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0);
+ rect->rx = new_rx;
+ rect->ry = new_rx * rect->getAspectRatioRxy();
+ rect->ry = MIN(rect->ry.computed, rect->height.computed / 2.0);
+
+ }
+ else {
rect->rx = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0);
}
@@ -295,7 +303,14 @@ RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*orig
// resulting in a perfect circle (and not an ellipse)
gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
rect->rx = rect->ry = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp);
- } else {
+ }else if(rect->getLockRxy()&& rect->rx._set)
+ {
+ double new_ry = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, rect->height.computed / 2.0);
+ rect->ry = new_ry;
+ rect->rx = new_ry / rect->getAspectRatioRxy();
+ rect->rx = MIN(rect->rx.computed, rect->width.computed / 2.0);
+ }
+ else {
if (!rect->rx._set || rect->rx.computed == 0) {
rect->ry = CLAMP(s[Geom::Y] - rect->y.computed,
0.0,
@@ -357,6 +372,8 @@ RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &or
g_assert(rect != nullptr);
Geom::Point s = p;
+ double old_width = rect->width.computed;
+ double old_height = rect->height.computed;
if (state & GDK_CONTROL_MASK) {
// original width/height when drag started
@@ -412,7 +429,29 @@ RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &or
}
- } else {
+ } else if (rect->getLockWh()){
+
+ s = snap_knot_position(p, state);
+ double new_width = MAX(s[Geom::X] - rect->x.computed, 0);
+ double new_height = MAX(s[Geom::Y] - rect->y.computed, 0);
+
+
+ double aspect = rect->getAspectRatioWh(); // height / width
+ bool width_changed = fabs(new_width - old_width) > 1e-6;
+ bool height_changed = fabs(new_height - old_height) > 1e-6;
+
+ if (width_changed && !height_changed) {
+ new_height = new_width * aspect;
+ } else if (height_changed && !width_changed) {
+ new_width = new_height / aspect;
+ } else if (width_changed && height_changed) {
+ new_height = new_width * aspect; // Prioritize width for corner drag
+ }
+
+ rect->width = new_width;
+ rect->height = new_height;
+ }
+ else {
// move freely
s = snap_knot_position(p, state);
rect->width = MAX(s[Geom::X] - rect->x.computed, 0);
diff --git a/src/ui/toolbar/rect-toolbar.cpp b/src/ui/toolbar/rect-toolbar.cpp
index 4887e6078d8eb2aa2a96a2590862ef0577ccadbc..872cb1965c441c4ffe9940efb88add9717c18d23 100644
--- a/src/ui/toolbar/rect-toolbar.cpp
+++ b/src/ui/toolbar/rect-toolbar.cpp
@@ -27,12 +27,16 @@
#include "rect-toolbar.h"
+#include
+
#include
#include
#include
-#include
+#include
#include
#include
+#include
+#include
#include "desktop.h"
#include "document-undo.h"
@@ -47,6 +51,7 @@
#include "ui/widget/spinbutton.h"
#include "ui/widget/unit-tracker.h"
#include "widgets/widget-sizes.h"
+#include "preferences.h"
using Inkscape::UI::Widget::UnitTracker;
using Inkscape::DocumentUndo;
@@ -85,12 +90,19 @@ RectToolbar::RectToolbar(Glib::RefPtr const &builder)
, _height_item{UI::get_derived_widget(builder, "_height_item", "height", &SPRect::getVisibleHeight, &SPRect::setVisibleHeight)}
, _rx_item{UI::get_derived_widget(builder, "_rx_item", "rx", &SPRect::getVisibleRx, &SPRect::setVisibleRx)}
, _ry_item{UI::get_derived_widget(builder, "_ry_item", "ry", &SPRect::getVisibleRy, &SPRect::setVisibleRy)}
+ , _lock_wh_button{get_widget(builder, "_lock_wh_button")}
+ , _lock_rxy_button{get_widget(builder, "_lock_rxy_button")}
{
+ auto prefs = Inkscape::Preferences::get();
auto unit_menu = _tracker->create_tool_item(_("Units"), (""));
get_widget(builder, "unit_menu_box").append(*unit_menu);
_not_rounded.signal_clicked().connect([this] { _setDefaults(); });
+ // Configure the lock buttons
+ _lock_wh_button.signal_toggled().connect([this] { toggle_lock_wh(); });
+ _lock_rxy_button.signal_toggled().connect([this] { toggle_lock_rxy(); });
+
for (auto sb : _getDerivedSpinButtons()) {
auto const adj = sb->get_adjustment();
auto const path = Glib::ustring{"/tools/shapes/rect/"} + sb->name;
@@ -213,20 +225,52 @@ void RectToolbar::_valueChanged(DerivedSpinButton &btn)
auto guard = _blocker.block();
auto const adj = btn.get_adjustment();
+ double value = Quantity::convert(adj->get_value(), _tracker->getActiveUnit(), "px");
+ // save the new value to preferences
if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
auto const path = Glib::ustring{"/tools/shapes/rect/"} + btn.name;
- Preferences::get()->setDouble(path, Quantity::convert(adj->get_value(), _tracker->getActiveUnit(), "px"));
+ Preferences::get()->setDouble(path, value);
}
bool modified = false;
for (auto item : _desktop->getSelection()->items()) {
if (auto rect = cast(item)) {
- if (adj->get_value() != 0) {
- (rect->*btn.setter)(Quantity::convert(adj->get_value(), _tracker->getActiveUnit(), "px"));
+ double new_value = value;
+ double paired_value = 0.0;
+
+ if (&btn == &_width_item && rect->getLockWh()) {
+ paired_value = new_value * rect->getAspectRatioWh();
+ _height_item.get_adjustment()->set_value(Quantity::convert(paired_value, "px", _tracker->getActiveUnit()));
+ rect->setVisibleHeight(paired_value);
+ } else if (&btn == &_height_item && rect->getLockWh()) {
+ paired_value = new_value / rect->getAspectRatioWh();
+ _width_item.get_adjustment()->set_value(Quantity::convert(paired_value, "px", _tracker->getActiveUnit()));
+ rect->setVisibleWidth(paired_value);
+ } else if (&btn == &_rx_item && rect->getLockRxy()) {
+ paired_value = new_value * rect->getAspectRatioRxy();
+ _ry_item.get_adjustment()->set_value(Quantity::convert(paired_value, "px", _tracker->getActiveUnit()));
+ rect->setVisibleRy(paired_value);
+ } else if (&btn == &_ry_item && rect->getLockRxy()) {
+ paired_value = new_value / rect->getAspectRatioRxy();
+ _rx_item.get_adjustment()->set_value(Quantity::convert(paired_value, "px", _tracker->getActiveUnit()));
+ rect->setVisibleRx(paired_value);
+ }
+
+ // Update the primary dimension
+ if (new_value != 0) {
+ (rect->*btn.setter)(new_value);
} else {
rect->removeAttribute(btn.name);
}
+
+ // Update aspect ratio after change
+ double w = rect->getVisibleWidth();
+ double h = rect->getVisibleHeight();
+ rect->setAspectRatioWh(h / (w != 0 ? w : 1.0));
+ double rx = rect->getVisibleRx();
+ double ry = rect->getVisibleRy();
+ rect->setAspectRatioRxy(ry / (rx != 0 ? rx : 1.0));
modified = true;
}
}
@@ -275,6 +319,10 @@ void RectToolbar::_selectionChanged(Selection *selection)
if (_single) {
_attachRepr(repr, rect);
+ _lock_wh_button.set_active(rect->getLockWh());
+ _lock_rxy_button.set_active(rect->getLockRxy());
+ _aspect_ratio_wh = rect->getAspectRatioWh();
+ _aspect_ratio_rxy = rect->getAspectRatioRxy();
_queueUpdate();
}
@@ -340,6 +388,29 @@ void RectToolbar::_update()
_sensitivize();
}
+void RectToolbar::toggle_lock_wh() {
+ bool active = _lock_wh_button.get_active();
+ if (_single && _rect) {
+ _rect->setLockWh(active);
+ _lock_wh_button.set_image_from_icon_name(active ? "object-locked" : "object-unlocked");
+ double w = _rect->getVisibleWidth();
+ double h = _rect->getVisibleHeight();
+ _rect->setAspectRatioWh(h / (w != 0 ? w : 1.0)); // Update aspect ratio when toggling
+ DocumentUndo::done(_desktop->getDocument(), _("Toggle rectangle lock"), INKSCAPE_ICON("draw-rectangle"));
+ }
+}
+
+void RectToolbar::toggle_lock_rxy() {
+ bool active = _lock_rxy_button.get_active();
+ if (_single && _rect) {
+ _rect->setLockRxy(active);
+ _lock_rxy_button.set_image_from_icon_name(active ? "object-locked" : "object-unlocked");
+ double rx = _rect->getVisibleRx();
+ double ry = _rect->getVisibleRy();
+ _rect->setAspectRatioRxy(ry / (rx != 0 ? rx : 1.0));
+ DocumentUndo::done(_desktop->getDocument(), _("Toggle rectangle lock"), INKSCAPE_ICON("draw-rectangle"));
+ }
+}
} // namespace Inkscape::UI::Toolbar
/*
diff --git a/src/ui/toolbar/rect-toolbar.h b/src/ui/toolbar/rect-toolbar.h
index 14d7d1f414c9c16eb669d3b0c00e98e75aa442e5..1296342cdabcbd8d99997319fdc33765e7996b3a 100644
--- a/src/ui/toolbar/rect-toolbar.h
+++ b/src/ui/toolbar/rect-toolbar.h
@@ -29,8 +29,9 @@
*/
#include
-
+#include
#include "toolbar.h"
+#include "preferences.h"
#include "ui/operation-blocker.h"
#include "xml/node-observer.h"
@@ -39,6 +40,7 @@ class Builder;
class Button;
class Label;
class Adjustment;
+class ToggleButton;
} // namespace Gtk
class SPRect;
@@ -68,6 +70,8 @@ public:
void setDesktop(SPDesktop *desktop) override;
void setActiveUnit(Util::Unit const *unit) override;
+ bool getLockWH() const { return _lock_wh; }
+ bool getLockRXY() const { return _lock_rxy; }
private:
RectToolbar(Glib::RefPtr const &builder);
@@ -85,6 +89,16 @@ private:
auto _getDerivedSpinButtons() const { return std::to_array({&_rx_item, &_ry_item, &_width_item, &_height_item}); }
void _valueChanged(DerivedSpinButton &btn);
+ // Add lock button and state
+ Gtk::ToggleButton &_lock_wh_button; // New toggle button for locking width/height
+ Gtk::ToggleButton &_lock_rxy_button; // New toggle button for locking rx/ry
+ bool _lock_wh = false;
+ bool _lock_rxy = false;
+ double _aspect_ratio_wh=0.0;
+ double _aspect_ratio_rxy=0.0;
+ void toggle_lock_wh();
+ void toggle_lock_rxy();
+
XML::Node *_repr = nullptr;
SPRect *_rect = nullptr;
void _attachRepr(XML::Node *repr, SPRect *rect);
diff --git a/testfiles/CMakeLists.txt b/testfiles/CMakeLists.txt
index 0f2591485378bb1448569c3252c5712f9b94d279..f8283f629b67a223caf679ef37f85daf73c190cd 100644
--- a/testfiles/CMakeLists.txt
+++ b/testfiles/CMakeLists.txt
@@ -142,6 +142,7 @@ foreach(test_source ${TEST_SOURCES})
add_executable(${testname} src/${test_source}.cpp)
target_include_directories(${testname} SYSTEM PRIVATE ${GTEST_INCLUDE_DIRS})
target_link_libraries(${testname} cpp_test_static_library 2Geom::2geom)
+ set_target_properties(${testname} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
add_test(NAME ${testname} COMMAND ${testname})
set_tests_properties(${testname} PROPERTIES ENVIRONMENT "${INKSCAPE_TEST_PROFILE_DIR_ENV}/${testname};${CMAKE_CTEST_ENV}")
add_dependencies(tests ${testname})