diff --git a/share/ui/toolbar-star.ui b/share/ui/toolbar-star.ui index ddaf932d719c46f648a0bbb32018558c23b6e8b0..fc4aca34156228fee40d1fdfda82be77598c7529 100644 --- a/share/ui/toolbar-star.ui +++ b/share/ui/toolbar-star.ui @@ -29,6 +29,11 @@ True True + + 1000000 + 0.10 + 5 + start center @@ -195,6 +200,34 @@ + + + Length of edge + + + 5 + Edge: + + + + + True + 7 + 0.000 + _length_adj + 0.10 + 3 + + + + + + + + + diff --git a/src/object/sp-star.cpp b/src/object/sp-star.cpp index de4e195d97824ff50916b8f20405569757841577..dff17d8d5779dccf7fea9249e2df213b8da73179 100644 --- a/src/object/sp-star.cpp +++ b/src/object/sp-star.cpp @@ -522,6 +522,74 @@ void SPStar::update_patheffect(bool write) { SPShape::update_patheffect(write); } +/** + * Calculate the average side length of the polygon. + * + * For spoked polygons (stars) this is the radius delta; for non-spoked + * polygons this is the regular side length directly. + * + * @returns the average length of the polygon sides. + */ +double SPStar::getSideLength() const +{ + if (!flatsided) { + // Pointy star + double totalLength = 0.0; + auto tr = i2doc_affine(); + + for (gint i = 0; i < sides; i++) { + Geom::Point outer1 = sp_star_get_xy(this, SP_STAR_POINT_KNOT1, i, false) * tr; + Geom::Point inner1 = sp_star_get_xy(this, SP_STAR_POINT_KNOT2, i, false) * tr; + + totalLength += Geom::distance(outer1, inner1); + + Geom::Point outer2 = sp_star_get_xy(this, SP_STAR_POINT_KNOT1, (i + 1) % sides, false) * tr; + totalLength += Geom::distance(inner1, outer2); + } + + // Return the average side length (since we have 2 * sides distances, divide by 2 * sides) + return totalLength / (2 * sides); + } + + double diameter = 0.0; + auto tr = i2doc_affine(); + for (gint i = 0; i < sides; i++) { + diameter += Geom::distance(sp_star_get_xy(this, SP_STAR_POINT_KNOT1, i, false) * tr, + sp_star_get_xy(this, SP_STAR_POINT_KNOT1, (i + 1) % sides, false) * tr); + } + return diameter / sides; +} + +/** + * Set the average side length of the polygon. + * + * For spoked polygons (stars) this is the radius delta; for non-spoked + * polygons this is the regular side length directly. + * + * @param length the new average length of the polygon sides. + */ +void SPStar::setSideLength(double length) { + double currentLength = getSideLength(); + if (currentLength <= 0 || length <= 0) { + return; // Prevent division by zero or invalid scaling + } + + double scale = length / currentLength; + + if (!flatsided) { + // Pointy star + r[0] *= scale; + r[1] *= scale; + } else { + // Flat star + r[0] *= scale; + } + + this -> set_shape(); + + requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + /** * sp_star_get_xy: Get X-Y value as item coordinate system * @star: star item diff --git a/src/object/sp-star.h b/src/object/sp-star.h index 47abebf70b9487ee47650ccc77cda3b2a39b036b..1066847f57d3ba94fdea769f8e3533a5d60d5e35 100644 --- a/src/object/sp-star.h +++ b/src/object/sp-star.h @@ -38,11 +38,6 @@ public: double rounded; double randomized; -// CPPIFY: This derivation is a bit weird. -// parent_class = reinterpret_cast(g_type_class_ref(SP_TYPE_SHAPE)); -// So shouldn't star be derived from shape instead of polygon? -// What does polygon have that shape doesn't? - void build(SPDocument *document, Inkscape::XML::Node *repr) override; Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags) override; void set(SPAttr key, char const* value) override; @@ -55,6 +50,8 @@ public: void update_patheffect(bool write) override; void set_shape() override; Geom::Affine set_transform(Geom::Affine const& xform) override; + double getSideLength() const; + void setSideLength(double length); }; void sp_star_position_set (SPStar *star, int sides, Geom::Point center, double r1, double r2, double arg1, double arg2, bool isflat, double rounded, double randomized); diff --git a/src/ui/dialog/object-attributes.cpp b/src/ui/dialog/object-attributes.cpp index 9bf7aaec465d0566a0140f91f69e55069a5de1a2..f73c6d515168495177bd16ed349e609f6fed09bc 100644 --- a/src/ui/dialog/object-attributes.cpp +++ b/src/ui/dialog/object-attributes.cpp @@ -726,13 +726,13 @@ public: _path->updateRepr(); }); }); + _clear_rnd.signal_clicked().connect([this](){ _rand.get_adjustment()->set_value(0); }); _clear_round.signal_clicked().connect([this](){ _rounded.get_adjustment()->set_value(0); }); _clear_ratio.signal_clicked().connect([this](){ _ratio.get_adjustment()->set_value(0.5); }); _poly.signal_toggled().connect([this](){ set_flat(true); }); _star.signal_toggled().connect([this](){ set_flat(false); }); - _align.signal_clicked().connect([this](){ change_value(_path, {}, [this](double) { align_star_shape(_path); }); }); @@ -755,6 +755,7 @@ public: } _rounded.set_value(_path->rounded); _rand.set_value(_path->randomized); + _clear_rnd .set_visible(_path->randomized != 0); _clear_round.set_visible(_path->rounded != 0); _clear_ratio.set_visible(std::abs(_ratio.get_value() - 0.5) > 0.0005); diff --git a/src/ui/toolbar/star-toolbar.cpp b/src/ui/toolbar/star-toolbar.cpp index 27ef1109eacc02f4332b7520899b4382a5b0731c..30595c6999a331a65a235056e82dfa48965e619e 100644 --- a/src/ui/toolbar/star-toolbar.cpp +++ b/src/ui/toolbar/star-toolbar.cpp @@ -41,6 +41,8 @@ #include "ui/icon-names.h" #include "ui/tools/star-tool.h" #include "ui/util.h" +#include "ui/widget/combo-tool-item.h" +#include "ui/widget/unit-tracker.h" #include "ui/widget/spinbutton.h" using Inkscape::DocumentUndo; @@ -59,6 +61,8 @@ StarToolbar::StarToolbar(Glib::RefPtr const &builder) , _spoke_item{get_derived_widget(builder, "_spoke_item")} , _roundedness_item{get_derived_widget(builder, "_roundedness_item")} , _randomization_item{get_derived_widget(builder, "_randomization_item")} + , _tracker{std::make_unique(Util::UNIT_TYPE_LINEAR)} + , _length_item{get_derived_widget(builder, "_length_item")} { bool is_flat_sided = Preferences::get()->getBool("/tools/shapes/star/isflatsided", false); @@ -111,6 +115,7 @@ StarToolbar::StarToolbar(Glib::RefPtr const &builder) setup_derived_spin_button(_spoke_item, "proportion", 0.5, &StarToolbar::proportion_value_changed); setup_derived_spin_button(_roundedness_item, "rounded", 0.0, &StarToolbar::rounded_value_changed); setup_derived_spin_button(_randomization_item, "randomized", 0.0, &StarToolbar::randomized_value_changed); + setup_derived_spin_button(_length_item, "length", 0.0, &StarToolbar::length_value_changed); // Flatsided checkbox _flat_item_buttons.push_back(&get_widget(builder, "flat_polygon_button")); @@ -128,6 +133,10 @@ StarToolbar::StarToolbar(Glib::RefPtr const &builder) _spoke_box.set_visible(!is_flat_sided); + auto unit_menu = _tracker->create_tool_item(_("Units"), ("")); + get_widget(builder, "unit_menu_box").append(*unit_menu); + _tracker->addAdjustment(_length_item.get_adjustment()->gobj()); + _initMenuBtns(); } @@ -166,6 +175,7 @@ void StarToolbar::setDesktop(SPDesktop *desktop) { if (_desktop) { _selection_changed_conn.disconnect(); + _selection_modified_conn.disconnect(); if (_repr) { _detachRepr(); @@ -177,10 +187,16 @@ void StarToolbar::setDesktop(SPDesktop *desktop) if (_desktop) { auto sel = _desktop->getSelection(); _selection_changed_conn = sel->connectChanged(sigc::mem_fun(*this, &StarToolbar::_selectionChanged)); + _selection_modified_conn = sel->connectChanged(sigc::mem_fun(*this, &StarToolbar::_selectionModified)); _selectionChanged(sel); // Synthesize an emission to trigger the update } } +void StarToolbar::setActiveUnit(Util::Unit const *unit) +{ + _tracker->setActiveUnit(unit); +} + void StarToolbar::side_mode_changed(int mode) { bool const flat = mode == 0; @@ -356,6 +372,25 @@ void StarToolbar::randomized_value_changed() } } +void StarToolbar::length_value_changed() +{ + if (!_blocker.pending() || _tracker->isUpdating()) { + // in turn, prevent listener from responding + auto guard = _blocker.block(); + + auto adj = _length_item.get_adjustment(); + Preferences::get()->setDouble("/tools/shapes/star/length", adj->get_value()); + + auto value = Util::Quantity::convert(adj->get_value(), _tracker->getActiveUnit(), "px"); + + for (auto item : _desktop->getSelection()->items()) { + if (auto star = cast(item)) { + star -> setSideLength(value); + } + } + } +} + void StarToolbar::_setDefaults() { _batchundo = true; @@ -393,20 +428,45 @@ void StarToolbar::_selectionChanged(Selection *selection) int n_selected = 0; XML::Node *repr = nullptr; + double lengths = 0; for (auto item : selection->items()) { - if (is(item)) { + if (auto star = cast(item)) { n_selected++; repr = item->getRepr(); + lengths += star->getSideLength(); } } _mode_item.set_markup(n_selected == 0 ? _("New:") : _("Change:")); + _length_item.set_sensitive(n_selected > 0); if (n_selected == 1) { _attachRepr(repr); _repr->synthesizeEvents(*this); // Fixme: BAD } + _selectionModified(selection); +} + +void StarToolbar::_selectionModified(Selection *selection) +{ + if (!_blocker.pending()|| _tracker->isUpdating()) { + auto guard = _blocker.block(); + auto length_adj = _length_item.get_adjustment(); + + int n_selected = 0; + double lengths = 0; + for (auto item : selection->items()) { + if (auto star = cast(item)) { + n_selected++; + lengths += star->getSideLength(); + } + } + if (n_selected > 0) { + auto value = Util::Quantity::convert(lengths / n_selected, "px", _tracker->getActiveUnit()); + length_adj->set_value(value); + } + } } void StarToolbar::notifyAttributeChanged(XML::Node &, GQuark name_, Util::ptr_shared, Util::ptr_shared) @@ -426,6 +486,7 @@ void StarToolbar::notifyAttributeChanged(XML::Node &, GQuark name_, Util::ptr_sh bool isFlatSided = Preferences::get()->getBool("/tools/shapes/star/isflatsided", false); auto mag_adj = _magnitude_item.get_adjustment(); auto spoke_adj = _spoke_item.get_adjustment(); + auto length_adj = _length_item.get_adjustment(); if (!strcmp(name, "inkscape:randomized")) { double randomized = _repr->getAttributeDouble("inkscape:randomized", 0.0); @@ -457,6 +518,20 @@ void StarToolbar::notifyAttributeChanged(XML::Node &, GQuark name_, Util::ptr_sh int sides = _repr->getAttributeInt("sodipodi:sides", 0); mag_adj->set_value(sides); } + + double lengths = 0; + int n_selected = 0; + for (auto item : _desktop->getSelection()->items()) { + if (auto star = cast(item)) { + n_selected++; + lengths += star->getSideLength(); + } + } + + if (n_selected > 0) { + auto value = Util::Quantity::convert(lengths / n_selected, "px", _tracker->getActiveUnit()); + length_adj->set_value(value); + } } } // namespace Inkscape::UI::Toolbar diff --git a/src/ui/toolbar/star-toolbar.h b/src/ui/toolbar/star-toolbar.h index d3dbd2726d67d95aefd84e321f73cbc97d7b99e5..9bad4f4659ab8c9fef3f82375c14b69f920ee0cd 100644 --- a/src/ui/toolbar/star-toolbar.h +++ b/src/ui/toolbar/star-toolbar.h @@ -45,6 +45,7 @@ namespace Tools { class ToolBase; } namespace Widget { class Label; class SpinButton; +class UnitTracker; } // namespace Widget } // namespace UI namespace XML { class Node; } @@ -61,6 +62,7 @@ public: ~StarToolbar() override; void setDesktop(SPDesktop *desktop) override; + void setActiveUnit(Util::Unit const *unit) override; private: StarToolbar(Glib::RefPtr const &builder); @@ -74,6 +76,9 @@ private: UI::Widget::SpinButton &_spoke_item; UI::Widget::SpinButton &_roundedness_item; UI::Widget::SpinButton &_randomization_item; + UI::Widget::SpinButton &_length_item; + + std::unique_ptr _tracker; XML::Node *_repr = nullptr; void _attachRepr(XML::Node *repr); @@ -82,6 +87,7 @@ private: bool _batchundo = false; OperationBlocker _blocker; sigc::connection _selection_changed_conn; + sigc::connection _selection_modified_conn; void setup_derived_spin_button(UI::Widget::SpinButton &btn, Glib::ustring const &name, double default_value, ValueChangedMemFun value_changed_mem_fun); @@ -90,8 +96,10 @@ private: void proportion_value_changed(); void rounded_value_changed(); void randomized_value_changed(); + void length_value_changed(); void _setDefaults(); void _selectionChanged(Selection *selection); + void _selectionModified(Selection *selection); void notifyAttributeChanged(XML::Node &node, GQuark name, Util::ptr_shared old_value, Util::ptr_shared new_value) override; };