summaryrefslogtreecommitdiffstats
path: root/src/live_effects/parameter/powerstrokepointarray.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/live_effects/parameter/powerstrokepointarray.cpp')
-rw-r--r--src/live_effects/parameter/powerstrokepointarray.cpp308
1 files changed, 308 insertions, 0 deletions
diff --git a/src/live_effects/parameter/powerstrokepointarray.cpp b/src/live_effects/parameter/powerstrokepointarray.cpp
new file mode 100644
index 0000000..e33166f
--- /dev/null
+++ b/src/live_effects/parameter/powerstrokepointarray.cpp
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/dialog/lpe-powerstroke-properties.h"
+#include "live_effects/parameter/powerstrokepointarray.h"
+
+#include "knotholder.h"
+#include "live_effects/effect.h"
+#include "live_effects/lpe-powerstroke.h"
+
+
+#include <2geom/piecewise.h>
+#include <2geom/sbasis-geometric.h>
+
+#include "preferences.h" // for proportional stroke/path scaling behavior
+
+#include <glibmm/i18n.h>
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+PowerStrokePointArrayParam::PowerStrokePointArrayParam( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr,
+ Effect* effect)
+ : ArrayParam<Geom::Point>(label, tip, key, wr, effect, 0)
+{
+ knot_shape = SP_KNOT_SHAPE_DIAMOND;
+ knot_mode = SP_KNOT_MODE_XOR;
+ knot_color = 0xff88ff00;
+}
+
+PowerStrokePointArrayParam::~PowerStrokePointArrayParam()
+= default;
+
+Gtk::Widget *
+PowerStrokePointArrayParam::param_newWidget()
+{
+ return nullptr;
+}
+
+void PowerStrokePointArrayParam::param_transform_multiply(Geom::Affine const &postmul, bool /*set*/)
+{
+ // Check if proportional stroke-width scaling is on
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool transform_stroke = prefs ? prefs->getBool("/options/transform/stroke", true) : true;
+ if (transform_stroke) {
+ std::vector<Geom::Point> result;
+ result.reserve(_vector.size()); // reserve space for the points that will be added in the for loop
+ for (auto point_it : _vector)
+ {
+ // scale each width knot with the average scaling in X and Y
+ Geom::Coord const A = point_it[Geom::Y] * postmul.descrim();
+ result.emplace_back(point_it[Geom::X], A);
+ }
+ param_set_and_write_new_value(result);
+ }
+}
+
+/** call this method to recalculate the controlpoints such that they stay at the same location relative to the new path. Useful after adding/deleting nodes to the path.*/
+void
+PowerStrokePointArrayParam::recalculate_controlpoints_for_new_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
+{
+ Inkscape::LivePathEffect::LPEPowerStroke *lpe = dynamic_cast<Inkscape::LivePathEffect::LPEPowerStroke *>(param_effect);
+ if (lpe) {
+ if (last_pwd2.size() > pwd2_in.size()) {
+ double factor = (double)pwd2_in.size() / (double)last_pwd2.size();
+ for (auto & i : _vector) {
+ i[Geom::X] *= factor;
+ }
+ } else if (last_pwd2.size() < pwd2_in.size()) {
+ // Path has become longer: probably node added, maintain position of knots
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > normal = rot90(unitVector(derivative(pwd2_in)));
+ for (auto & i : _vector) {
+ Geom::Point pt = i;
+ Geom::Point position = last_pwd2.valueAt(pt[Geom::X]) + pt[Geom::Y] * last_pwd2_normal.valueAt(pt[Geom::X]);
+ double t = nearest_time(position, pwd2_in);
+ i[Geom::X] = t;
+ }
+ }
+ write_to_SVG();
+ }
+}
+
+/** call this method to recalculate the controlpoints when path is reversed.*/
+std::vector<Geom::Point>
+PowerStrokePointArrayParam::reverse_controlpoints(bool write)
+{
+ std::vector<Geom::Point> controlpoints;
+ if (!last_pwd2.empty()) {
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in_reverse = reverse(last_pwd2);
+ for (auto & i : _vector) {
+ Geom::Point control_pos = last_pwd2.valueAt(i[Geom::X]);
+ double new_pos = Geom::nearest_time(control_pos, pwd2_in_reverse);
+ controlpoints.emplace_back(new_pos,i[Geom::Y]);
+ i[Geom::X] = new_pos;
+ }
+ if (write) {
+ write_to_SVG();
+ _vector.clear();
+ _vector = controlpoints;
+ controlpoints.clear();
+ write_to_SVG();
+ return _vector;
+ }
+ }
+ return controlpoints;
+}
+
+float PowerStrokePointArrayParam::median_width()
+{
+ size_t size = _vector.size();
+ if (size > 0)
+ {
+ if (size % 2 == 0)
+ {
+ return (_vector[size / 2 - 1].y() + _vector[size / 2].y()) / 2;
+ }
+ else
+ {
+ return _vector[size / 2].y();
+ }
+ }
+ return 1;
+}
+
+void
+PowerStrokePointArrayParam::set_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in, Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_normal_in)
+{
+ last_pwd2 = pwd2_in;
+ last_pwd2_normal = pwd2_normal_in;
+}
+
+
+void
+PowerStrokePointArrayParam::set_oncanvas_looks(SPKnotShapeType shape, SPKnotModeType mode, guint32 color)
+{
+ knot_shape = shape;
+ knot_mode = mode;
+ knot_color = color;
+}
+/*
+class PowerStrokePointArrayParamKnotHolderEntity : public KnotHolderEntity {
+public:
+ PowerStrokePointArrayParamKnotHolderEntity(PowerStrokePointArrayParam *p, unsigned int index);
+ virtual ~PowerStrokePointArrayParamKnotHolderEntity() {}
+
+ virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
+ virtual Geom::Point knot_get() const;
+ virtual void knot_click(guint state);
+
+ // Checks whether the index falls within the size of the parameter's vector
+ bool valid_index(unsigned int index) const {
+ return (_pparam->_vector.size() > index);
+ };
+
+private:
+ PowerStrokePointArrayParam *_pparam;
+ unsigned int _index;
+};*/
+
+PowerStrokePointArrayParamKnotHolderEntity::PowerStrokePointArrayParamKnotHolderEntity(PowerStrokePointArrayParam *p, unsigned int index)
+ : _pparam(p),
+ _index(index)
+{
+}
+
+void
+PowerStrokePointArrayParamKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
+{
+ using namespace Geom;
+
+ if (!valid_index(_index)) {
+ return;
+ }
+ /// @todo how about item transforms???
+ Piecewise<D2<SBasis> > const & pwd2 = _pparam->get_pwd2();
+ Piecewise<D2<SBasis> > const & n = _pparam->get_pwd2_normal();
+
+ Geom::Point const s = snap_knot_position(p, state);
+ double t = nearest_time(s, pwd2);
+ double offset = dot(s - pwd2.valueAt(t), n.valueAt(t));
+ _pparam->_vector.at(_index) = Geom::Point(t, offset/_pparam->_scale_width);
+ if (_pparam->_vector.size() == 1 ) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/live_effects/powerstroke/width", offset);
+ }
+ sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false);
+}
+
+Geom::Point
+PowerStrokePointArrayParamKnotHolderEntity::knot_get() const
+{
+ using namespace Geom;
+
+ if (!valid_index(_index)) {
+ return Geom::Point(infinity(), infinity());
+ }
+
+ Piecewise<D2<SBasis> > const & pwd2 = _pparam->get_pwd2();
+ Piecewise<D2<SBasis> > const & n = _pparam->get_pwd2_normal();
+
+ Point offset_point = _pparam->_vector.at(_index);
+ if (offset_point[X] > pwd2.size() || offset_point[X] < 0) {
+ g_warning("Broken powerstroke point at %f, I won't try to add that", offset_point[X]);
+ return Geom::Point(infinity(), infinity());
+ }
+ Point canvas_point = pwd2.valueAt(offset_point[X]) + (offset_point[Y] * _pparam->_scale_width) * n.valueAt(offset_point[X]);
+ return canvas_point;
+}
+
+void
+PowerStrokePointArrayParamKnotHolderEntity::knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state)
+{
+ _pparam->param_effect->refresh_widgets = true;
+ _pparam->write_to_SVG();
+}
+
+void PowerStrokePointArrayParamKnotHolderEntity::knot_set_offset(Geom::Point offset)
+{
+ _pparam->_vector.at(_index) = Geom::Point(offset.x(), offset.y() / 2);
+ this->parent_holder->knot_ungrabbed_handler(this->knot, 0);
+}
+
+void
+PowerStrokePointArrayParamKnotHolderEntity::knot_click(guint state)
+{
+ if (state & GDK_CONTROL_MASK) {
+ if (state & GDK_MOD1_MASK) {
+ // delete the clicked knot
+ std::vector<Geom::Point> & vec = _pparam->_vector;
+ if (vec.size() > 1) { //Force don't remove last knot
+ vec.erase(vec.begin() + _index);
+ _pparam->param_set_and_write_new_value(vec);
+ // shift knots down one index
+ for(auto & ent : parent_holder->entity) {
+ PowerStrokePointArrayParamKnotHolderEntity *pspa_ent = dynamic_cast<PowerStrokePointArrayParamKnotHolderEntity *>(ent);
+ if ( pspa_ent && pspa_ent->_pparam == this->_pparam ) { // check if the knotentity belongs to this powerstrokepointarray parameter
+ if (pspa_ent->_index > this->_index) {
+ --pspa_ent->_index;
+ }
+ }
+ };
+ // temporary hide, when knotholder were recreated it finally drop
+ this->knot->hide();
+ }
+ return;
+ } else {
+ // add a knot to XML
+ std::vector<Geom::Point> & vec = _pparam->_vector;
+ vec.insert(vec.begin() + _index, 1, vec.at(_index)); // this clicked knot is duplicated
+ _pparam->param_set_and_write_new_value(vec);
+
+ // shift knots up one index
+ for(auto & ent : parent_holder->entity) {
+ PowerStrokePointArrayParamKnotHolderEntity *pspa_ent = dynamic_cast<PowerStrokePointArrayParamKnotHolderEntity *>(ent);
+ if ( pspa_ent && pspa_ent->_pparam == this->_pparam ) { // check if the knotentity belongs to this powerstrokepointarray parameter
+ if (pspa_ent->_index > this->_index) {
+ ++pspa_ent->_index;
+ }
+ }
+ };
+ // add knot to knotholder
+ PowerStrokePointArrayParamKnotHolderEntity *e = new PowerStrokePointArrayParamKnotHolderEntity(_pparam, _index+1);
+ e->create(this->desktop, this->item, parent_holder, Inkscape::CTRL_TYPE_LPE,
+ _("<b>Stroke width control point</b>: drag to alter the stroke width. <b>Ctrl+click</b> adds a "
+ "control point, <b>Ctrl+Alt+click</b> deletes it, <b>Shift+click</b> launches width dialog."),
+ _pparam->knot_shape, _pparam->knot_mode, _pparam->knot_color);
+ parent_holder->add(e);
+ }
+ }
+ else if ((state & GDK_MOD1_MASK) || (state & GDK_SHIFT_MASK))
+ {
+ Geom::Point offset = Geom::Point(_pparam->_vector.at(_index).x(), _pparam->_vector.at(_index).y() * 2);
+ Inkscape::UI::Dialogs::PowerstrokePropertiesDialog::showDialog(this->desktop, offset, this);
+ }
+}
+
+void PowerStrokePointArrayParam::addKnotHolderEntities(KnotHolder *knotholder, SPItem *item)
+{
+ for (unsigned int i = 0; i < _vector.size(); ++i) {
+ PowerStrokePointArrayParamKnotHolderEntity *e = new PowerStrokePointArrayParamKnotHolderEntity(this, i);
+ e->create(nullptr, item, knotholder, Inkscape::CTRL_TYPE_LPE,
+ _("<b>Stroke width control point</b>: drag to alter the stroke width. <b>Ctrl+click</b> adds a "
+ "control point, <b>Ctrl+Alt+click</b> deletes it, <b>Shift+click</b> launches width dialog."),
+ knot_shape, knot_mode, knot_color);
+ knotholder->add(e);
+ }
+}
+
+} /* namespace LivePathEffect */
+
+} /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :