revert jeff's last commit since it breaks the build
[moon.git] / src / stylus.cpp
blob3001a21e4ce3ff64547fac37c194b0a358fe483a
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * stylus.cpp
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
14 #include <config.h>
15 #include <stdlib.h>
16 #include <math.h>
18 #include "stylus.h"
19 #include "collection.h"
20 #include "color.h"
21 #include "moon-path.h"
22 #include "error.h"
24 #define DEBUG_HITTEST 0
28 // StylusPointCollection
31 bool
32 StylusPointCollection::CanAdd (Value *value)
34 // We skip DependencyObjectCollection::CanAdd because that one
35 // mandates 1 parent per DO, which StylusPoints violate.
36 return Collection::CanAdd (value) && !Contains (value);
39 double
40 StylusPointCollection::AddStylusPoints (StylusPointCollection *points)
42 if (!points)
43 return 1.0; // documented as such, needs testing
45 for (int i = 0; i < points->GetCount (); i++)
46 Add (points->GetValueAt (i)->AsDependencyObject ());
48 return array->len - 1;
51 Rect
52 StylusPointCollection::GetBounds ()
54 if (array->len == 0)
55 return Rect (0, 0, 0, 0);
57 StylusPoint *point = GetValueAt (0)->AsStylusPoint ();
58 Rect r = Rect (point->GetX (), point->GetY (), 0, 0);
60 for (guint i = 1; i < array->len; i++) {
61 point = GetValueAt (i)->AsStylusPoint ();
62 r = r.ExtendTo (point->GetX (), point->GetY ());
65 return r;
70 // Stroke
73 Stroke::Stroke ()
75 SetObjectType (Type::STROKE);
76 old_bounds = Rect ();
77 bounds = Rect ();
78 dirty = Rect ();
81 bool
82 Stroke::HitTestEndcapSegment (Point c,
83 double w, double h,
84 Point p1, Point p2)
86 Point op1 = p1;
87 Point op2 = p2;
89 #if DEBUG_HITTEST
90 fprintf (stderr, "HitTestEndcapSegment: (%g,%g / %g, %g) hits segment (%g,%g - %g,%g)?\n",
91 c.x, c.y,
92 w, h,
93 p1.x, p1.y,
94 p2.x, p2.y);
95 #endif
97 // handle dx == 0
98 if (p2.x == p1.x) {
99 #if DEBUG_HITTEST
100 fprintf (stderr, "dx == 0, returning %d\n", p1.x >= (c.x - w/2) && p1.x <= (c.x + w/2));
101 #endif
102 if (p1.x >= (c.x - w/2) && p1.x <= (c.x + w/2)) {
103 if (p1.y < (c.y - h/2) && p2.y < (c.y - h/2))
104 return false;
105 if (p1.y > (c.y + h/2) && p2.y > (c.y + h/2))
106 return false;
107 return true;
109 return false;
112 p1 = p1 - c;
113 p2 = p2 - c;
115 // this body of code basically uses the following form of the line:
117 // y = mx + b_
119 // and the equation for an ellipse:
121 // x^2 y^2
122 // --- --- - 1 = 0
123 // a^2 b^2
125 // where a > b (as leave off the center point of the ellipse
126 // because we've subtracted it out above).
128 // we substitute the line equation in for y in the ellipse
129 // equation, and use the quadratic formula to find our roots
130 // (if there are any).
132 double m = (p2.y - p1.y)/(p2.x - p1.x);
133 double b_ = p1.y - m * p1.x;
135 double a, b;
136 if (w > h) {
137 a = w / 2;
138 b = h / 2;
140 else {
141 a = h / 2;
142 b = w / 2;
145 if (b == 0 || a == 0) {
146 return false;
149 double aq = (m*m) / (b*b) + 1 / (a*a);
150 double bq = (2 * m * b_) / (b * b);
151 double cq = (b_ * b_) / (b * b) - 1;
153 double discr = bq * bq - 4 * aq * cq;
155 #if DEBUG_HITTEST
156 fprintf (stderr, "HitTestEndCapSegment: discr = %g\n", discr);
157 #endif
159 // if we have roots we need to check if they occur on the line
160 // segment (using the parametric form of the line).
161 if (discr < 0)
162 return false;
163 else {
164 double sqrt_discr = discr > 0 ? sqrt(discr) : 0;
166 double root_1 = ((- bq) - sqrt_discr) / (2 * aq);
167 if (root_1 > p1.x && (root_1 - p1.x) < (p2.x - p1.x))
168 return true;
170 double root_2 = (- bq + sqrt_discr) / (2 * aq);
171 return (root_2 > p1.x && (root_2 - p1.x) < (p2.x - p1.x));
175 static bool
176 intersect_line_2d (Point p1, Point p2, Point p3, Point p4)
178 // taken from http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
179 // line segments are p1 - p2 and p3 - p4
181 double denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
183 if (denom == 0) // they're parallel
184 return false;
186 double ua = (p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x);
187 ua /= denom;
189 double ub = (p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x);
190 ub /= denom;
191 if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
192 return true;
194 return false;
197 // given the line segment between @p1 and @p2, with an ellipse with
198 // width = @w and height = @h centered at point @p, return the left
199 // (lesser x coordinate if the x's are different, and lesser y
200 // coordinate if they're the same) and right (greater x coordinate, or
201 // greater y coordinate) intersection points of the line through the
202 // same point @p but perpendicular to line @p2 - @p1.
203 static void
204 calc_perpendicular_intersection_points (Point p1, Point p2,
205 Point p,
206 double w, double h,
207 Point *left_point, Point *right_point)
209 if (p2.y == p1.y) { // horizontal line
210 *left_point = Point (p.x, p.y - h / 2);
211 *right_point = Point (p.x, p.y + h / 2);
213 else if (p2.x == p1.x) { // vertical line
214 *left_point = Point (p.x - w / 2, p.y);
215 *right_point = Point (p.x + w / 2, p.y);
217 else {
218 // slope of the perpendicular line
219 double m = -(p2.x - p1.x)/p2.y - p1.y;
221 double a, b;
222 if (w > h) {
223 a = w / 2;
224 b = h / 2;
226 else {
227 a = h / 2;
228 b = w / 2;
231 double aq = (m*m) / (b*b) + 1 / (a*a);
233 double discr = 4 * aq;
235 if (discr <= 0) {
236 g_warning ("should never happen, there should always be two roots");
237 *left_point = p;
238 *right_point = p;
240 else {
241 double sqrt_discr = sqrt (discr);
243 double root = (sqrt_discr) / (2 * aq);
245 *left_point = Point (-root + p.x, (-root * m) + p.y);
246 *right_point = Point (root + p.x, (root * m) + p.y);
252 bool
253 Stroke::HitTestSegmentSegment (Point stroke_p1, Point stroke_p2,
254 double w, double h,
255 Point p1, Point p2)
257 #if DEBUG_HITTEST
258 fprintf (stderr, "HitTestSegmentSegment: (%g,%g - %g,%g / %g, %g) hits segment (%g,%g - %g,%g) ?\n",
259 stroke_p1.x, stroke_p1.y,
260 stroke_p2.x, stroke_p2.y,
261 w, h,
262 p1.x, p1.y,
263 p2.x, p2.y);
264 #endif
265 Point left_stroke_p1, right_stroke_p1;
266 Point left_stroke_p2, right_stroke_p2;
268 calc_perpendicular_intersection_points (stroke_p1, stroke_p2, stroke_p1, w, h, &left_stroke_p1, &right_stroke_p1);
269 calc_perpendicular_intersection_points (stroke_p1, stroke_p2, stroke_p2, w, h, &left_stroke_p2, &right_stroke_p2);
271 if (intersect_line_2d (left_stroke_p1, left_stroke_p2, p1, p2))
272 return true;
273 if (intersect_line_2d (right_stroke_p1, right_stroke_p2, p1, p2))
274 return true;
276 return false;
279 bool
280 Stroke::HitTestEndcapPoint (Point c,
281 double w, double h,
282 Point p)
284 #if DEBUG_HITTEST
285 fprintf (stderr, "HitTestEndcapPoint: (%g,%g / %g, %g) hits point %g,%g ?\n",
286 c.x, c.y,
287 w, h,
288 p.x, p.y);
289 #endif
291 Point dp = p - c;
292 double a, b;
294 a = w / 2;
295 b = h / 2;
297 bool rv = ((dp.x * dp.x) / (a * a) + (dp.y * dp.y) / (b * b)) < 1;
298 #if DEBUG_HITTEST
299 fprintf (stderr, " + %s\n", rv ? "TRUE" : "FALSE");
300 #endif
301 return rv;
304 static bool
305 point_gte_line (Point p,
306 Point p1, Point p2)
308 // return true if the point is to the right of or beneath the line segment
310 if (p1.y == p2.y) {
311 return p.y > p1.y;
313 else if (p1.x == p2.x)
314 return p.x > p1.x;
315 else {
316 double m = (p2.y - p1.y) / (p2.x - p1.x);
318 if (m > 0)
319 return p.y < (p1.y + m * p.x);
320 else
321 return p.y > (p1.y + m * p.x);
325 static bool
326 point_lte_line (Point p,
327 Point p1, Point p2)
329 // return true if the point is to the right of or beneath the line segment
331 if (p1.y == p2.y)
332 return p.y < p1.y;
333 else if (p1.x == p2.x)
334 return p.x < p1.x;
335 else {
336 double m = (p2.y - p1.y) / (p2.x - p1.x);
338 if (m > 0)
339 return p.y > (p1.y + m * p.x);
340 else
341 return p.y < (p1.y + m * p.x);
345 bool
346 Stroke::HitTestSegmentPoint (Point stroke_p1, Point stroke_p2,
347 double w, double h,
348 Point p)
350 #if DEBUG_HITTEST
351 fprintf (stderr, "HitTestSegment: (%g,%g - %g,%g / %g, %g) hits point (%g,%g) ?\n",
352 stroke_p1.x, stroke_p1.y,
353 stroke_p2.x, stroke_p2.y,
354 w, h,
355 p.x, p.y);
356 #endif
358 Point left_stroke_p1, right_stroke_p1;
359 Point left_stroke_p2, right_stroke_p2;
361 calc_perpendicular_intersection_points (stroke_p1, stroke_p2, stroke_p1, w, h, &left_stroke_p1, &right_stroke_p1);
362 calc_perpendicular_intersection_points (stroke_p1, stroke_p2, stroke_p2, w, h, &left_stroke_p2, &right_stroke_p2);
364 return point_gte_line (p, left_stroke_p1, left_stroke_p2) && point_lte_line (p, right_stroke_p1, right_stroke_p2);
368 bool
369 Stroke::HitTestSegment (Point p1, Point p2, double w, double h, StylusPointCollection *stylusPoints)
371 StylusPoint *sp;
373 if (HitTestEndcap (p1, w, h, stylusPoints))
374 return true;
376 if (HitTestEndcap (p2, w, h, stylusPoints))
377 return true;
379 for (int i = 0; i < stylusPoints->GetCount (); i++) {
380 sp = stylusPoints->GetValueAt (i)->AsStylusPoint ();
382 if (i + 1 == stylusPoints->GetCount ()) {
383 Point p (sp->GetX (), sp->GetY ());
385 if (!bounds.PointInside (p))
386 continue;
388 if (HitTestSegmentPoint (p1, p2,
389 w, h,
391 return true;
393 else {
394 StylusPoint *next_sp = stylusPoints->GetValueAt (i + 1)->AsStylusPoint ();
395 i++;
397 Point p (sp->GetX (), sp->GetY ());
398 Point next_p (next_sp->GetX (), next_sp->GetY ());
400 if (HitTestSegmentSegment (p1, p2,
401 w, h,
402 p, next_p))
403 return true;
407 return false;
410 bool
411 Stroke::HitTestEndcap (Point p, double w, double h, StylusPointCollection *stylusPoints)
413 StylusPoint *sp = stylusPoints->GetValueAt (0)->AsStylusPoint ();
414 Point cur, next;
416 cur.x = sp->GetX ();
417 cur.y = sp->GetY ();
419 if (stylusPoints->GetCount () < 2) {
420 // singleton input point to match against
421 if (bounds.PointInside (cur)) {
422 if (HitTestEndcapPoint (p, w, h, cur))
423 return true;
425 #if DEBUG_HITTEST
426 fprintf (stderr, "\t(%f, %f) EndcapPoint failed\n",
427 cur.x, cur.y);
428 #endif
429 } else {
430 #if DEBUG_HITTEST
431 fprintf (stderr, "\t(%f, %f) is not within bounds\n",
432 cur.x, cur.y);
433 #endif
437 for (int i = 1; i < stylusPoints->GetCount (); i++) {
438 sp = stylusPoints->GetValueAt (i)->AsStylusPoint ();
439 next.x = sp->GetX ();
440 next.y = sp->GetY ();
442 if (HitTestEndcapSegment (p, w, h, cur, next))
443 return true;
445 #if DEBUG_HITTEST
446 fprintf (stderr, "\t(%f, %f) (%f, %f) EndcapSegment failed\n",
447 cur.x, cur.y, next.x, next.y);
448 #endif
450 cur.x = next.x;
451 cur.y = next.y;
454 return false;
457 bool
458 Stroke::HitTest (StylusPointCollection *stylusPoints)
460 StylusPointCollection *myStylusPoints = GetStylusPoints ();
462 if (myStylusPoints->GetCount () == 0) {
463 #if DEBUG_HITTEST
464 fprintf (stderr, "no points in the collection, returning false!\n");
465 #endif
466 return false;
469 DrawingAttributes *da = GetDrawingAttributes ();
470 StylusPoint *sp;
472 double height, width;
474 if (da) {
475 height = da->GetHeight ();
476 width = da->GetWidth ();
478 Color *col = da->GetOutlineColor ();
479 if (col->a != 0x00) {
480 height += 4.0;
481 width += 4.0;
484 else {
485 height = width = 6.0;
489 #if DEBUG_HITTEST
490 fprintf (stderr, "Stroke::HitTest()\n");
491 fprintf (stderr, "\tInput points:\n");
493 for (int i = 0; i < stylusPoints->GetCount (); i++) {
494 sp = stylusPoints->GetValueAt (i)->AsStylusPoint ();
496 fprintf (stderr, "\t\tPoint: (%f, %f)\n", sp->GetX (), sp->GetY ());
499 fprintf (stderr, "\tStroke points:\n");
501 for (int i = 0; i < myStylusPoints->GetCount (); i++) {
502 sp = myStylusPoints->GetValueAt (i)->AsStylusPoint ();
504 fprintf (stderr, "\t\tPoint: (%f, %f)\n", sp->GetX (), sp->GetY ());
506 #endif
507 if (!GetBounds ().IntersectsWith (stylusPoints->GetBounds ()))
508 return false;
510 /* test the beginning endcap */
511 sp = myStylusPoints->GetValueAt (0)->AsStylusPoint ();
513 if (HitTestEndcap (Point (sp->GetX (), sp->GetY ()),
514 width, height, stylusPoints)) {
515 #if DEBUG_HITTEST
516 fprintf (stderr, "\tA point matched the beginning endcap\n");
517 #endif
518 return true;
521 /* test all the interior line segments */
522 StylusPoint *prev_point = sp;
523 for (int i = 1; i < myStylusPoints->GetCount (); i++) {
524 sp = myStylusPoints->GetValueAt (i)->AsStylusPoint ();
526 if (HitTestSegment (Point (prev_point->GetX (), prev_point->GetY ()),
527 Point (sp->GetX (), sp->GetY ()),
528 width, height, stylusPoints)) {
529 #if DEBUG_HITTEST
530 fprintf (stderr, "\tA point matched an interior line segment\n");
531 #endif
532 return true;
536 /* the the ending endcap */
537 if (myStylusPoints->GetCount () > 1) {
538 sp = myStylusPoints->GetValueAt (myStylusPoints->GetCount () - 1)->AsStylusPoint ();
540 if (HitTestEndcap (Point (sp->GetX (), sp->GetY ()),
541 width, height, stylusPoints)) {
542 #if DEBUG_HITTEST
543 fprintf (stderr, "\tA point matched the ending endcap\n");
544 #endif
545 return true;
549 #if DEBUG_HITTEST
550 fprintf (stderr, "\tso sad, no points intersected...\n");
551 #endif
553 return false;
556 Rect
557 Stroke::AddStylusPointToBounds (StylusPoint *stylus_point, const Rect &bounds)
559 DrawingAttributes *da = GetDrawingAttributes ();
560 double height, width;
562 if (da) {
563 height = da->GetHeight ();
564 width = da->GetWidth ();
566 Color *col = da->GetOutlineColor ();
567 if (col->a != 0x00) {
568 height += 4.0;
569 width += 4.0;
571 } else {
572 height = width = 6.0;
575 return bounds.Union (Rect (stylus_point->GetX () - width / 2,
576 stylus_point->GetY () - height / 2,
577 width, height));
580 void
581 Stroke::ComputeBounds ()
583 bounds = Rect ();
585 StylusPointCollection *spc = GetStylusPoints ();
586 if (!spc)
587 return;
589 for (int i = 0; i < spc->GetCount (); i++)
590 bounds = AddStylusPointToBounds (spc->GetValueAt (i)->AsStylusPoint (), bounds);
593 void
594 Stroke::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
596 Rect point;
598 if (col != GetStylusPoints ()) {
599 DependencyObject::OnCollectionChanged (col, args);
600 return;
603 old_bounds = bounds;
605 switch (args->GetChangedAction ()) {
606 case CollectionChangedActionAdd:
607 // add previous point to dirty
608 if (args->GetIndex() > 0)
609 dirty = AddStylusPointToBounds (col->GetValueAt (args->GetIndex() - 1)->AsStylusPoint (), dirty);
611 // add new point to dirty
612 dirty = AddStylusPointToBounds (args->GetNewItem()->AsStylusPoint (), dirty);
614 // add next point to dirty
615 if (args->GetIndex() + 1 < col->GetCount ())
616 dirty = AddStylusPointToBounds (col->GetValueAt (args->GetIndex() + 1)->AsStylusPoint (), dirty);
618 // update official bounds
619 bounds = bounds.Union (dirty);
620 break;
621 case CollectionChangedActionRemove:
622 case CollectionChangedActionReplace:
623 case CollectionChangedActionCleared:
624 ComputeBounds ();
625 dirty = dirty.Union (old_bounds.Union (bounds));
626 break;
627 case CollectionChangedActionClearing:
628 // nothing needed here.
629 break;
632 NotifyListenersOfPropertyChange (Stroke::StylusPointsProperty, NULL);
635 void
636 Stroke::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
638 if (col != GetStylusPoints ()) {
639 DependencyObject::OnCollectionItemChanged (col, obj, args);
640 return;
643 old_bounds = bounds;
645 ComputeBounds ();
647 dirty = old_bounds.Union (bounds);
649 NotifyListenersOfPropertyChange (Stroke::StylusPointsProperty, NULL);
652 void
653 Stroke::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
655 if (args->GetProperty ()->GetOwnerType() != Type::STROKE) {
656 DependencyObject::OnPropertyChanged (args, error);
659 if (args->GetId () == Stroke::DrawingAttributesProperty) {
660 ComputeBounds ();
663 NotifyListenersOfPropertyChange (args, error);
666 void
667 Stroke::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
669 if (prop->GetId () == Stroke::DrawingAttributesProperty) {
670 if (subobj_args->GetId () == DrawingAttributes::WidthProperty ||
671 subobj_args->GetId () == DrawingAttributes::HeightProperty ||
672 subobj_args->GetId () == DrawingAttributes::OutlineColorProperty) {
673 ComputeBounds ();
677 DependencyObject::OnSubPropertyChanged (prop, obj, subobj_args);
682 // StrokeCollection
685 bool
686 StrokeCollection::CanAdd (Value *value)
688 // We skip DependencyObjectCollection::CanAdd because that one
689 // mandates 1 parent per DO, which strokes violate.
690 return Collection::CanAdd (value) && !Contains (value);
693 bool
694 StrokeCollection::AddedToCollection (Value *value, MoonError *error)
696 DependencyObject *obj = value->AsDependencyObject ();
698 obj->SetIsAttached (IsAttached ());
699 obj->SetParent (this, error);
700 obj->AddPropertyChangeListener (this);
702 // Bypass DependencyObjectCollection::AddedToCollection(), we
703 // are handling everything it would normally do. Also Clear()
704 // the MoonError because we ignore any errors from
705 // SetLogicalParent() since we'll only get an error if the
706 // stroke already has a logical parent (which is OK for
707 // strokes).
709 error->Clear ();
711 return Collection::AddedToCollection (value, error);
714 Rect
715 StrokeCollection::GetBounds ()
717 Rect r = Rect (0, 0, 0, 0);
719 for (guint i = 0; i < array->len; i++)
720 r = r.Union (((Value *) array->pdata[i])->AsStroke ()->GetBounds ());
722 return r;
725 StrokeCollection *
726 StrokeCollection::HitTest (StylusPointCollection *stylusPoints)
728 StrokeCollection *result = new StrokeCollection ();
730 if (stylusPoints->GetCount () == 0)
731 return result;
733 for (guint i = 0; i < array->len; i++) {
734 Stroke *s = ((Value *) array->pdata[i])->AsStroke ();
736 if (s->HitTest(stylusPoints))
737 result->Add (s);
740 return result;
743 static void
744 drawing_attributes_quick_render (cairo_t *cr, double thickness, Color *color, StylusPointCollection *collection)
746 StylusPoint *sp;
747 double x, y;
749 if (collection->GetCount () == 0)
750 return;
752 sp = collection->GetValueAt (0)->AsStylusPoint ();
753 x = sp->GetX ();
754 y = sp->GetY ();
756 cairo_move_to (cr, x, y);
758 if (collection->GetCount () > 1) {
759 for (int i = 1; i < collection->GetCount (); i++) {
760 sp = collection->GetValueAt (i)->AsStylusPoint ();
761 x = sp->GetX ();
762 y = sp->GetY ();
764 cairo_line_to (cr, x, y);
766 } else {
767 cairo_line_to (cr, x, y);
770 if (color)
771 cairo_set_source_rgba (cr, color->r, color->g, color->b, color->a);
772 else
773 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
775 cairo_set_line_width (cr, thickness);
776 cairo_stroke (cr);
779 static void
780 drawing_attributes_normal_render (cairo_t *cr, double width, double height, Color *color, Color *outline, StylusPointCollection *collection)
782 // FIXME: use cairo_stroke_to_path once available
783 // until then draw bigger with the outline color and smaller with the inner color
784 drawing_attributes_quick_render (cr, height + 4.0, outline, collection);
785 drawing_attributes_quick_render (cr, (height > 4.0) ? height - 2.0 : 2.0, color, collection);
788 void
789 DrawingAttributes::Render (cairo_t *cr, StylusPointCollection *collection)
791 if (!collection)
792 return;
794 double height = GetHeight ();
795 double width = GetWidth ();
796 Color *color = GetColor ();
797 Color *outline = GetOutlineColor ();
799 // we can render very quickly if the pen is round, i.e. Width==Height (circle)
800 // and when no OutlineColor are specified (e.g. NULL, transparent)
801 if ((!outline || outline->a == 0x00) && (height == width)) {
802 drawing_attributes_quick_render (cr, height, color, collection);
803 // TODO - we could add another fast-path in the case where height!=width and without an outline
804 // in this case we would need a scaling transform (for the pen) and adjust the coordinates
805 } else {
806 drawing_attributes_normal_render (cr, width, height, color, outline, collection);
810 void
811 DrawingAttributes::RenderWithoutDrawingAttributes (cairo_t *cr, StylusPointCollection *collection)
813 // default values that (seems to) match the output when no DrawingAttributes are specified
814 drawing_attributes_quick_render (cr, 2.0, NULL, collection);
819 // InkPresenter
822 InkPresenter::InkPresenter ()
824 SetObjectType (Type::INKPRESENTER);
827 void
828 InkPresenter::PostRender (cairo_t *cr, Region *region, bool front_to_back)
830 // render our chidren if not in front to back mode
831 if (!front_to_back) {
832 VisualTreeWalker walker = VisualTreeWalker (this, ZForward);
833 while (UIElement *child = walker.Step ())
834 child->DoRender (cr, region);
837 cairo_set_matrix (cr, &absolute_xform);
838 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
839 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
841 StrokeCollection *strokes = GetStrokes ();
842 // for each stroke in collection
843 for (int i = 0; i < strokes->GetCount (); i++) {
844 Stroke *stroke = strokes->GetValueAt (i)->AsStroke ();
845 DrawingAttributes *da = stroke->GetDrawingAttributes ();
846 StylusPointCollection *spc = stroke->GetStylusPoints ();
848 if (da) {
849 da->Render (cr, spc);
850 } else {
851 DrawingAttributes::RenderWithoutDrawingAttributes (cr, spc);
854 stroke->ResetDirty ();
857 // Chain up in front_to_back mode since we've alread rendered content
858 UIElement::PostRender (cr, region, true);
862 void
863 InkPresenter::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
865 if (args->GetProperty ()->GetOwnerType() != Type::INKPRESENTER) {
866 Canvas::OnPropertyChanged (args, error);
867 return;
870 if (args->GetId () == InkPresenter::StrokesProperty) {
871 // be smart about invalidating only the union of the
872 // old stroke bounds and the new stroke bounds
874 if (args->GetOldValue()) {
875 StrokeCollection *strokes = args->GetOldValue()->AsStrokeCollection();
876 if (strokes)
877 Invalidate (strokes->GetBounds().Transform (&absolute_xform));
878 //XXX else ?
881 if (args->GetNewValue()) {
882 StrokeCollection *strokes = args->GetNewValue()->AsStrokeCollection();
883 if (strokes)
884 Invalidate (strokes->GetBounds().Transform (&absolute_xform));
885 //XXX else ?
888 UpdateBounds ();
891 NotifyListenersOfPropertyChange (args, error);
894 void
895 InkPresenter::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
897 Stroke *stroke;
899 if (col != GetStrokes ()) {
900 Canvas::OnCollectionChanged (col, args);
901 return;
904 switch (args->GetChangedAction()) {
905 case CollectionChangedActionAdd:
906 stroke = args->GetNewItem()->AsStroke ();
907 Invalidate (stroke->GetBounds().Transform (&absolute_xform));
908 UpdateBounds ();
909 break;
910 case CollectionChangedActionRemove:
911 stroke = args->GetOldItem()->AsStroke ();
912 Invalidate (stroke->GetOldBounds ().Transform (&absolute_xform));
913 Invalidate (stroke->GetBounds ().Transform (&absolute_xform));
914 UpdateBounds ();
915 break;
916 case CollectionChangedActionReplace:
917 stroke = args->GetOldItem()->AsStroke ();
918 Invalidate (stroke->GetOldBounds ().Transform (&absolute_xform));
919 stroke = args->GetNewItem()->AsStroke ();
920 Invalidate (stroke->GetBounds().Transform (&absolute_xform));
921 UpdateBounds ();
922 break;
923 case CollectionChangedActionCleared:
924 Invalidate (render_bounds);
925 Invalidate (((StrokeCollection*)col)->GetBounds().Transform (&absolute_xform));
926 UpdateBounds ();
927 break;
928 case CollectionChangedActionClearing:
929 // nothing needed here.
930 break;
934 void
935 InkPresenter::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
937 Stroke *stroke = (Stroke *) obj;
939 if (col != GetStrokes ()) {
940 Canvas::OnCollectionItemChanged (col, obj, args);
941 return;
944 Invalidate (stroke->GetDirty ().Transform (&absolute_xform));
945 UpdateBounds ();
948 void
949 InkPresenter::ComputeBounds ()
951 Canvas::ComputeBounds ();
953 render_bounds = bounds;
955 StrokeCollection *strokes = GetStrokes ();
956 if (!strokes)
957 return;
959 Rect stroke_bounds = strokes->GetBounds ();
960 stroke_bounds = stroke_bounds.Transform (&absolute_xform);
961 bounds_with_children = bounds_with_children.Union (stroke_bounds);
963 render_bounds = render_bounds.Union (stroke_bounds);
966 Rect
967 InkPresenter::GetRenderBounds ()
969 return render_bounds;
972 void
973 InkPresenter::ShiftPosition (Point p)
975 double dx = p.x - bounds.x;
976 double dy = p.y - bounds.y;
978 // need to do this after computing the delta
979 Canvas::ShiftPosition (p);
981 render_bounds.x += dx;
982 render_bounds.y += dy;
985 void
986 stroke_get_bounds (Stroke *stroke, Rect *bounds)
988 *bounds = stroke->GetBounds ();
991 void
992 stroke_collection_get_bounds (StrokeCollection *collection, Rect *bounds)
994 *bounds = collection->GetBounds ();