index bd4f82096765f7dd83e23c4b5750be1e46f2dfb8..bb8e690923e66402bdabd7f5e71914e3267ff809 100644 (file)
#include "desktop.h"
#include "desktop-events.h"
#include "desktop-handles.h"
-#include "desktop-affine.h"
#include "desktop-style.h"
#include "message-context.h"
#include "preferences.h"
static gint sp_dyna_draw_context_root_handler(SPEventContext *ec, GdkEvent *event);
static void clear_current(SPDynaDrawContext *dc);
-static void set_to_accumulated(SPDynaDrawContext *dc, bool unionize);
+static void set_to_accumulated(SPDynaDrawContext *dc, bool unionize, bool subtract);
static void add_cap(SPCurve *curve, Geom::Point const &from, Geom::Point const &to, double rounding);
static bool accumulate_calligraphic(SPDynaDrawContext *dc);
ddc->hatch_spacing_step = 0;
new (&ddc->hatch_pointer_past) std::list<double>();
new (&ddc->hatch_nearest_past) std::list<double>();
+ new (&ddc->inertia_vectors) std::list<Geom::Point>();
+ new (&ddc->hatch_vectors) std::list<Geom::Point>();
ddc->hatch_last_nearest = Geom::Point(0,0);
ddc->hatch_last_pointer = Geom::Point(0,0);
- ddc->hatch_vector_accumulated = Geom::Point(0,0);
ddc->hatch_escaped = false;
ddc->hatch_area = NULL;
ddc->hatch_item = NULL;
ddc->hatch_livarot_path = NULL;
ddc->trace_bg = false;
+ ddc->just_started_drawing = false;
}
static void
ddc->hatch_pointer_past.~list();
ddc->hatch_nearest_past.~list();
+ ddc->inertia_vectors.~list();
+ ddc->hatch_vectors.~list();
}
static void
// If force is below the absolute threshold DYNA_EPSILON,
// or we haven't yet reached DYNA_VEL_START (i.e. at the beginning of stroke)
// _and_ the force is below the (higher) DYNA_EPSILON_START threshold,
- // discard this move.
+ // discard this move.
// This prevents flips, blobs, and jerks caused by microscopic tremor of the tablet pen,
// especially bothersome at the start of the stroke where we don't yet have the inertia to
// smooth them out.
// get the real brush point, not the same as pointer (affected by hatch tracking and/or mass
// drag)
Geom::Point brush = sp_dyna_draw_get_vpoint(dc, dc->cur);
- Geom::Point brush_w = SP_EVENT_CONTEXT(dc)->desktop->d2w(brush);
+ Geom::Point brush_w = SP_EVENT_CONTEXT(dc)->desktop->d2w(brush);
double trace_thick = 1;
if (dc->trace_bg) {
return TRUE;
}
- Geom::Point const button_w(event->button.x,
- event->button.y);
- Geom::Point const button_dt(desktop->w2d(button_w));
- sp_dyna_draw_reset(dc, button_dt);
- sp_dyna_draw_extinput(dc, event);
- sp_dyna_draw_apply(dc, button_dt);
dc->accumulated->reset();
if (dc->repr) {
dc->repr = NULL;
sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3);
dc->is_drawing = true;
+ dc->just_started_drawing = true;
}
break;
case GDK_MOTION_NOTIFY:
} else {
dc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Select a guide path</b> to track with <b>Ctrl</b>"));
}
- }
+ }
if ( dc->is_drawing && (event->motion.state & GDK_BUTTON1_MASK) && !event_context->space_panning) {
dc->dragging = TRUE;
if (event->motion.state & GDK_CONTROL_MASK && dc->hatch_item) { // hatching
+#define HATCH_VECTOR_ELEMENTS 12
+#define INERTIA_ELEMENTS 24
#define SPEED_ELEMENTS 12
-#define SPEED_MIN 0.12
-#define SPEED_NORMAL 0.65
+#define SPEED_MIN 0.3
+#define SPEED_NORMAL 0.35
+#define INERTIA_FORCE 0.5
// speed is the movement of the nearest point along the guide path, divided by
// the movement of the pointer at the same period; it is averaged for the last
// attracted again until the end of this stroke
dc->hatch_escaped = true;
+ if (dc->inertia_vectors.size() >= INERTIA_ELEMENTS/2) { // move by inertia
+ Geom::Point moved_past_escape = motion_dt - dc->inertia_vectors.front();
+ Geom::Point inertia =
+ dc->inertia_vectors.front() - dc->inertia_vectors.back();
+
+ double dot = Geom::dot (moved_past_escape, inertia);
+ dot /= Geom::L2(moved_past_escape) * Geom::L2(inertia);
+
+ if (dot > 0) { // mouse is still moving in approx the same direction
+ Geom::Point should_have_moved =
+ (inertia) * (1/Geom::L2(inertia)) * Geom::L2(moved_past_escape);
+ motion_dt = dc->inertia_vectors.front() +
+ (INERTIA_FORCE * should_have_moved + (1 - INERTIA_FORCE) * moved_past_escape);
+ }
+ }
+
} else {
// Calculate angle cosine of this vector-to-guide and all past vectors
// summed, to detect if we accidentally flipped to the other side of the
// guide
- double dot = Geom::dot (pointer - nearest, dc->hatch_vector_accumulated);
- dot /= Geom::L2(pointer - nearest) * Geom::L2(dc->hatch_vector_accumulated);
+ Geom::Point hatch_vector_accumulated = std::accumulate
+ (dc->hatch_vectors.begin(), dc->hatch_vectors.end(), Geom::Point(0,0));
+ double dot = Geom::dot (pointer - nearest, hatch_vector_accumulated);
+ dot /= Geom::L2(pointer - nearest) * Geom::L2(hatch_vector_accumulated);
if (dc->hatch_spacing != 0) { // spacing was already set
double target;
} else {
// looks like we're starting to lose speed,
// so _gradually_ let go attraction to prevent jerks
- target = (dc->hatch_spacing * speed + hatch_dist * (SPEED_NORMAL - speed))/SPEED_NORMAL;
+ target = (dc->hatch_spacing * speed + hatch_dist * (SPEED_NORMAL - speed))/SPEED_NORMAL;
}
if (!IS_NAN(dot) && dot < -0.5) {// flip
target = -target;
// return it to the desktop coords
motion_dt = new_pointer * motion_to_curve.inverse();
+ if (speed >= SPEED_NORMAL) {
+ dc->inertia_vectors.push_front(motion_dt);
+ if (dc->inertia_vectors.size() > INERTIA_ELEMENTS)
+ dc->inertia_vectors.pop_back();
+ }
+
} else {
- // this is the first motion event, set the dist
+ // this is the first motion event, set the dist
dc->hatch_spacing = hatch_dist;
}
// remember last points
dc->hatch_last_pointer = pointer;
dc->hatch_last_nearest = nearest;
- dc->hatch_vector_accumulated += (pointer - nearest);
+
+ dc->hatch_vectors.push_front(pointer - nearest);
+ if (dc->hatch_vectors.size() > HATCH_VECTOR_ELEMENTS)
+ dc->hatch_vectors.pop_back();
}
dc->_message_context->set(Inkscape::NORMAL_MESSAGE, dc->hatch_escaped? _("Tracking: <b>connection to guide path lost!</b>") : _("<b>Tracking</b> a guide path"));
dc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Drawing</b> a calligraphic stroke"));
}
+ if (dc->just_started_drawing) {
+ dc->just_started_drawing = false;
+ sp_dyna_draw_reset(dc, motion_dt);
+ }
+
if (!sp_dyna_draw_apply(dc, motion_dt)) {
ret = TRUE;
break;
}
// Draw the hatching circle if necessary
- if (event->motion.state & GDK_CONTROL_MASK) {
- if (dc->hatch_spacing == 0 && hatch_dist != 0) {
+ if (event->motion.state & GDK_CONTROL_MASK) {
+ if (dc->hatch_spacing == 0 && hatch_dist != 0) {
// Haven't set spacing yet: gray, center free, update radius live
Geom::Point c = desktop->w2d(motion_w);
Geom::Matrix const sm (Geom::Scale(hatch_dist, hatch_dist) * Geom::Translate(c));
sp_canvas_item_show(dc->hatch_area);
} else if (dc->dragging && dc->hatch_escaped) {
// Tracking escaped: red, center free, fixed radius
- Geom::Point c = desktop->w2d(motion_w);
+ Geom::Point c = motion_dt;
Geom::Matrix const sm (Geom::Scale(dc->hatch_spacing, dc->hatch_spacing) * Geom::Translate(c));
sp_canvas_item_affine_absolute(dc->hatch_area, sm);
/* Create object */
fit_and_split(dc, TRUE);
if (accumulate_calligraphic(dc))
- set_to_accumulated(dc, event->button.state & GDK_SHIFT_MASK); // performs document_done
+ set_to_accumulated(dc, event->button.state & GDK_SHIFT_MASK, event->button.state & GDK_MOD1_MASK); // performs document_done
else
g_warning ("Failed to create path: invalid data in dc->cal1 or dc->cal2");
if (!dc->hatch_pointer_past.empty()) dc->hatch_pointer_past.clear();
if (!dc->hatch_nearest_past.empty()) dc->hatch_nearest_past.clear();
+ if (!dc->inertia_vectors.empty()) dc->inertia_vectors.clear();
+ if (!dc->hatch_vectors.empty()) dc->hatch_vectors.clear();
dc->hatch_last_nearest = Geom::Point(0,0);
dc->hatch_last_pointer = Geom::Point(0,0);
- dc->hatch_vector_accumulated = Geom::Point(0,0);
dc->hatch_escaped = false;
dc->hatch_item = NULL;
dc->hatch_livarot_path = NULL;
+ dc->just_started_drawing = false;
- if (dc->hatch_spacing != 0 && !dc->keep_selected) {
+ if (dc->hatch_spacing != 0 && !dc->keep_selected) {
// we do not select the newly drawn path, so increase spacing by step
if (dc->hatch_spacing_step == 0) {
dc->hatch_spacing_step = dc->hatch_spacing;
}
static void
-set_to_accumulated(SPDynaDrawContext *dc, bool unionize)
+set_to_accumulated(SPDynaDrawContext *dc, bool unionize, bool subtract)
{
SPDesktop *desktop = SP_EVENT_CONTEXT(dc)->desktop;
item->transform = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse();
item->updateRepr();
}
- Geom::PathVector pathv = dc->accumulated->get_pathvector() * sp_desktop_dt2doc_affine(desktop);
+ Geom::PathVector pathv = dc->accumulated->get_pathvector() * desktop->dt2doc();
gchar *str = sp_svg_write_path(pathv);
g_assert( str != NULL );
dc->repr->setAttribute("d", str);
if (unionize) {
sp_desktop_selection(desktop)->add(dc->repr);
sp_selected_path_union_skip_undo(desktop);
+ } else if (subtract) {
+ sp_desktop_selection(desktop)->add(dc->repr);
+ sp_selected_path_diff_skip_undo(desktop);
} else {
if (dc->keep_selected) {
sp_desktop_selection(desktop)->set(dc->repr);
- }
+ }
}
} else {
dc->repr = NULL;
}
- sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_CALLIGRAPHIC,
+ sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_CALLIGRAPHIC,
_("Draw calligraphic stroke"));
}
static void
add_cap(SPCurve *curve,
Geom::Point const &from,
- Geom::Point const &to,
+ Geom::Point const &to,
double rounding)
{
if (Geom::L2( to - from ) > DYNA_EPSILON) {
dc->cal1->is_empty() ||
dc->cal2->is_empty() ||
(dc->cal1->get_segment_count() <= 0) ||
- dc->cal1->first_path()->closed()
+ dc->cal1->first_path()->closed()
) {
dc->cal1->reset();
dc->cal2->reset();
SPCurve *rev_cal2 = dc->cal2->create_reverse();
if (
(rev_cal2->get_segment_count() <= 0) ||
- rev_cal2->first_path()->closed()
+ rev_cal2->first_path()->closed()
) {
rev_cal2->unref();
dc->cal1->reset();
!dc_cal1_firstseg ||
!rev_cal2_firstseg ||
!dc_cal1_lastseg ||
- !rev_cal2_lastseg
+ !rev_cal2_lastseg
) {
rev_cal2->unref();
dc->cal1->reset();