2 * shape.cpp: This match the classes inside System.Windows.Shapes
5 * Moonlight List (moonlight-list@lists.ximian.com)
7 * Copyright 2007-2008 Novell, Inc. (http://www.novell.com)
9 * See the LICENSE file included with the distribution for details.
26 // SL-Cairo convertion and helper routines
29 #define EXACT_BOUNDS 1
31 static cairo_line_join_t
32 convert_line_join (PenLineJoin pen_line_join
)
34 switch (pen_line_join
) {
36 /* note: invalid values should be trapped in SetValue (see bug #340799) */
37 g_warning ("Invalid value (%d) specified for PenLineJoin, using default.", pen_line_join
);
38 /* at this stage we use the default value (Miter) for Shape */
39 case PenLineJoinMiter
:
40 return CAIRO_LINE_JOIN_MITER
;
41 case PenLineJoinBevel
:
42 return CAIRO_LINE_JOIN_BEVEL
;
43 case PenLineJoinRound
:
44 return CAIRO_LINE_JOIN_ROUND
;
48 /* NOTE: Triangle doesn't exist in Cairo - unless you patched it using https://bugzilla.novell.com/show_bug.cgi?id=345892 */
49 #ifndef HAVE_CAIRO_LINE_CAP_TRIANGLE
50 #define CAIRO_LINE_CAP_TRIANGLE CAIRO_LINE_CAP_ROUND
53 static cairo_line_cap_t
54 convert_line_cap (PenLineCap pen_line_cap
)
56 switch (pen_line_cap
) {
58 /* note: invalid values should be trapped in SetValue (see bug #340799) */
59 g_warning ("Invalid value (%d) specified for PenLineCap, using default.", pen_line_cap
);
60 /* at this stage we use the default value (Flat) for Shape */
62 return CAIRO_LINE_CAP_BUTT
;
63 case PenLineCapSquare
:
64 return CAIRO_LINE_CAP_SQUARE
;
66 return CAIRO_LINE_CAP_ROUND
;
67 case PenLineCapTriangle
:
68 return CAIRO_LINE_CAP_TRIANGLE
;
73 convert_fill_rule (FillRule fill_rule
)
77 /* note: invalid values should be trapped in SetValue (see bug #340799) */
78 g_warning ("Invalid value (%d) specified for FillRule, using default.", fill_rule
);
79 /* at this stage we use the default value (EvenOdd) for Geometry */
81 return CAIRO_FILL_RULE_EVEN_ODD
;
83 return CAIRO_FILL_RULE_WINDING
;
93 SetObjectType (Type::SHAPE
);
98 cached_surface
= NULL
;
99 SetShapeFlags (UIElement::SHAPE_NORMAL
);
100 cairo_matrix_init_identity (&stretch_transform
);
102 SetStrokeDashArray (DOPtr
<DoubleCollection
> (new DoubleCollection ()));
107 // That also destroys the cached surface
108 InvalidatePathCache (true);
112 Shape::GetTransformOrigin ()
114 if (GetStretch () != StretchNone
)
115 return FrameworkElement::GetTransformOrigin ();
121 Shape::GetGeometryTransform ()
123 Matrix
*matrix
= new Matrix (&stretch_transform
);
125 MatrixTransform
*transform
= new MatrixTransform ();
127 transform
->SetValue (MatrixTransform::MatrixProperty
, matrix
);
134 Shape::Draw (cairo_t
*cr
)
136 if (!path
|| (path
->cairo
.num_data
== 0))
140 cairo_transform (cr
, &stretch_transform
);
143 cairo_append_path (cr
, &path
->cairo
);
148 // break up operations so we can exclude optional stuff, like:
149 // * StrokeStartLineCap & StrokeEndLineCap
150 // * StrokeLineJoin & StrokeMiterLimit
154 Shape::SetupLine (cairo_t
*cr
)
156 double thickness
= GetStrokeThickness ();
158 // check if something will be drawn or return
159 // note: override this method if cairo is used to compute bounds
163 cairo_set_line_width (cr
, thickness
);
165 return SetupDashes (cr
, thickness
);
169 Shape::SetupDashes (cairo_t
*cr
, double thickness
)
171 return Shape::SetupDashes (cr
, thickness
, GetStrokeDashOffset () * thickness
);
175 Shape::SetupDashes (cairo_t
*cr
, double thickness
, double offset
)
177 DoubleCollection
*dashes
= GetStrokeDashArray ();
178 if (dashes
&& (dashes
->GetCount() > 0)) {
179 int count
= dashes
->GetCount();
181 // NOTE: special case - if we continue cairo will stops drawing!
182 if ((count
== 1) && (dashes
->GetValueAt(0)->AsDouble() == 0.0))
185 // multiply dashes length with thickness
186 double *dmul
= new double [count
];
187 for (int i
=0; i
< count
; i
++) {
188 dmul
[i
] = dashes
->GetValueAt(i
)->AsDouble() * thickness
;
191 cairo_set_dash (cr
, dmul
, count
, offset
);
194 cairo_set_dash (cr
, NULL
, 0, 0.0);
200 Shape::SetupLineCaps (cairo_t
*cr
)
202 // Setting the cap to dash_cap. the endcaps (if different) are handled elsewhere
203 PenLineCap cap
= GetStrokeDashCap ();
205 cairo_set_line_cap (cr
, convert_line_cap (cap
));
209 Shape::SetupLineJoinMiter (cairo_t
*cr
)
211 PenLineJoin join
= GetStrokeLineJoin ();
212 double limit
= GetStrokeMiterLimit ();
214 cairo_set_line_join (cr
, convert_line_join (join
));
215 cairo_set_miter_limit (cr
, limit
);
218 // returns true if the path is set on the cairo, false if not
220 Shape::Fill (cairo_t
*cr
, bool do_op
)
227 fill
->SetupBrush (cr
, GetStretchExtents ());
228 cairo_set_fill_rule (cr
, convert_fill_rule (GetFillRule ()));
229 fill
->Fill (cr
, true);
235 Shape::ComputeStretchBounds ()
238 * NOTE: this code is extremely fragile don't make a change here without
239 * checking the results of the test harness on with MOON_DRT_CATEGORIES=stretch
242 bool autodim
= isnan (GetWidth ());
244 Stretch stretch
= GetStretch ();
245 Rect shape_bounds
= GetNaturalBounds ();
247 if (shape_bounds
.width
<= 0.0 || shape_bounds
.height
<= 0.0) {
248 SetShapeFlags (UIElement::SHAPE_EMPTY
);
252 Size
framework (GetActualWidth (), GetActualHeight ());
253 Size
specified (GetWidth (), GetHeight ());
255 if (specified
.width
<= 0.0 || specified
.height
<= 0.0) {
256 SetShapeFlags (UIElement::SHAPE_EMPTY
);
260 if (GetVisualParent () && GetVisualParent()->Is (Type::CANVAS
)) {
261 if (!isnan (specified
.width
))
262 framework
.width
= specified
.width
;
263 if (!isnan (specified
.height
))
264 framework
.height
= specified
.height
;
267 framework
.width
= framework
.width
== 0.0 ? shape_bounds
.width
: framework
.width
;
268 framework
.height
= framework
.height
== 0.0 ? shape_bounds
.height
: framework
.height
;
270 if (stretch
!= StretchNone
) {
271 Rect logical_bounds
= ComputeShapeBounds (true, NULL
);
273 bool adj_x
= logical_bounds
.width
!= 0.0;
274 bool adj_y
= logical_bounds
.height
!= 0.0;
276 double diff_x
= shape_bounds
.width
- logical_bounds
.width
;
277 double diff_y
= shape_bounds
.height
- logical_bounds
.height
;
278 double sw
= adj_x
? (framework
.width
- diff_x
) / logical_bounds
.width
: 1.0;
279 double sh
= adj_y
? (framework
.height
- diff_y
) / logical_bounds
.height
: 1.0;
288 sw
= sh
= (sw
< sh
) ? sw
: sh
;
291 case StretchUniformToFill
:
292 sw
= sh
= (sw
> sh
) ? sw
: sh
;
299 // trying to avoid the *VERY*SLOW* adjustments
300 // e.g. apps like Silverlight World have a ratio up to 50 unneeded for 1 needed adjustment
301 // so it all boilds down are we gonna change bounds anyway ?
302 #define IS_SIGNIFICANT(dx,x) (IS_ZERO(dx) && (fabs(dx) * x - x > 1.0))
303 if ((adj_x
&& IS_SIGNIFICANT((sw
- 1), shape_bounds
.width
)) || (adj_y
&& IS_SIGNIFICANT((sh
- 1), shape_bounds
.height
))) {
304 // FIXME: this IS still UBER slow
305 // hereafter we're doing a second pass to refine the sw and sh we guessed
306 // the first time. This usually gives pixel-recise stretches for Paths
308 cairo_matrix_init_scale (&temp
, adj_x
? sw
: 1.0, adj_y
? sh
: 1.0);
309 Rect stretch_bounds
= ComputeShapeBounds (false, &temp
);
310 if (stretch_bounds
.width
!= shape_bounds
.width
&& stretch_bounds
.height
!= shape_bounds
.height
) {
311 sw
*= adj_x
? (framework
.width
- stretch_bounds
.width
+ logical_bounds
.width
* sw
) / (logical_bounds
.width
* sw
): 1.0;
312 sh
*= adj_y
? (framework
.height
- stretch_bounds
.height
+ logical_bounds
.height
* sh
) / (logical_bounds
.height
* sh
): 1.0;
316 sw
= sh
= (sw
< sh
) ? sw
: sh
;
318 case StretchUniformToFill
:
319 sw
= sh
= (sw
> sh
) ? sw
: sh
;
325 // end of the 2nd pass code
328 double x
= !autodim
|| adj_x
? shape_bounds
.x
: 0;
329 double y
= !autodim
|| adj_y
? shape_bounds
.y
: 0;
332 cairo_matrix_translate (&stretch_transform
,
333 adj_x
? framework
.width
* 0.5 : 0,
334 adj_y
? framework
.height
* 0.5 : 0);
336 cairo_matrix_translate (&stretch_transform
,
337 adj_x
? (logical_bounds
.width
* sw
+ diff_x
) * .5 : 0,
338 adj_y
? (logical_bounds
.height
* sh
+ diff_y
) * .5: 0);
340 cairo_matrix_scale (&stretch_transform
,
344 cairo_matrix_translate (&stretch_transform
,
345 adj_x
? -shape_bounds
.width
* 0.5 : 0,
346 adj_y
? -shape_bounds
.height
* 0.5 : 0);
348 if (!Is (Type::LINE
) || !autodim
)
349 cairo_matrix_translate (&stretch_transform
, -x
, -y
);
351 // Double check our math
352 cairo_matrix_t test
= stretch_transform
;
353 if (cairo_matrix_invert (&test
)) {
354 g_warning ("Unable to compute stretch transform %f %f %f %f \n", sw
, sh
, shape_bounds
.x
, shape_bounds
.y
);
358 shape_bounds
= shape_bounds
.Transform (&stretch_transform
);
364 Shape::Stroke (cairo_t
*cr
, bool do_op
)
367 stroke
->SetupBrush (cr
, GetStretchExtents ());
373 Shape::Clip (cairo_t
*cr
)
375 Rect specified
= Rect (0, 0, GetWidth (), GetHeight ());
376 Rect paint
= Rect (0, 0, GetActualWidth (), GetActualHeight ());
377 UIElement
*parent
= GetVisualParent ();
378 bool in_flow
= parent
&& !parent
->Is (Type::CANVAS
);
381 * NOTE the clumbsy rounding up to 1 here is based on tests like
382 * test-shape-path-stretch.xaml where it silverlight attempts
383 * to make sure something is always drawn a better mechanism
386 if (!IsDegenerate ()) {
387 bool clip_bounds
= false;
388 if (!isnan (specified
.width
) && specified
.width
>= 1) { // && paint.width > specified.width) {
389 paint
.width
= specified
.width
;
391 paint
.height
= isnan (specified
.height
) ? 0 : MAX (1, specified
.height
);
395 if (!isnan (specified
.height
) && specified
.height
>= 1) { // && paint.height > specified.height) {
396 paint
.height
= specified
.height
;
399 paint
.width
= isnan (specified
.width
) ? 0 : MAX (1, specified
.width
);
409 RenderLayoutClip (cr
);
413 // Returns TRUE if surface is a good candidate for caching.
414 // We accept a little bit of scaling.
417 Shape::IsCandidateForCaching (void)
426 * these fill and stroke short circuits are attempts be smart
427 * about determining when the cost of caching is greater than
428 * the cost of simply drawing all the choices here should really
429 * have best and worst case perf tests associated with them
430 * but they don't right now
432 bool gradient_fill
= false;
433 /* XXX FIXME this should be a property on the shape */
434 bool simple
= Is (Type::RECTANGLE
) || Is (Type::ELLIPSE
);
437 if (fill
->IsAnimating ())
440 gradient_fill
|= fill
->Is (Type::GRADIENTBRUSH
);
444 if (stroke
&& stroke
->IsAnimating ())
447 if (simple
&& !gradient_fill
)
450 // This is not 100% correct check -- the actual surface size might be
451 // a tiny little bit larger. It's not a problem though if we go few
452 // bytes above the cache limit.
453 if (!GetSurface ()->VerifyWithCacheSizeCounter ((int) bounds
.width
, (int) bounds
.height
))
456 // one last line of defense, lets not cache things
457 // much larger than the screen.
458 if (bounds
.width
* bounds
.height
> 4000000)
465 // This routine is useful for Shape derivatives: it can be used
466 // to either get the bounding box from cairo, or to paint it
469 Shape::DoDraw (cairo_t
*cr
, bool do_op
)
473 // quick out if, when building the path, we detected an empty shape
477 if (do_op
&& cached_surface
== NULL
&& IsCandidateForCaching ()) {
478 Rect cache_extents
= bounds
.RoundOut ();
479 cairo_t
*cached_cr
= NULL
;
481 // g_warning ("bounds (%f, %f), extents (%f, %f), cache_extents (%f, %f)",
482 // bounds.width, bounds.height,
483 // extents.width, extents.height,
484 // cache_extents.width, cache_extents.height);
486 cached_surface
= image_brush_create_similar (cr
, (int) cache_extents
.width
, (int) cache_extents
.height
);
487 cairo_surface_set_device_offset (cached_surface
, -cache_extents
.x
, -cache_extents
.y
);
488 cached_cr
= cairo_create (cached_surface
);
490 cairo_set_matrix (cached_cr
, &absolute_xform
);
492 ret
= DrawShape (cached_cr
, do_op
);
494 cairo_destroy (cached_cr
);
496 // Increase our cache size
497 cached_size
= GetSurface ()->AddToCacheSizeCounter ((int) cache_extents
.width
, (int) cache_extents
.height
);
500 if (do_op
&& cached_surface
) {
501 cairo_pattern_t
*cached_pattern
= NULL
;
503 cached_pattern
= cairo_pattern_create_for_surface (cached_surface
);
504 cairo_set_matrix (cr
, &absolute_xform
);
508 cairo_identity_matrix (cr
);
509 cairo_set_source (cr
, cached_pattern
);
510 cairo_pattern_destroy (cached_pattern
);
513 cairo_set_matrix (cr
, &absolute_xform
);
517 if (DrawShape (cr
, do_op
))
527 Shape::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
530 DoDraw (cr
, true && !path_only
);
535 Shape::ShiftPosition (Point p
)
537 double dx
= bounds
.x
- p
.x
;
538 double dy
= bounds
.y
- p
.y
;
540 // FIXME this is much less than ideal but we must invalidate the surface cache
541 // if the shift is not an integer otherwise we can potentially drow outside our
542 // rounded out bounds.
543 if (cached_surface
&& (dx
== trunc(dx
)) && (dy
== trunc(dy
))) {
544 cairo_surface_set_device_offset (cached_surface
, trunc (-p
.x
), trunc (-p
.y
));
546 InvalidateSurfaceCache ();
549 FrameworkElement::ShiftPosition (p
);
554 Shape::ComputeActualSize ()
556 Size desired
= FrameworkElement::ComputeActualSize ();
557 Rect shape_bounds
= GetNaturalBounds ();
561 if (!GetSurface ()) //|| LayoutInformation::GetPreviousConstraint (this) != NULL)
564 if (shape_bounds
.width
<= 0 && shape_bounds
.height
<= 0)
567 if (GetStretch () == StretchNone
&& shape_bounds
.width
> 0 && shape_bounds
.height
> 0)
568 return Size (shape_bounds
.width
, shape_bounds
.height
);
570 /* don't stretch to infinite size */
571 if (isinf (desired
.width
))
572 desired
.width
= shape_bounds
.width
;
573 if (isinf (desired
.height
))
574 desired
.height
= shape_bounds
.height
;
576 /* compute the scaling */
577 if (shape_bounds
.width
> 0)
578 sx
= desired
.width
/ shape_bounds
.width
;
579 if (shape_bounds
.height
> 0)
580 sy
= desired
.height
/ shape_bounds
.height
;
582 switch (GetStretch ()) {
584 sx
= sy
= MIN (sx
, sy
);
586 case StretchUniformToFill
:
587 sx
= sy
= MAX (sx
, sy
);
593 desired
= desired
.Min (shape_bounds
.width
* sx
, shape_bounds
.height
* sy
);
599 Shape::MeasureOverride (Size availableSize
)
601 Size desired
= availableSize
;
602 Rect shape_bounds
= GetNaturalBounds ();
606 if (GetStretch () == StretchNone
)
607 return ApplySizeConstraints (Size (shape_bounds
.x
+ shape_bounds
.width
, shape_bounds
.y
+ shape_bounds
.height
));
609 if (Is (Type::RECTANGLE
) || Is (Type::ELLIPSE
)) {
610 desired
= Size (0,0);
613 /* don't stretch to infinite size */
614 if (isinf (availableSize
.width
))
615 desired
.width
= shape_bounds
.width
;
616 if (isinf (availableSize
.height
))
617 desired
.height
= shape_bounds
.height
;
619 /* compute the scaling */
620 if (shape_bounds
.width
> 0)
621 sx
= desired
.width
/ shape_bounds
.width
;
622 if (shape_bounds
.height
> 0)
623 sy
= desired
.height
/ shape_bounds
.height
;
625 /* don't use infinite dimensions as constraints */
626 if (isinf (availableSize
.width
))
628 if (isinf (availableSize
.height
))
631 switch (GetStretch ()) {
633 sx
= sy
= MIN (sx
, sy
);
635 case StretchUniformToFill
:
636 sx
= sy
= MAX (sx
, sy
);
639 if (isinf (availableSize
.width
))
641 if (isinf (availableSize
.height
))
646 desired
= Size (shape_bounds
.width
* sx
, shape_bounds
.height
* sy
);
648 return desired
.Min (availableSize
);
652 Shape::ArrangeOverride (Size finalSize
)
654 Size arranged
= finalSize
;
655 Rect shape_bounds
= GetNaturalBounds ();
659 //if (!LayoutInformation::GetPreviousConstraint (this))
660 // MeasureOverride (finalSize);
662 if (GetStretch () == StretchNone
) {
663 arranged
= Size (shape_bounds
.x
+ shape_bounds
.width
,
664 shape_bounds
.y
+ shape_bounds
.height
);
666 arranged
= arranged
.Max (finalSize
);
671 /* compute the scaling */
672 if (shape_bounds
.width
== 0)
673 shape_bounds
.width
= arranged
.width
;
674 if (shape_bounds
.height
== 0)
675 shape_bounds
.height
= arranged
.height
;
677 if (shape_bounds
.width
!= arranged
.width
)
678 sx
= arranged
.width
/ shape_bounds
.width
;
679 if (shape_bounds
.height
!= arranged
.height
)
680 sy
= arranged
.height
/ shape_bounds
.height
;
682 switch (GetStretch ()) {
684 sx
= sy
= MIN (sx
, sy
);
686 case StretchUniformToFill
:
687 sx
= sy
= MAX (sx
, sy
);
693 arranged
= Size (shape_bounds
.width
* sx
, shape_bounds
.height
* sy
);
695 if ((Is (Type::RECTANGLE
) || Is (Type::ELLIPSE
)) && LayoutInformation::GetPreviousConstraint (this)) {
696 arranged
= ApplySizeConstraints (arranged
);
698 extents
= Rect (0,0, arranged
.width
, arranged
.height
);
701 // We need to clear any existing path so that it will be correctly
704 moon_path_clear (path
);
712 Shape::TransformBounds (cairo_matrix_t
*old
, cairo_matrix_t
*current
)
714 InvalidateSurfaceCache ();
715 bounds_with_children
= bounds
= IntersectBoundsWithClipPath (GetStretchExtents (), false).Transform (current
);
719 Shape::ComputeBounds ()
721 bounds_with_children
= bounds
= IntersectBoundsWithClipPath (GetStretchExtents (), false).Transform (&absolute_xform
);
722 //printf ("%f,%f,%f,%f\n", bounds.x, bounds.y, bounds.width, bounds.height);
726 Shape::ComputeShapeBounds (bool logical
, cairo_matrix_t
*matrix
)
728 if (Is (Type::RECTANGLE
) || Is (Type::ELLIPSE
))
731 if (!path
|| (path
->cairo
.num_data
== 0))
737 double thickness
= (logical
|| !IsStroked ()) ? 0.0 : GetStrokeThickness ();
739 cairo_t
*cr
= measuring_context_create ();
741 cairo_set_matrix (cr
, matrix
);
743 cairo_set_line_width (cr
, thickness
);
745 if (thickness
> 0.0) {
746 //FIXME: still not 100% precise since it could be different from the end cap
747 PenLineCap cap
= GetStrokeStartLineCap ();
748 if (cap
== PenLineCapFlat
)
749 cap
= GetStrokeEndLineCap ();
750 cairo_set_line_cap (cr
, convert_line_cap (cap
));
753 cairo_append_path (cr
, &path
->cairo
);
755 cairo_identity_matrix (cr
);
757 double x1
, y1
, x2
, y2
;
760 cairo_path_extents (cr
, &x1
, &y1
, &x2
, &y2
);
761 } else if (thickness
> 0) {
762 cairo_stroke_extents (cr
, &x1
, &y1
, &x2
, &y2
);
764 cairo_fill_extents (cr
, &x1
, &y1
, &x2
, &y2
);
767 Rect bounds
= Rect (MIN (x1
, x2
), MIN (y1
, y2
), fabs (x2
- x1
), fabs (y2
- y1
));
769 measuring_context_destroy (cr
);
775 Shape::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
777 *height
= GetStretchExtents ().height
;
778 *width
= GetStretchExtents ().width
;
782 Shape::InsideObject (cairo_t
*cr
, double x
, double y
)
786 TransformPoint (&x
, &y
);
787 if (!GetStretchExtents ().PointInside (x
, y
))
790 Geometry
*clip
= GetClip ();
795 ret
= cairo_in_fill (cr
, x
, y
);
797 ret
= cairo_in_stroke (cr
, x
, y
);
806 // don't check in_stroke without a stroke or in_fill without a fill (even if it can be filled)
807 if (fill
&& CanFill ())
808 ret
|= cairo_in_fill (cr
, x
, y
);
810 ret
|= cairo_in_stroke (cr
, x
, y
);
818 Shape::CacheInvalidateHint (void)
820 // Also kills the surface cache
821 InvalidatePathCache ();
825 Shape::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
827 if (args
->GetProperty ()->GetOwnerType() != Type::SHAPE
) {
828 if ((args
->GetId () == FrameworkElement::HeightProperty
)
829 || (args
->GetId () == FrameworkElement::WidthProperty
))
830 InvalidateStretch ();
832 // CacheInvalidateHint should handle visibility changes in
833 // DirtyRenderVisibility
835 FrameworkElement::OnPropertyChanged (args
, error
);
839 if (args
->GetId () == Shape::StretchProperty
) {
840 InvalidateStretch ();
842 else if (args
->GetId () == Shape::StrokeProperty
) {
843 Brush
*new_stroke
= args
->GetNewValue() ? args
->GetNewValue()->AsBrush () : NULL
;
845 if (!stroke
|| !new_stroke
) {
846 // If the stroke changes from null to
847 // <something> or <something> to null, then
848 // some shapes need to reclaculate the offset
849 // (based on stroke thickness) to start
851 InvalidateStrokeBounds ();
853 InvalidateSurfaceCache ();
856 } else if (args
->GetId () == Shape::FillProperty
) {
857 Brush
*new_fill
= args
->GetNewValue() ? args
->GetNewValue()->AsBrush () : NULL
;
859 if (!fill
|| !new_fill
) {
860 InvalidateFillBounds ();
862 InvalidateSurfaceCache ();
864 fill
= args
->GetNewValue() ? args
->GetNewValue()->AsBrush() : NULL
;
865 } else if (args
->GetId () == Shape::StrokeThicknessProperty
) {
866 // do we invalidate the path here for Type::RECT and Type::ELLIPSE
867 // in case they degenerate? Or do we need it for line caps too
868 InvalidateStrokeBounds ();
869 } else if (args
->GetId () == Shape::StrokeDashCapProperty
870 || args
->GetId () == Shape::StrokeEndLineCapProperty
871 || args
->GetId () == Shape::StrokeLineJoinProperty
872 || args
->GetId () == Shape::StrokeMiterLimitProperty
873 || args
->GetId () == Shape::StrokeStartLineCapProperty
) {
874 InvalidateStrokeBounds ();
879 NotifyListenersOfPropertyChange (args
, error
);
883 Shape::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
885 if (prop
&& (prop
->GetId () == Shape::FillProperty
|| prop
->GetId () == Shape::StrokeProperty
)) {
887 InvalidateSurfaceCache ();
890 FrameworkElement::OnSubPropertyChanged (prop
, obj
, subobj_args
);
894 Shape::InvalidateStretch ()
896 extents
= Rect (0, 0, -INFINITY
, -INFINITY
);
897 cairo_matrix_init_identity (&stretch_transform
);
898 InvalidatePathCache ();
899 InvalidateMeasure ();
903 Shape::GetStretchExtents ()
905 if (extents
.IsEmpty ())
906 extents
= ComputeStretchBounds ();
912 Shape::InvalidateStrokeBounds ()
914 InvalidateNaturalBounds ();
918 Shape::InvalidateFillBounds ()
920 InvalidateNaturalBounds ();
924 Shape::InvalidateNaturalBounds ()
926 natural_bounds
= Rect (0, 0, -INFINITY
, -INFINITY
);
927 InvalidateStretch ();
931 Shape::GetNaturalBounds ()
933 if (natural_bounds
.IsEmpty ())
934 natural_bounds
= ComputeShapeBounds (false, NULL
);
936 return natural_bounds
;
940 Shape::InvalidatePathCache (bool free
)
942 //SetShapeFlags (UIElement::SHAPE_NORMAL);
945 moon_path_destroy (path
);
948 moon_path_clear (path
);
952 // we always pass true here because in some cases
953 // while the bounds may not have change the rendering
956 InvalidateMeasure ();
957 InvalidateArrange ();
958 InvalidateSurfaceCache ();
962 Shape::InvalidateSurfaceCache (void)
964 if (cached_surface
) {
965 cairo_surface_destroy (cached_surface
);
967 GetSurface ()->RemoveFromCacheSizeCounter (cached_size
);
968 cached_surface
= NULL
;
974 Shape::CreateDefaultStretch (DependencyObject
*instance
, DependencyProperty
*property
)
976 if (instance
->Is (Type::RECTANGLE
) || instance
->Is (Type::ELLIPSE
))
977 return new Value (StretchFill
);
979 return new Value (StretchNone
);
988 SetObjectType (Type::ELLIPSE
);
992 * Ellipses (like Rectangles) are special and they don't need to participate
993 * in the other stretch logic
996 Ellipse::ComputeStretchBounds ()
998 return ComputeShapeBounds (false);
1002 Ellipse::ComputeShapeBounds (bool logical
)
1004 Rect rect
= Rect (0, 0, GetActualWidth (), GetActualHeight ());
1005 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1006 double t
= GetStrokeThickness ();
1008 if (rect
.width
< 0.0 || rect
.height
< 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1009 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1013 if (GetVisualParent () && GetVisualParent ()->Is (Type::CANVAS
)) {
1014 if (isnan (GetWidth ()) != isnan (GetHeight ())) {
1015 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1020 switch (GetStretch ()) {
1022 rect
.width
= rect
.height
= 0.0;
1024 case StretchUniform
:
1025 rect
.width
= rect
.height
= (rect
.width
< rect
.height
) ? rect
.width
: rect
.height
;
1027 case StretchUniformToFill
:
1028 rect
.width
= rect
.height
= (rect
.width
> rect
.height
) ? rect
.width
: rect
.height
;
1031 /* nothing needed here. the assignment of w/h above
1032 is correct for this case. */
1036 if (rect
.width
<= t
|| rect
.height
<= t
){
1037 rect
.width
= MAX (rect
.width
, t
+ t
* 0.001);
1038 rect
.height
= MAX (rect
.height
, t
+ t
* 0.001);
1039 SetShapeFlags (UIElement::SHAPE_DEGENERATE
);
1041 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1046 // * Shape::StrokeStartLineCap
1047 // * Shape::StrokeEndLineCap
1048 // * Shape::StrokeLineJoin
1049 // * Shape::StrokeMiterLimit
1051 Ellipse::DrawShape (cairo_t
*cr
, bool do_op
)
1053 bool drawn
= Fill (cr
, do_op
);
1057 if (!SetupLine (cr
))
1068 Ellipse::BuildPath ()
1070 Stretch stretch
= GetStretch ();
1071 double t
= IsStroked () ? GetStrokeThickness () : 0.0;
1072 Rect rect
= Rect (0.0, 0.0, GetActualWidth (), GetActualHeight ());
1074 if (rect
.width
< 0.0 || rect
.height
< 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1075 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1080 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1084 rect
.width
= rect
.height
= 0.0;
1086 case StretchUniform
:
1087 rect
.width
= rect
.height
= (rect
.width
< rect
.height
) ? rect
.width
: rect
.height
;
1089 case StretchUniformToFill
:
1090 rect
.width
= rect
.height
= (rect
.width
> rect
.height
) ? rect
.width
: rect
.height
;
1093 /* nothing needed here. the assignment of w/h above
1094 is correct for this case. */
1098 if (rect
.width
<= t
|| rect
.height
<= t
){
1099 rect
.width
= MAX (rect
.width
, t
+ t
* 0.001);
1100 rect
.height
= MAX (rect
.height
, t
+ t
* 0.001);
1101 SetShapeFlags (UIElement::SHAPE_DEGENERATE
);
1103 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1105 rect
= rect
.GrowBy ( -t
/2, -t
/2);
1107 path
= moon_path_renew (path
, MOON_PATH_ELLIPSE_LENGTH
);
1108 moon_ellipse (path
, rect
.x
, rect
.y
, rect
.width
, rect
.height
);
1112 Ellipse::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1115 DependencyProperty *prop = args->GetProperty ();
1117 if ((prop->GetId () == Shape::StrokeThicknessProperty) || (prop->GetId () == Shape::StretchProperty) ||
1118 (prop->GetId () == FrameworkElement::WidthProperty) || (prop->GetId () == FrameworkElement::HeightProperty)) {
1119 // FIXME why are we building the path here?
1121 InvalidateSurfaceCache ();
1125 // Ellipse has no property of it's own
1126 Shape::OnPropertyChanged (args
, error
);
1133 Rectangle::Rectangle ()
1135 SetObjectType (Type::RECTANGLE
);
1139 * Rectangles (like Ellipses) are special and they don't need to participate
1140 * in the other stretch logic
1143 Rectangle::ComputeStretchBounds ()
1145 Rect shape_bounds
= ComputeShapeBounds (false);
1146 return ComputeShapeBounds (false);
1150 Rectangle::ComputeShapeBounds (bool logical
)
1152 Rect rect
= Rect (0, 0, GetActualWidth (), GetActualHeight ());
1153 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1155 if (rect
.width
< 0.0 || rect
.height
< 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1156 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1160 if (GetVisualParent () && GetVisualParent ()->Is (Type::CANVAS
)) {
1161 if (isnan (GetWidth ()) != isnan (GetHeight ())) {
1162 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1167 double t
= IsStroked () ? GetStrokeThickness () : 0.0;
1168 switch (GetStretch ()) {
1170 rect
.width
= rect
.height
= 0.0;
1172 case StretchUniform
:
1173 rect
.width
= rect
.height
= MIN (rect
.width
, rect
.height
);
1175 case StretchUniformToFill
:
1176 // this gets an rectangle larger than it's dimension, relative
1177 // scaling is ok but we need Shape::Draw to clip to it's original size
1178 rect
.width
= rect
.height
= MAX (rect
.width
, rect
.height
);
1181 /* nothing needed here. the assignment of w/h above
1182 is correct for this case. */
1186 if (rect
.width
== 0)
1188 if (rect
.height
== 0)
1191 if (t
>= rect
.width
|| t
>= rect
.height
) {
1192 SetShapeFlags (UIElement::SHAPE_DEGENERATE
);
1193 rect
= rect
.GrowBy (t
* .5005, t
* .5005);
1195 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1202 Rectangle::GetCoverageBounds ()
1204 Brush
*fill
= GetFill ();
1206 if (fill
!= NULL
&& fill
->IsOpaque()) {
1207 /* make it a little easier - only consider the rectangle inside the corner radii.
1208 we're also a little more conservative than we need to be, regarding stroke
1210 double xr
= (GetRadiusX () + GetStrokeThickness () / 2);
1211 double yr
= (GetRadiusY () + GetStrokeThickness () / 2);
1213 return bounds
.GrowBy (-xr
, -yr
).RoundIn ();
1220 // The Rectangle shape can be drawn while ignoring properties:
1221 // * Shape::StrokeStartLineCap
1222 // * Shape::StrokeEndLineCap
1223 // * Shape::StrokeLineJoin [for rounded-corner rectangles only]
1224 // * Shape::StrokeMiterLimit [for rounded-corner rectangles only]
1226 Rectangle::DrawShape (cairo_t
*cr
, bool do_op
)
1228 bool drawn
= Fill (cr
, do_op
);
1233 if (!SetupLine (cr
))
1239 SetupLineJoinMiter (cr
);
1241 // Draw if the path wasn't drawn by the Fill call
1250 * - a Width="0" or a Height="0" can be rendered differently from not specifying Width or Height
1251 * - if a rectangle has only a Width or only a Height it is NEVER rendered
1254 Rectangle::BuildPath ()
1256 Stretch stretch
= GetStretch ();
1257 double t
= IsStroked () ? GetStrokeThickness () : 0.0;
1259 // nothing is drawn (nor filled) if no StrokeThickness="0"
1260 // unless both Width and Height are specified or when no streching is required
1261 Rect rect
= Rect (0, 0, GetActualWidth (), GetActualHeight ());
1263 double radius_x
= GetRadiusX ();
1264 double radius_y
= GetRadiusY ();
1268 rect
.width
= rect
.height
= 0;
1270 case StretchUniform
:
1271 rect
.width
= rect
.height
= MIN (rect
.width
, rect
.height
);
1273 case StretchUniformToFill
:
1274 // this gets an rectangle larger than it's dimension, relative
1275 // scaling is ok but we need Shape::Draw to clip to it's original size
1276 rect
.width
= rect
.height
= MAX (rect
.width
, rect
.height
);
1279 /* nothing needed here. the assignment of w/h above
1280 is correct for this case. */
1284 if (rect
.width
== 0)
1286 if (rect
.height
== 0)
1289 if (t
>= rect
.width
|| t
>= rect
.height
) {
1290 rect
= rect
.GrowBy (t
* 0.001, t
* 0.001);
1291 SetShapeFlags (UIElement::SHAPE_DEGENERATE
);
1293 rect
= rect
.GrowBy (-t
* 0.5, -t
* 0.5);
1294 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1297 path
= moon_path_renew (path
, MOON_PATH_ROUNDED_RECTANGLE_LENGTH
);
1298 moon_rounded_rectangle (path
, rect
.x
, rect
.y
, rect
.width
, rect
.height
, radius_x
, radius_y
);
1302 Rectangle::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1304 if (args
->GetProperty ()->GetOwnerType() != Type::RECTANGLE
) {
1305 Shape::OnPropertyChanged (args
, error
);
1309 if ((args
->GetId () == Rectangle::RadiusXProperty
) || (args
->GetId () == Rectangle::RadiusYProperty
)) {
1310 InvalidatePathCache ();
1314 NotifyListenersOfPropertyChange (args
, error
);
1321 // * the internal path must be rebuilt when
1322 // - Line::X1Property, Line::Y1Property, Line::X2Property or Line::Y2Property is changed
1324 // * bounds calculation is based on
1325 // - Line::X1Property, Line::Y1Property, Line::X2Property and Line::Y2Property
1326 // - Shape::StrokeThickness
1329 #define LINECAP_SMALL_OFFSET 0.1
1331 //Draw the start cap. Shared with Polyline
1333 line_draw_cap (cairo_t
*cr
, Shape
* shape
, PenLineCap cap
, double x1
, double y1
, double x2
, double y2
)
1336 if (cap
== PenLineCapFlat
)
1340 cairo_transform (cr
, &(shape
->stretch_transform
));
1341 if (cap
== PenLineCapRound
) {
1342 cairo_move_to (cr
, x1
, y1
);
1343 cairo_line_to (cr
, x1
, y1
);
1345 cairo_set_line_cap (cr
, convert_line_cap (cap
));
1346 shape
->Stroke (cr
, true);
1354 sy1
= y1
+ LINECAP_SMALL_OFFSET
;
1356 sy1
= y1
- LINECAP_SMALL_OFFSET
;
1357 } else if (y1
== y2
) {
1361 sx1
= x1
+ LINECAP_SMALL_OFFSET
;
1363 sx1
= x1
- LINECAP_SMALL_OFFSET
;
1365 double m
= (y1
- y2
) / (x1
- x2
);
1367 sx1
= x1
+ LINECAP_SMALL_OFFSET
;
1369 sx1
= x1
- LINECAP_SMALL_OFFSET
;
1371 sy1
= m
* sx1
+ y1
- (m
* x1
);
1373 cairo_move_to (cr
, x1
, y1
);
1374 cairo_line_to (cr
, sx1
, sy1
);
1376 cairo_set_line_cap (cr
, convert_line_cap (cap
));
1377 shape
->Stroke (cr
, true);
1380 // The Line shape can be drawn while ignoring properties:
1381 // * Shape::StrokeLineJoin
1382 // * Shape::StrokeMiterLimit
1385 Line::DrawShape (cairo_t
*cr
, bool do_op
)
1387 // no need to clear path since none has been drawn to cairo
1391 if (!SetupLine (cr
))
1394 // here we hack around #345888 where Cairo doesn't support different start and end linecaps
1395 PenLineCap start
= GetStrokeStartLineCap ();
1396 PenLineCap end
= GetStrokeEndLineCap ();
1397 PenLineCap dash
= GetStrokeDashCap ();
1398 bool dashed
= false;
1399 DoubleCollection
*dashes
= GetStrokeDashArray ();
1401 if (dashes
&& (dashes
->GetCount() > 0))
1405 //if (do_op && !(start == end && start == dash)) {
1406 if (do_op
&& (start
!= end
|| (dashed
&& !(start
== end
&& start
== dash
)))) {
1407 double x1
= GetX1 ();
1408 double y1
= GetY1 ();
1409 double x2
= GetX2 ();
1410 double y2
= GetY2 ();
1412 // draw start and end line caps
1413 if (start
!= PenLineCapFlat
)
1414 line_draw_cap (cr
, this, start
, x1
, y1
, x2
, y2
);
1416 if (end
!= PenLineCapFlat
) {
1417 //don't draw the end cap if it's in an "off" segment
1418 double thickness
= GetStrokeThickness ();
1419 double offset
= GetStrokeDashOffset ();
1421 SetupDashes (cr
, thickness
, sqrt ((x2
-x1
)*(x2
-x1
) + (y2
-y1
)*(y2
-y1
)) + offset
* thickness
);
1422 line_draw_cap (cr
, this, end
, x2
, y2
, x1
, y1
);
1426 cairo_set_line_cap (cr
, convert_line_cap (dash
));
1428 cairo_set_line_cap (cr
, convert_line_cap (start
));
1436 calc_line_bounds (double x1
, double x2
, double y1
, double y2
, double thickness
, PenLineCap start_cap
, PenLineCap end_cap
, Rect
* bounds
)
1439 bounds
->x
= x1
- thickness
/ 2.0;
1440 bounds
->y
= MIN (y1
, y2
) - (y1
< y2
&& start_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0) - (y1
>= y2
&& end_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0);
1441 bounds
->width
= thickness
;
1442 bounds
->height
= fabs (y2
- y1
) + (start_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0) + (end_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0);
1443 } else if (y1
== y2
) {
1444 bounds
->x
= MIN (x1
, x2
) - (x1
< x2
&& start_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0) - (x1
>= x2
&& end_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0);
1445 bounds
->y
= y1
- thickness
/ 2.0;
1446 bounds
->width
= fabs (x2
- x1
) + (start_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0) + (end_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0);
1447 bounds
->height
= thickness
;
1449 double m
= fabs ((y1
- y2
) / (x1
- x2
));
1451 double dx
= sin (atan (m
)) * thickness
;
1452 double dy
= cos (atan (m
)) * thickness
;
1454 double dx
= (m
> 1.0) ? thickness
: thickness
* m
;
1455 double dy
= (m
< 1.0) ? thickness
: thickness
/ m
;
1458 switch (start_cap
) {
1459 case PenLineCapSquare
:
1460 bounds
->x
= MIN (x1
, x2
) - (dx
+ dy
) / 2.0;
1462 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1463 case PenLineCapRound
:
1464 bounds
->x
= MIN (x1
, x2
) - thickness
/ 2.0;
1466 default: //PenLineCapFlat
1467 bounds
->x
= MIN (x1
, x2
) - dx
/ 2.0;
1471 case PenLineCapSquare
:
1472 bounds
->x
= MIN (x1
, x2
) - (dx
+ dy
) / 2.0;
1474 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1475 case PenLineCapRound
:
1476 bounds
->x
= MIN (x1
, x2
) - thickness
/ 2.0;
1478 default: //PenLineCapFlat
1479 bounds
->x
= MIN (x1
, x2
) - dx
/ 2.0;
1482 switch (start_cap
) {
1483 case PenLineCapSquare
:
1484 bounds
->y
= MIN (y1
, y2
) - (dx
+ dy
) / 2.0;
1486 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1487 case PenLineCapRound
:
1488 bounds
->y
= MIN (y1
, y2
) - thickness
/ 2.0;
1490 default: //PenLineCapFlat
1491 bounds
->y
= MIN (y1
, y2
) - dy
/ 2.0;
1495 case PenLineCapSquare
:
1496 bounds
->y
= MIN (y1
, y2
) - (dx
+ dy
) / 2.0;
1498 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1499 case PenLineCapRound
:
1500 bounds
->y
= MIN (y1
, y2
) - thickness
/ 2.0;
1502 default: //PenLineCapFlat
1503 bounds
->y
= MIN (y1
, y2
) - dy
/ 2.0;
1505 bounds
->width
= fabs (x2
- x1
);
1506 bounds
->height
= fabs (y2
- y1
);
1507 switch (start_cap
) {
1508 case PenLineCapSquare
:
1509 bounds
->width
+= (dx
+ dy
) / 2.0;
1510 bounds
->height
+= (dx
+ dy
) / 2.0;
1512 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1513 case PenLineCapRound
:
1514 bounds
->width
+= thickness
/ 2.0;
1515 bounds
->height
+= thickness
/ 2.0;
1517 default: //PenLineCapFlat
1518 bounds
->width
+= dx
/2.0;
1519 bounds
->height
+= dy
/2.0;
1522 case PenLineCapSquare
:
1523 bounds
->width
+= (dx
+ dy
) / 2.0;
1524 bounds
->height
+= (dx
+ dy
) / 2.0;
1526 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1527 case PenLineCapRound
:
1528 bounds
->width
+= thickness
/ 2.0;
1529 bounds
->height
+= thickness
/ 2.0;
1531 default: //PenLineCapFlat
1532 bounds
->width
+= dx
/2.0;
1533 bounds
->height
+= dy
/2.0;
1541 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1543 path
= moon_path_renew (path
, MOON_PATH_MOVE_TO_LENGTH
+ MOON_PATH_LINE_TO_LENGTH
);
1545 double x1
= GetX1 ();
1546 double y1
= GetY1 ();
1547 double x2
= GetX2 ();
1548 double y2
= GetY2 ();
1550 moon_move_to (path
, x1
, y1
);
1551 moon_line_to (path
, x2
, y2
);
1555 Line::ComputeShapeBounds (bool logical
)
1557 Rect shape_bounds
= Rect ();
1561 thickness
= GetStrokeThickness ();
1565 PenLineCap start_cap
, end_cap
;
1567 start_cap
= GetStrokeStartLineCap ();
1568 end_cap
= GetStrokeEndLineCap ();
1570 start_cap
= end_cap
= PenLineCapFlat
;
1572 if (thickness
<= 0.0 && !logical
)
1573 return shape_bounds
;
1575 double x1
= GetX1 ();
1576 double y1
= GetY1 ();
1577 double x2
= GetX2 ();
1578 double y2
= GetY2 ();
1580 calc_line_bounds (x1
, x2
, y1
, y2
, thickness
, start_cap
, end_cap
, &shape_bounds
);
1582 return shape_bounds
;
1586 Line::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1588 if (args
->GetProperty ()->GetOwnerType() != Type::LINE
) {
1589 Shape::OnPropertyChanged (args
, error
);
1593 if (args
->GetId () == Line::X1Property
||
1594 args
->GetId () == Line::X2Property
||
1595 args
->GetId () == Line::Y1Property
||
1596 args
->GetId () == Line::Y2Property
) {
1597 InvalidateNaturalBounds ();
1600 NotifyListenersOfPropertyChange (args
, error
);
1607 // * the internal path must be rebuilt when
1608 // - Polygon::PointsProperty is changed
1609 // - Shape::StretchProperty is changed
1611 // * bounds calculation is based on
1612 // - Polygon::PointsProperty
1613 // - Shape::StretchProperty
1614 // - Shape::StrokeThickness
1619 SetObjectType (Type::POLYGON
);
1622 // The Polygon shape can be drawn while ignoring properties:
1623 // * Shape::StrokeStartLineCap
1624 // * Shape::StrokeEndLineCap
1626 Polygon::DrawShape (cairo_t
*cr
, bool do_op
)
1628 bool drawn
= Fill (cr
, do_op
);
1633 if (!SetupLine (cr
))
1636 SetupLineJoinMiter (cr
);
1643 // special case when a polygon has a single line in it (it's drawn longer than it should)
1644 // e.g. <Polygon Fill="#000000" Stroke="#FF00FF" StrokeThickness="8" Points="260,80 300,40" />
1646 polygon_extend_line (double *x1
, double *x2
, double *y1
, double *y2
, double thickness
)
1648 // not sure why it's a 5 ? afaik it's not related to the line length or any other property
1649 double t5
= thickness
* 5.0;
1650 double dx
= *x1
- *x2
;
1651 double dy
= *y1
- *y2
;
1654 t5
-= thickness
/ 2.0;
1662 } else if (dx
== 0.0) {
1663 t5
-= thickness
/ 2.0;
1672 double angle
= atan (dy
/ dx
);
1673 double ax
= fabs (sin (angle
) * t5
);
1681 double ay
= fabs (sin ((M_PI
/ 2.0) - angle
)) * t5
;
1693 Polygon::BuildPath ()
1695 PointCollection
*col
= GetPoints ();
1697 // the first point is a move to, resulting in an empty shape
1698 if (!col
|| (col
->GetCount() < 2)) {
1699 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1703 int i
, count
= col
->GetCount();
1704 GPtrArray
* points
= col
->Array();
1706 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1708 // 2 data per [move|line]_to + 1 for close path
1709 path
= moon_path_renew (path
, count
* 2 + 1);
1711 // special case, both the starting and ending points are 5 * thickness than the actual points
1713 double thickness
= GetStrokeThickness ();
1714 double x1
= ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->x
;
1715 double y1
= ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->y
;
1716 double x2
= ((Value
*)g_ptr_array_index(points
, 1))->AsPoint()->x
;
1717 double y2
= ((Value
*)g_ptr_array_index(points
, 1))->AsPoint()->y
;
1719 polygon_extend_line (&x1
, &x2
, &y1
, &y2
, thickness
);
1721 moon_move_to (path
, x1
, y1
);
1722 moon_line_to (path
, x2
, y2
);
1725 ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->x
,
1726 ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->y
);
1727 for (i
= 1; i
< count
; i
++)
1729 ((Value
*)g_ptr_array_index(points
, i
))->AsPoint()->x
,
1730 ((Value
*)g_ptr_array_index(points
, i
))->AsPoint()->y
);
1732 moon_close_path (path
);
1736 Polygon::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1738 if (args
->GetProperty ()->GetOwnerType() != Type::POLYGON
) {
1739 Shape::OnPropertyChanged (args
, error
);
1743 if (args
->GetId () == Polygon::PointsProperty
) {
1744 InvalidateNaturalBounds ();
1748 NotifyListenersOfPropertyChange (args
, error
);
1752 Polygon::OnCollectionChanged (Collection
*col
, CollectionChangedEventArgs
*args
)
1754 Shape::OnCollectionChanged (col
, args
);
1756 InvalidateNaturalBounds ();
1760 Polygon::OnCollectionItemChanged (Collection
*col
, DependencyObject
*obj
, PropertyChangedEventArgs
*args
)
1762 Shape::OnCollectionItemChanged (col
, obj
, args
);
1764 InvalidateNaturalBounds ();
1771 // * the internal path must be rebuilt when
1772 // - Polyline::PointsProperty is changed
1773 // - Shape::StretchProperty is changed
1775 // * bounds calculation is based on
1776 // - Polyline::PointsProperty
1777 // - Shape::StretchProperty
1778 // - Shape::StrokeThickness
1780 Polyline::Polyline ()
1782 SetObjectType (Type::POLYLINE
);
1785 // The Polyline shape can be drawn while ignoring NO properties
1787 Polyline::DrawShape (cairo_t
*cr
, bool do_op
)
1789 bool drawn
= Fill (cr
, do_op
);
1794 if (!SetupLine (cr
))
1796 SetupLineJoinMiter (cr
);
1798 // here we hack around #345888 where Cairo doesn't support different start and end linecaps
1799 PenLineCap start
= GetStrokeStartLineCap ();
1800 PenLineCap end
= GetStrokeEndLineCap ();
1801 PenLineCap dash
= GetStrokeDashCap ();
1803 if (do_op
&& ! (start
== end
&& start
== dash
)){
1804 // the previous fill, if needed, has preserved the path
1806 cairo_new_path (cr
);
1808 // since Draw may not have been called (e.g. no Fill) we must ensure the path was built
1809 if (!drawn
|| !path
|| (path
->cairo
.num_data
== 0))
1812 cairo_path_data_t
*data
= path
->cairo
.data
;
1813 int length
= path
->cairo
.num_data
;
1814 // single point polylines are not rendered
1815 if (length
>= MOON_PATH_MOVE_TO_LENGTH
+ MOON_PATH_LINE_TO_LENGTH
) {
1816 // draw line #1 with start cap
1817 if (start
!= PenLineCapFlat
) {
1818 line_draw_cap (cr
, this, start
, data
[1].point
.x
, data
[1].point
.y
, data
[3].point
.x
, data
[3].point
.y
);
1820 // draw last line with end cap
1821 if (end
!= PenLineCapFlat
) {
1822 line_draw_cap (cr
, this, end
, data
[length
-1].point
.x
, data
[length
-1].point
.y
, data
[length
-3].point
.x
, data
[length
-3].point
.y
);
1826 cairo_set_line_cap (cr
, convert_line_cap (dash
));
1834 Polyline::BuildPath ()
1836 PointCollection
*col
= GetPoints ();
1838 // the first point is a move to, resulting in an empty shape
1839 if (!col
|| (col
->GetCount() < 2)) {
1840 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1844 int i
, count
= col
->GetCount();
1845 GPtrArray
*points
= col
->Array();
1847 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1849 // 2 data per [move|line]_to
1850 path
= moon_path_renew (path
, count
* 2);
1853 ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->x
,
1854 ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->y
);
1856 for (i
= 1; i
< count
; i
++)
1858 ((Value
*)g_ptr_array_index(points
, i
))->AsPoint()->x
,
1859 ((Value
*)g_ptr_array_index(points
, i
))->AsPoint()->y
);
1863 Polyline::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1865 if (args
->GetProperty ()->GetOwnerType() != Type::POLYLINE
) {
1866 Shape::OnPropertyChanged (args
, error
);
1870 if (args
->GetId () == Polyline::PointsProperty
) {
1871 InvalidateNaturalBounds ();
1875 NotifyListenersOfPropertyChange (args
, error
);
1879 Polyline::OnCollectionChanged (Collection
*col
, CollectionChangedEventArgs
*args
)
1881 if (col
!= GetPoints ()) {
1882 Shape::OnCollectionChanged (col
, args
);
1886 InvalidateNaturalBounds ();
1890 Polyline::OnCollectionItemChanged (Collection
*col
, DependencyObject
*obj
, PropertyChangedEventArgs
*args
)
1892 Shape::OnCollectionItemChanged (col
, obj
, args
);
1894 InvalidateNaturalBounds ();
1901 Path::SetupLine (cairo_t
* cr
)
1903 // we cannot use the thickness==0 optimization (like Shape::SetupLine provides)
1904 // since we'll be using cairo to compute the path's bounds later
1905 // see bug #352188 for an example of what this breaks
1906 double thickness
= IsDegenerate () ? 1.0 : GetStrokeThickness ();
1907 cairo_set_line_width (cr
, thickness
);
1908 return SetupDashes (cr
, thickness
);
1911 // The Polygon shape can be drawn while ignoring properties:
1913 // FIXME: actually it depends on the geometry, another level of optimization awaits ;-)
1914 // e.g. close geometries don't need to setup line caps,
1916 // line join/miter don't applies to curve, like EllipseGeometry
1918 Path::DrawShape (cairo_t
*cr
, bool do_op
)
1920 bool drawn
= Shape::Fill (cr
, do_op
);
1923 if (!SetupLine (cr
))
1924 return drawn
; // return if we have a path in the cairo_t
1926 SetupLineJoinMiter (cr
);
1937 Path::GetFillRule ()
1941 if (!(geometry
= GetData ()))
1942 return Shape::GetFillRule ();
1944 return geometry
->GetFillRule ();
1948 Path::ComputeShapeBounds (bool logical
, cairo_matrix_t
*matrix
)
1950 Rect shape_bounds
= Rect ();
1953 if (!(geometry
= GetData ())) {
1954 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1955 return shape_bounds
;
1959 return geometry
->GetBounds ();
1961 double thickness
= !IsStroked () ? 0.0 : GetStrokeThickness ();
1963 cairo_t
*cr
= measuring_context_create ();
1964 cairo_set_line_width (cr
, thickness
);
1966 if (thickness
> 0.0) {
1967 //FIXME: still not 100% precise since it could be different from the end cap
1968 PenLineCap cap
= GetStrokeStartLineCap ();
1969 if (cap
== PenLineCapFlat
)
1970 cap
= GetStrokeEndLineCap ();
1971 cairo_set_line_cap (cr
, convert_line_cap (cap
));
1975 cairo_set_matrix (cr
, matrix
);
1976 geometry
->Draw (cr
);
1978 cairo_identity_matrix (cr
);
1980 double x1
, y1
, x2
, y2
;
1982 if (thickness
> 0) {
1983 cairo_stroke_extents (cr
, &x1
, &y1
, &x2
, &y2
);
1985 cairo_fill_extents (cr
, &x1
, &y1
, &x2
, &y2
);
1988 shape_bounds
= Rect (MIN (x1
, x2
), MIN (y1
, y2
), fabs (x2
- x1
), fabs (y2
- y1
));
1990 measuring_context_destroy (cr
);
1992 return shape_bounds
;
1996 Path::Draw (cairo_t
*cr
)
1998 cairo_new_path (cr
);
2002 if (!(geometry
= GetData ()))
2006 cairo_transform (cr
, &stretch_transform
);
2007 geometry
->Draw (cr
);
2012 Path::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2014 if (args
->GetProperty ()->GetOwnerType() != Type::PATH
) {
2015 Shape::OnPropertyChanged (args
, error
);
2019 InvalidateNaturalBounds ();
2021 NotifyListenersOfPropertyChange (args
, error
);
2025 Path::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2027 if (prop
&& prop
->GetId () == Path::DataProperty
) {
2028 InvalidateNaturalBounds ();
2031 Shape::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2035 * Right now implementing Path::ComputeLargestRectangle doesn't seems like a good idea. That would require
2036 * - checking the path for curves (and either flatten it or return an empty Rect)
2037 * - checking for polygon simplicity (finding intersections)
2038 * - checking for a convex polygon (if concave we can turn it into several convex or return an empty Rect)
2039 * - find the largest rectangle inside the (or each) convex polygon(s)
2040 * http://cgm.cs.mcgill.ca/~athens/cs507/Projects/2003/DanielSud/complete.html