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
);
408 RenderLayoutClip (cr
);
412 // Returns TRUE if surface is a good candidate for caching.
413 // We accept a little bit of scaling.
416 Shape::IsCandidateForCaching (void)
425 * these fill and stroke short circuits are attempts be smart
426 * about determining when the cost of caching is greater than
427 * the cost of simply drawing all the choices here should really
428 * have best and worst case perf tests associated with them
429 * but they don't right now
431 bool gradient_fill
= false;
432 /* XXX FIXME this should be a property on the shape */
433 bool simple
= Is (Type::RECTANGLE
) || Is (Type::ELLIPSE
);
436 if (fill
->IsAnimating ())
439 gradient_fill
|= fill
->Is (Type::GRADIENTBRUSH
);
443 if (stroke
&& stroke
->IsAnimating ())
446 if (simple
&& !gradient_fill
)
449 // This is not 100% correct check -- the actual surface size might be
450 // a tiny little bit larger. It's not a problem though if we go few
451 // bytes above the cache limit.
452 if (!GetSurface ()->VerifyWithCacheSizeCounter ((int) bounds
.width
, (int) bounds
.height
))
455 // one last line of defense, lets not cache things
456 // much larger than the screen.
457 if (bounds
.width
* bounds
.height
> 4000000)
464 // This routine is useful for Shape derivatives: it can be used
465 // to either get the bounding box from cairo, or to paint it
468 Shape::DoDraw (cairo_t
*cr
, bool do_op
)
472 // quick out if, when building the path, we detected an empty shape
476 if (do_op
&& cached_surface
== NULL
&& IsCandidateForCaching ()) {
477 Rect cache_extents
= bounds
.RoundOut ();
478 cairo_t
*cached_cr
= NULL
;
480 // g_warning ("bounds (%f, %f), extents (%f, %f), cache_extents (%f, %f)",
481 // bounds.width, bounds.height,
482 // extents.width, extents.height,
483 // cache_extents.width, cache_extents.height);
485 cached_surface
= image_brush_create_similar (cr
, (int) cache_extents
.width
, (int) cache_extents
.height
);
486 cairo_surface_set_device_offset (cached_surface
, -cache_extents
.x
, -cache_extents
.y
);
487 cached_cr
= cairo_create (cached_surface
);
489 cairo_set_matrix (cached_cr
, &absolute_xform
);
491 ret
= DrawShape (cached_cr
, do_op
);
493 cairo_destroy (cached_cr
);
495 // Increase our cache size
496 cached_size
= GetSurface ()->AddToCacheSizeCounter ((int) cache_extents
.width
, (int) cache_extents
.height
);
499 if (do_op
&& cached_surface
) {
500 cairo_pattern_t
*cached_pattern
= NULL
;
502 cached_pattern
= cairo_pattern_create_for_surface (cached_surface
);
503 cairo_set_matrix (cr
, &absolute_xform
);
507 cairo_identity_matrix (cr
);
508 cairo_set_source (cr
, cached_pattern
);
509 cairo_pattern_destroy (cached_pattern
);
512 cairo_set_matrix (cr
, &absolute_xform
);
516 if (DrawShape (cr
, do_op
))
526 Shape::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
529 DoDraw (cr
, true && !path_only
);
534 Shape::ShiftPosition (Point p
)
536 double dx
= bounds
.x
- p
.x
;
537 double dy
= bounds
.y
- p
.y
;
539 // FIXME this is much less than ideal but we must invalidate the surface cache
540 // if the shift is not an integer otherwise we can potentially drow outside our
541 // rounded out bounds.
542 if (cached_surface
&& (dx
== trunc(dx
)) && (dy
== trunc(dy
))) {
543 cairo_surface_set_device_offset (cached_surface
, trunc (-p
.x
), trunc (-p
.y
));
545 InvalidateSurfaceCache ();
548 FrameworkElement::ShiftPosition (p
);
553 Shape::ComputeActualSize ()
555 Size desired
= FrameworkElement::ComputeActualSize ();
556 Rect shape_bounds
= GetNaturalBounds ();
559 UIElement
*parent
= GetVisualParent ();
561 if (parent
&& !parent
->Is (Type::CANVAS
))
562 if (LayoutInformation::GetPreviousConstraint (this) || LayoutInformation::GetLayoutSlot (this))
568 if (shape_bounds
.width
<= 0 && shape_bounds
.height
<= 0)
571 if (GetStretch () == StretchNone
&& shape_bounds
.width
> 0 && shape_bounds
.height
> 0)
572 return Size (shape_bounds
.width
, shape_bounds
.height
);
574 /* don't stretch to infinite size */
575 if (isinf (desired
.width
))
576 desired
.width
= shape_bounds
.width
;
577 if (isinf (desired
.height
))
578 desired
.height
= shape_bounds
.height
;
580 /* compute the scaling */
581 if (shape_bounds
.width
> 0)
582 sx
= desired
.width
/ shape_bounds
.width
;
583 if (shape_bounds
.height
> 0)
584 sy
= desired
.height
/ shape_bounds
.height
;
586 switch (GetStretch ()) {
588 sx
= sy
= MIN (sx
, sy
);
590 case StretchUniformToFill
:
591 sx
= sy
= MAX (sx
, sy
);
597 desired
= desired
.Min (shape_bounds
.width
* sx
, shape_bounds
.height
* sy
);
603 Shape::MeasureOverride (Size availableSize
)
605 Size desired
= availableSize
;
606 Rect shape_bounds
= GetNaturalBounds ();
610 if (GetStretch () == StretchNone
)
611 return Size (shape_bounds
.x
+ shape_bounds
.width
, shape_bounds
.y
+ shape_bounds
.height
);
613 if (Is (Type::RECTANGLE
) || Is (Type::ELLIPSE
)) {
614 desired
= Size (0,0);
617 /* don't stretch to infinite size */
618 if (isinf (availableSize
.width
))
619 desired
.width
= shape_bounds
.width
;
620 if (isinf (availableSize
.height
))
621 desired
.height
= shape_bounds
.height
;
623 /* compute the scaling */
624 if (shape_bounds
.width
> 0)
625 sx
= desired
.width
/ shape_bounds
.width
;
626 if (shape_bounds
.height
> 0)
627 sy
= desired
.height
/ shape_bounds
.height
;
629 /* don't use infinite dimensions as constraints */
630 if (isinf (availableSize
.width
))
632 if (isinf (availableSize
.height
))
635 switch (GetStretch ()) {
637 sx
= sy
= MIN (sx
, sy
);
639 case StretchUniformToFill
:
640 sx
= sy
= MAX (sx
, sy
);
643 if (isinf (availableSize
.width
))
645 if (isinf (availableSize
.height
))
652 desired
= Size (shape_bounds
.width
* sx
, shape_bounds
.height
* sy
);
658 Shape::ArrangeOverride (Size finalSize
)
660 Size arranged
= finalSize
;
664 Rect shape_bounds
= GetNaturalBounds ();
666 InvalidateStretch ();
668 if (GetStretch () == StretchNone
)
669 return arranged
.Max (Size (shape_bounds
.x
+ shape_bounds
.width
, shape_bounds
.y
+ shape_bounds
.height
));
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
);
699 Shape::TransformBounds (cairo_matrix_t
*old
, cairo_matrix_t
*current
)
701 InvalidateSurfaceCache ();
702 bounds_with_children
= bounds
= IntersectBoundsWithClipPath (GetStretchExtents (), false).Transform (current
);
706 Shape::ComputeBounds ()
708 bounds_with_children
= bounds
= IntersectBoundsWithClipPath (GetStretchExtents (), false).Transform (&absolute_xform
);
709 //printf ("%f,%f,%f,%f\n", bounds.x, bounds.y, bounds.width, bounds.height);
713 Shape::ComputeShapeBounds (bool logical
, cairo_matrix_t
*matrix
)
715 double thickness
= (logical
|| !IsStroked ()) ? 0.0 : GetStrokeThickness ();
716 if (Is (Type::RECTANGLE
) || Is (Type::ELLIPSE
)) {
717 return Rect (0,0,1.0,1.0);
720 if (!path
|| (path
->cairo
.num_data
== 0))
726 cairo_t
*cr
= measuring_context_create ();
728 cairo_set_matrix (cr
, matrix
);
730 cairo_set_line_width (cr
, thickness
);
732 if (thickness
> 0.0) {
733 //FIXME: still not 100% precise since it could be different from the end cap
734 PenLineCap cap
= GetStrokeStartLineCap ();
735 if (cap
== PenLineCapFlat
)
736 cap
= GetStrokeEndLineCap ();
737 cairo_set_line_cap (cr
, convert_line_cap (cap
));
740 cairo_append_path (cr
, &path
->cairo
);
742 cairo_identity_matrix (cr
);
744 double x1
, y1
, x2
, y2
;
747 cairo_path_extents (cr
, &x1
, &y1
, &x2
, &y2
);
748 } else if (thickness
> 0) {
749 cairo_stroke_extents (cr
, &x1
, &y1
, &x2
, &y2
);
751 cairo_fill_extents (cr
, &x1
, &y1
, &x2
, &y2
);
754 Rect bounds
= Rect (MIN (x1
, x2
), MIN (y1
, y2
), fabs (x2
- x1
), fabs (y2
- y1
));
756 measuring_context_destroy (cr
);
762 Shape::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
764 *height
= GetStretchExtents ().height
;
765 *width
= GetStretchExtents ().width
;
769 Shape::InsideObject (cairo_t
*cr
, double x
, double y
)
773 if (!InsideLayoutClip (x
, y
))
776 if (!InsideClip (cr
, x
, y
))
779 TransformPoint (&x
, &y
);
780 if (!GetStretchExtents ().PointInside (x
, y
))
786 // don't check in_stroke without a stroke or in_fill without a fill (even if it can be filled)
787 if (fill
&& CanFill ())
788 ret
|= cairo_in_fill (cr
, x
, y
);
790 ret
|= cairo_in_stroke (cr
, x
, y
);
799 Shape::CacheInvalidateHint (void)
801 // Also kills the surface cache
802 InvalidatePathCache ();
806 Shape::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
808 if (args
->GetProperty ()->GetOwnerType() != Type::SHAPE
) {
809 if ((args
->GetId () == FrameworkElement::HeightProperty
)
810 || (args
->GetId () == FrameworkElement::WidthProperty
))
811 InvalidateStretch ();
813 // CacheInvalidateHint should handle visibility changes in
814 // DirtyRenderVisibility
816 FrameworkElement::OnPropertyChanged (args
, error
);
820 if (args
->GetId () == Shape::StretchProperty
) {
821 InvalidateMeasure ();
822 InvalidateStretch ();
824 else if (args
->GetId () == Shape::StrokeProperty
) {
825 Brush
*new_stroke
= args
->GetNewValue() ? args
->GetNewValue()->AsBrush () : NULL
;
827 if (!stroke
|| !new_stroke
) {
828 // If the stroke changes from null to
829 // <something> or <something> to null, then
830 // some shapes need to reclaculate the offset
831 // (based on stroke thickness) to start
833 InvalidateStrokeBounds ();
835 InvalidateSurfaceCache ();
838 } else if (args
->GetId () == Shape::FillProperty
) {
839 Brush
*new_fill
= args
->GetNewValue() ? args
->GetNewValue()->AsBrush () : NULL
;
841 if (!fill
|| !new_fill
) {
842 InvalidateFillBounds ();
844 InvalidateSurfaceCache ();
846 fill
= args
->GetNewValue() ? args
->GetNewValue()->AsBrush() : NULL
;
847 } else if (args
->GetId () == Shape::StrokeThicknessProperty
) {
848 // do we invalidate the path here for Type::RECT and Type::ELLIPSE
849 // in case they degenerate? Or do we need it for line caps too
850 InvalidateStrokeBounds ();
851 } else if (args
->GetId () == Shape::StrokeDashCapProperty
852 || args
->GetId () == Shape::StrokeEndLineCapProperty
853 || args
->GetId () == Shape::StrokeLineJoinProperty
854 || args
->GetId () == Shape::StrokeMiterLimitProperty
855 || args
->GetId () == Shape::StrokeStartLineCapProperty
) {
856 InvalidateStrokeBounds ();
861 NotifyListenersOfPropertyChange (args
, error
);
865 Shape::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
867 if (prop
&& (prop
->GetId () == Shape::FillProperty
|| prop
->GetId () == Shape::StrokeProperty
)) {
869 InvalidateSurfaceCache ();
872 FrameworkElement::OnSubPropertyChanged (prop
, obj
, subobj_args
);
876 Shape::InvalidateStretch ()
878 extents
= Rect (0, 0, -INFINITY
, -INFINITY
);
879 cairo_matrix_init_identity (&stretch_transform
);
880 InvalidatePathCache ();
884 Shape::GetStretchExtents ()
886 if (extents
.IsEmpty ())
887 extents
= ComputeStretchBounds ();
893 Shape::InvalidateStrokeBounds ()
895 InvalidateNaturalBounds ();
899 Shape::InvalidateFillBounds ()
901 InvalidateNaturalBounds ();
905 Shape::InvalidateNaturalBounds ()
907 natural_bounds
= Rect (0, 0, -INFINITY
, -INFINITY
);
908 InvalidateStretch ();
912 Shape::GetNaturalBounds ()
914 if (natural_bounds
.IsEmpty ())
915 natural_bounds
= ComputeShapeBounds (false, NULL
);
917 return natural_bounds
;
921 Shape::InvalidatePathCache (bool free
)
923 //SetShapeFlags (UIElement::SHAPE_NORMAL);
926 moon_path_destroy (path
);
929 moon_path_clear (path
);
933 // we always pass true here because in some cases
934 // while the bounds may not have change the rendering
937 //InvalidateMeasure ();
938 //InvalidateArrange ();
939 InvalidateSurfaceCache ();
943 Shape::InvalidateSurfaceCache (void)
945 if (cached_surface
) {
946 cairo_surface_destroy (cached_surface
);
948 GetSurface ()->RemoveFromCacheSizeCounter (cached_size
);
949 cached_surface
= NULL
;
955 Shape::CreateDefaultStretch (DependencyObject
*instance
, DependencyProperty
*property
)
957 if (instance
->Is (Type::RECTANGLE
) || instance
->Is (Type::ELLIPSE
))
958 return new Value (StretchFill
);
960 return new Value (StretchNone
);
969 SetObjectType (Type::ELLIPSE
);
973 * Ellipses (like Rectangles) are special and they don't need to participate
974 * in the other stretch logic
977 Ellipse::ComputeStretchBounds ()
979 return ComputeShapeBounds (false);
983 Ellipse::ComputeShapeBounds (bool logical
)
985 Rect rect
= Rect (0, 0, GetActualWidth (), GetActualHeight ());
986 SetShapeFlags (UIElement::SHAPE_NORMAL
);
987 double t
= GetStrokeThickness ();
989 if (rect
.width
< 0.0 || rect
.height
< 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
990 SetShapeFlags (UIElement::SHAPE_EMPTY
);
994 if (GetVisualParent () && GetVisualParent ()->Is (Type::CANVAS
)) {
995 if (isnan (GetWidth ()) != isnan (GetHeight ())) {
996 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1001 switch (GetStretch ()) {
1003 rect
.width
= rect
.height
= 0.0;
1005 case StretchUniform
:
1006 rect
.width
= rect
.height
= (rect
.width
< rect
.height
) ? rect
.width
: rect
.height
;
1008 case StretchUniformToFill
:
1009 rect
.width
= rect
.height
= (rect
.width
> rect
.height
) ? rect
.width
: rect
.height
;
1012 /* nothing needed here. the assignment of w/h above
1013 is correct for this case. */
1017 if (rect
.width
<= t
|| rect
.height
<= t
){
1018 rect
.width
= MAX (rect
.width
, t
+ t
* 0.001);
1019 rect
.height
= MAX (rect
.height
, t
+ t
* 0.001);
1020 SetShapeFlags (UIElement::SHAPE_DEGENERATE
);
1022 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1027 // * Shape::StrokeStartLineCap
1028 // * Shape::StrokeEndLineCap
1029 // * Shape::StrokeLineJoin
1030 // * Shape::StrokeMiterLimit
1032 Ellipse::DrawShape (cairo_t
*cr
, bool do_op
)
1034 bool drawn
= Fill (cr
, do_op
);
1038 if (!SetupLine (cr
))
1049 Ellipse::BuildPath ()
1051 Stretch stretch
= GetStretch ();
1052 double t
= IsStroked () ? GetStrokeThickness () : 0.0;
1053 Rect rect
= Rect (0.0, 0.0, GetActualWidth (), GetActualHeight ());
1055 if (rect
.width
< 0.0 || rect
.height
< 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1056 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1061 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1065 rect
.width
= rect
.height
= 0.0;
1067 case StretchUniform
:
1068 rect
.width
= rect
.height
= (rect
.width
< rect
.height
) ? rect
.width
: rect
.height
;
1070 case StretchUniformToFill
:
1071 rect
.width
= rect
.height
= (rect
.width
> rect
.height
) ? rect
.width
: rect
.height
;
1074 /* nothing needed here. the assignment of w/h above
1075 is correct for this case. */
1079 if (rect
.width
<= t
|| rect
.height
<= t
){
1080 rect
.width
= MAX (rect
.width
, t
+ t
* 0.001);
1081 rect
.height
= MAX (rect
.height
, t
+ t
* 0.001);
1082 SetShapeFlags (UIElement::SHAPE_DEGENERATE
);
1084 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1086 rect
= rect
.GrowBy ( -t
/2, -t
/2);
1088 path
= moon_path_renew (path
, MOON_PATH_ELLIPSE_LENGTH
);
1089 moon_ellipse (path
, rect
.x
, rect
.y
, rect
.width
, rect
.height
);
1093 Ellipse::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1096 DependencyProperty *prop = args->GetProperty ();
1098 if ((prop->GetId () == Shape::StrokeThicknessProperty) || (prop->GetId () == Shape::StretchProperty) ||
1099 (prop->GetId () == FrameworkElement::WidthProperty) || (prop->GetId () == FrameworkElement::HeightProperty)) {
1100 // FIXME why are we building the path here?
1102 InvalidateSurfaceCache ();
1106 // Ellipse has no property of it's own
1107 Shape::OnPropertyChanged (args
, error
);
1114 Rectangle::Rectangle ()
1116 SetObjectType (Type::RECTANGLE
);
1120 * Rectangles (like Ellipses) are special and they don't need to participate
1121 * in the other stretch logic
1124 Rectangle::ComputeStretchBounds ()
1126 Rect shape_bounds
= ComputeShapeBounds (false);
1127 return ComputeShapeBounds (false);
1131 Rectangle::ComputeShapeBounds (bool logical
)
1133 Rect rect
= Rect (0, 0, GetActualWidth (), GetActualHeight ());
1134 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1136 if (rect
.width
< 0.0 || rect
.height
< 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1137 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1141 if (GetVisualParent () && GetVisualParent ()->Is (Type::CANVAS
)) {
1142 if (isnan (GetWidth ()) != isnan (GetHeight ())) {
1143 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1148 double t
= IsStroked () ? GetStrokeThickness () : 0.0;
1149 switch (GetStretch ()) {
1151 rect
.width
= rect
.height
= 0.0;
1153 case StretchUniform
:
1154 rect
.width
= rect
.height
= MIN (rect
.width
, rect
.height
);
1156 case StretchUniformToFill
:
1157 // this gets an rectangle larger than it's dimension, relative
1158 // scaling is ok but we need Shape::Draw to clip to it's original size
1159 rect
.width
= rect
.height
= MAX (rect
.width
, rect
.height
);
1162 /* nothing needed here. the assignment of w/h above
1163 is correct for this case. */
1167 if (rect
.width
== 0)
1169 if (rect
.height
== 0)
1172 if (t
>= rect
.width
|| t
>= rect
.height
) {
1173 SetShapeFlags (UIElement::SHAPE_DEGENERATE
);
1174 rect
= rect
.GrowBy (t
* .5005, t
* .5005);
1176 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1183 Rectangle::GetCoverageBounds ()
1185 Brush
*fill
= GetFill ();
1187 if (fill
!= NULL
&& fill
->IsOpaque()) {
1188 /* make it a little easier - only consider the rectangle inside the corner radii.
1189 we're also a little more conservative than we need to be, regarding stroke
1191 double xr
= (GetRadiusX () + GetStrokeThickness () / 2);
1192 double yr
= (GetRadiusY () + GetStrokeThickness () / 2);
1194 return bounds
.GrowBy (-xr
, -yr
).RoundIn ();
1201 // The Rectangle shape can be drawn while ignoring properties:
1202 // * Shape::StrokeStartLineCap
1203 // * Shape::StrokeEndLineCap
1204 // * Shape::StrokeLineJoin [for rounded-corner rectangles only]
1205 // * Shape::StrokeMiterLimit [for rounded-corner rectangles only]
1207 Rectangle::DrawShape (cairo_t
*cr
, bool do_op
)
1209 bool drawn
= Fill (cr
, do_op
);
1214 if (!SetupLine (cr
))
1220 SetupLineJoinMiter (cr
);
1222 // Draw if the path wasn't drawn by the Fill call
1231 * - a Width="0" or a Height="0" can be rendered differently from not specifying Width or Height
1232 * - if a rectangle has only a Width or only a Height it is NEVER rendered
1235 Rectangle::BuildPath ()
1237 Stretch stretch
= GetStretch ();
1238 double t
= IsStroked () ? GetStrokeThickness () : 0.0;
1240 // nothing is drawn (nor filled) if no StrokeThickness="0"
1241 // unless both Width and Height are specified or when no streching is required
1242 Rect rect
= Rect (0, 0, GetActualWidth (), GetActualHeight ());
1244 double radius_x
= GetRadiusX ();
1245 double radius_y
= GetRadiusY ();
1249 rect
.width
= rect
.height
= 0;
1251 case StretchUniform
:
1252 rect
.width
= rect
.height
= MIN (rect
.width
, rect
.height
);
1254 case StretchUniformToFill
:
1255 // this gets an rectangle larger than it's dimension, relative
1256 // scaling is ok but we need Shape::Draw to clip to it's original size
1257 rect
.width
= rect
.height
= MAX (rect
.width
, rect
.height
);
1260 /* nothing needed here. the assignment of w/h above
1261 is correct for this case. */
1265 if (rect
.width
== 0)
1267 if (rect
.height
== 0)
1270 if (t
>= rect
.width
|| t
>= rect
.height
) {
1271 rect
= rect
.GrowBy (t
* 0.001, t
* 0.001);
1272 SetShapeFlags (UIElement::SHAPE_DEGENERATE
);
1274 rect
= rect
.GrowBy (-t
* 0.5, -t
* 0.5);
1275 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1278 path
= moon_path_renew (path
, MOON_PATH_ROUNDED_RECTANGLE_LENGTH
);
1279 moon_rounded_rectangle (path
, rect
.x
, rect
.y
, rect
.width
, rect
.height
, radius_x
, radius_y
);
1283 Rectangle::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1285 if (args
->GetProperty ()->GetOwnerType() != Type::RECTANGLE
) {
1286 Shape::OnPropertyChanged (args
, error
);
1290 if ((args
->GetId () == Rectangle::RadiusXProperty
) || (args
->GetId () == Rectangle::RadiusYProperty
)) {
1291 InvalidateMeasure ();
1292 InvalidatePathCache ();
1296 NotifyListenersOfPropertyChange (args
, error
);
1303 // * the internal path must be rebuilt when
1304 // - Line::X1Property, Line::Y1Property, Line::X2Property or Line::Y2Property is changed
1306 // * bounds calculation is based on
1307 // - Line::X1Property, Line::Y1Property, Line::X2Property and Line::Y2Property
1308 // - Shape::StrokeThickness
1311 #define LINECAP_SMALL_OFFSET 0.1
1313 //Draw the start cap. Shared with Polyline
1315 line_draw_cap (cairo_t
*cr
, Shape
* shape
, PenLineCap cap
, double x1
, double y1
, double x2
, double y2
)
1318 if (cap
== PenLineCapFlat
)
1322 cairo_transform (cr
, &(shape
->stretch_transform
));
1323 if (cap
== PenLineCapRound
) {
1324 cairo_move_to (cr
, x1
, y1
);
1325 cairo_line_to (cr
, x1
, y1
);
1327 cairo_set_line_cap (cr
, convert_line_cap (cap
));
1328 shape
->Stroke (cr
, true);
1336 sy1
= y1
+ LINECAP_SMALL_OFFSET
;
1338 sy1
= y1
- LINECAP_SMALL_OFFSET
;
1339 } else if (y1
== y2
) {
1343 sx1
= x1
+ LINECAP_SMALL_OFFSET
;
1345 sx1
= x1
- LINECAP_SMALL_OFFSET
;
1347 double m
= (y1
- y2
) / (x1
- x2
);
1349 sx1
= x1
+ LINECAP_SMALL_OFFSET
;
1351 sx1
= x1
- LINECAP_SMALL_OFFSET
;
1353 sy1
= m
* sx1
+ y1
- (m
* x1
);
1355 cairo_move_to (cr
, x1
, y1
);
1356 cairo_line_to (cr
, sx1
, sy1
);
1358 cairo_set_line_cap (cr
, convert_line_cap (cap
));
1359 shape
->Stroke (cr
, true);
1362 // The Line shape can be drawn while ignoring properties:
1363 // * Shape::StrokeLineJoin
1364 // * Shape::StrokeMiterLimit
1367 Line::DrawShape (cairo_t
*cr
, bool do_op
)
1369 // no need to clear path since none has been drawn to cairo
1373 if (!SetupLine (cr
))
1376 // here we hack around #345888 where Cairo doesn't support different start and end linecaps
1377 PenLineCap start
= GetStrokeStartLineCap ();
1378 PenLineCap end
= GetStrokeEndLineCap ();
1379 PenLineCap dash
= GetStrokeDashCap ();
1380 bool dashed
= false;
1381 DoubleCollection
*dashes
= GetStrokeDashArray ();
1383 if (dashes
&& (dashes
->GetCount() > 0))
1387 //if (do_op && !(start == end && start == dash)) {
1388 if (do_op
&& (start
!= end
|| (dashed
&& !(start
== end
&& start
== dash
)))) {
1389 double x1
= GetX1 ();
1390 double y1
= GetY1 ();
1391 double x2
= GetX2 ();
1392 double y2
= GetY2 ();
1394 // draw start and end line caps
1395 if (start
!= PenLineCapFlat
)
1396 line_draw_cap (cr
, this, start
, x1
, y1
, x2
, y2
);
1398 if (end
!= PenLineCapFlat
) {
1399 //don't draw the end cap if it's in an "off" segment
1400 double thickness
= GetStrokeThickness ();
1401 double offset
= GetStrokeDashOffset ();
1403 SetupDashes (cr
, thickness
, sqrt ((x2
-x1
)*(x2
-x1
) + (y2
-y1
)*(y2
-y1
)) + offset
* thickness
);
1404 line_draw_cap (cr
, this, end
, x2
, y2
, x1
, y1
);
1408 cairo_set_line_cap (cr
, convert_line_cap (dash
));
1410 cairo_set_line_cap (cr
, convert_line_cap (start
));
1418 calc_line_bounds (double x1
, double x2
, double y1
, double y2
, double thickness
, PenLineCap start_cap
, PenLineCap end_cap
, Rect
* bounds
)
1421 bounds
->x
= x1
- thickness
/ 2.0;
1422 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);
1423 bounds
->width
= thickness
;
1424 bounds
->height
= fabs (y2
- y1
) + (start_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0) + (end_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0);
1425 } else if (y1
== y2
) {
1426 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);
1427 bounds
->y
= y1
- thickness
/ 2.0;
1428 bounds
->width
= fabs (x2
- x1
) + (start_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0) + (end_cap
!= PenLineCapFlat
? thickness
/ 2.0 : 0.0);
1429 bounds
->height
= thickness
;
1431 double m
= fabs ((y1
- y2
) / (x1
- x2
));
1433 double dx
= sin (atan (m
)) * thickness
;
1434 double dy
= cos (atan (m
)) * thickness
;
1436 double dx
= (m
> 1.0) ? thickness
: thickness
* m
;
1437 double dy
= (m
< 1.0) ? thickness
: thickness
/ m
;
1440 switch (start_cap
) {
1441 case PenLineCapSquare
:
1442 bounds
->x
= MIN (x1
, x2
) - (dx
+ dy
) / 2.0;
1444 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1445 case PenLineCapRound
:
1446 bounds
->x
= MIN (x1
, x2
) - thickness
/ 2.0;
1448 default: //PenLineCapFlat
1449 bounds
->x
= MIN (x1
, x2
) - dx
/ 2.0;
1453 case PenLineCapSquare
:
1454 bounds
->x
= MIN (x1
, x2
) - (dx
+ dy
) / 2.0;
1456 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1457 case PenLineCapRound
:
1458 bounds
->x
= MIN (x1
, x2
) - thickness
/ 2.0;
1460 default: //PenLineCapFlat
1461 bounds
->x
= MIN (x1
, x2
) - dx
/ 2.0;
1464 switch (start_cap
) {
1465 case PenLineCapSquare
:
1466 bounds
->y
= MIN (y1
, y2
) - (dx
+ dy
) / 2.0;
1468 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1469 case PenLineCapRound
:
1470 bounds
->y
= MIN (y1
, y2
) - thickness
/ 2.0;
1472 default: //PenLineCapFlat
1473 bounds
->y
= MIN (y1
, y2
) - dy
/ 2.0;
1477 case PenLineCapSquare
:
1478 bounds
->y
= MIN (y1
, y2
) - (dx
+ dy
) / 2.0;
1480 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1481 case PenLineCapRound
:
1482 bounds
->y
= MIN (y1
, y2
) - thickness
/ 2.0;
1484 default: //PenLineCapFlat
1485 bounds
->y
= MIN (y1
, y2
) - dy
/ 2.0;
1487 bounds
->width
= fabs (x2
- x1
);
1488 bounds
->height
= fabs (y2
- y1
);
1489 switch (start_cap
) {
1490 case PenLineCapSquare
:
1491 bounds
->width
+= (dx
+ dy
) / 2.0;
1492 bounds
->height
+= (dx
+ dy
) / 2.0;
1494 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1495 case PenLineCapRound
:
1496 bounds
->width
+= thickness
/ 2.0;
1497 bounds
->height
+= thickness
/ 2.0;
1499 default: //PenLineCapFlat
1500 bounds
->width
+= dx
/2.0;
1501 bounds
->height
+= dy
/2.0;
1504 case PenLineCapSquare
:
1505 bounds
->width
+= (dx
+ dy
) / 2.0;
1506 bounds
->height
+= (dx
+ dy
) / 2.0;
1508 case PenLineCapTriangle
: //FIXME, reverting to Round for now
1509 case PenLineCapRound
:
1510 bounds
->width
+= thickness
/ 2.0;
1511 bounds
->height
+= thickness
/ 2.0;
1513 default: //PenLineCapFlat
1514 bounds
->width
+= dx
/2.0;
1515 bounds
->height
+= dy
/2.0;
1523 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1525 path
= moon_path_renew (path
, MOON_PATH_MOVE_TO_LENGTH
+ MOON_PATH_LINE_TO_LENGTH
);
1527 double x1
= GetX1 ();
1528 double y1
= GetY1 ();
1529 double x2
= GetX2 ();
1530 double y2
= GetY2 ();
1532 moon_move_to (path
, x1
, y1
);
1533 moon_line_to (path
, x2
, y2
);
1537 Line::ComputeShapeBounds (bool logical
)
1539 Rect shape_bounds
= Rect ();
1543 thickness
= GetStrokeThickness ();
1547 PenLineCap start_cap
, end_cap
;
1549 start_cap
= GetStrokeStartLineCap ();
1550 end_cap
= GetStrokeEndLineCap ();
1552 start_cap
= end_cap
= PenLineCapFlat
;
1554 if (thickness
<= 0.0 && !logical
)
1555 return shape_bounds
;
1557 double x1
= GetX1 ();
1558 double y1
= GetY1 ();
1559 double x2
= GetX2 ();
1560 double y2
= GetY2 ();
1562 calc_line_bounds (x1
, x2
, y1
, y2
, thickness
, start_cap
, end_cap
, &shape_bounds
);
1564 return shape_bounds
;
1568 Line::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1570 if (args
->GetProperty ()->GetOwnerType() != Type::LINE
) {
1571 Shape::OnPropertyChanged (args
, error
);
1575 if (args
->GetId () == Line::X1Property
||
1576 args
->GetId () == Line::X2Property
||
1577 args
->GetId () == Line::Y1Property
||
1578 args
->GetId () == Line::Y2Property
) {
1579 InvalidateNaturalBounds ();
1582 NotifyListenersOfPropertyChange (args
, error
);
1589 // * the internal path must be rebuilt when
1590 // - Polygon::PointsProperty is changed
1591 // - Shape::StretchProperty is changed
1593 // * bounds calculation is based on
1594 // - Polygon::PointsProperty
1595 // - Shape::StretchProperty
1596 // - Shape::StrokeThickness
1601 SetObjectType (Type::POLYGON
);
1604 // The Polygon shape can be drawn while ignoring properties:
1605 // * Shape::StrokeStartLineCap
1606 // * Shape::StrokeEndLineCap
1608 Polygon::DrawShape (cairo_t
*cr
, bool do_op
)
1610 bool drawn
= Fill (cr
, do_op
);
1615 if (!SetupLine (cr
))
1618 SetupLineJoinMiter (cr
);
1625 // special case when a polygon has a single line in it (it's drawn longer than it should)
1626 // e.g. <Polygon Fill="#000000" Stroke="#FF00FF" StrokeThickness="8" Points="260,80 300,40" />
1628 polygon_extend_line (double *x1
, double *x2
, double *y1
, double *y2
, double thickness
)
1630 // not sure why it's a 5 ? afaik it's not related to the line length or any other property
1631 double t5
= thickness
* 5.0;
1632 double dx
= *x1
- *x2
;
1633 double dy
= *y1
- *y2
;
1636 t5
-= thickness
/ 2.0;
1644 } else if (dx
== 0.0) {
1645 t5
-= thickness
/ 2.0;
1654 double angle
= atan (dy
/ dx
);
1655 double ax
= fabs (sin (angle
) * t5
);
1663 double ay
= fabs (sin ((M_PI
/ 2.0) - angle
)) * t5
;
1675 Polygon::BuildPath ()
1677 PointCollection
*col
= GetPoints ();
1679 // the first point is a move to, resulting in an empty shape
1680 if (!col
|| (col
->GetCount() < 2)) {
1681 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1685 int i
, count
= col
->GetCount();
1686 GPtrArray
* points
= col
->Array();
1688 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1690 // 2 data per [move|line]_to + 1 for close path
1691 path
= moon_path_renew (path
, count
* 2 + 1);
1693 // special case, both the starting and ending points are 5 * thickness than the actual points
1695 double thickness
= GetStrokeThickness ();
1696 double x1
= ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->x
;
1697 double y1
= ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->y
;
1698 double x2
= ((Value
*)g_ptr_array_index(points
, 1))->AsPoint()->x
;
1699 double y2
= ((Value
*)g_ptr_array_index(points
, 1))->AsPoint()->y
;
1701 polygon_extend_line (&x1
, &x2
, &y1
, &y2
, thickness
);
1703 moon_move_to (path
, x1
, y1
);
1704 moon_line_to (path
, x2
, y2
);
1707 ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->x
,
1708 ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->y
);
1709 for (i
= 1; i
< count
; i
++)
1711 ((Value
*)g_ptr_array_index(points
, i
))->AsPoint()->x
,
1712 ((Value
*)g_ptr_array_index(points
, i
))->AsPoint()->y
);
1714 moon_close_path (path
);
1718 Polygon::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1720 if (args
->GetProperty ()->GetOwnerType() != Type::POLYGON
) {
1721 Shape::OnPropertyChanged (args
, error
);
1725 if (args
->GetId () == Polygon::PointsProperty
) {
1726 InvalidateNaturalBounds ();
1730 NotifyListenersOfPropertyChange (args
, error
);
1734 Polygon::OnCollectionChanged (Collection
*col
, CollectionChangedEventArgs
*args
)
1736 Shape::OnCollectionChanged (col
, args
);
1738 InvalidateNaturalBounds ();
1742 Polygon::OnCollectionItemChanged (Collection
*col
, DependencyObject
*obj
, PropertyChangedEventArgs
*args
)
1744 Shape::OnCollectionItemChanged (col
, obj
, args
);
1746 InvalidateNaturalBounds ();
1753 // * the internal path must be rebuilt when
1754 // - Polyline::PointsProperty is changed
1755 // - Shape::StretchProperty is changed
1757 // * bounds calculation is based on
1758 // - Polyline::PointsProperty
1759 // - Shape::StretchProperty
1760 // - Shape::StrokeThickness
1762 Polyline::Polyline ()
1764 SetObjectType (Type::POLYLINE
);
1767 // The Polyline shape can be drawn while ignoring NO properties
1769 Polyline::DrawShape (cairo_t
*cr
, bool do_op
)
1771 bool drawn
= Fill (cr
, do_op
);
1776 if (!SetupLine (cr
))
1778 SetupLineJoinMiter (cr
);
1780 // here we hack around #345888 where Cairo doesn't support different start and end linecaps
1781 PenLineCap start
= GetStrokeStartLineCap ();
1782 PenLineCap end
= GetStrokeEndLineCap ();
1783 PenLineCap dash
= GetStrokeDashCap ();
1785 if (do_op
&& ! (start
== end
&& start
== dash
)){
1786 // the previous fill, if needed, has preserved the path
1788 cairo_new_path (cr
);
1790 // since Draw may not have been called (e.g. no Fill) we must ensure the path was built
1791 if (!drawn
|| !path
|| (path
->cairo
.num_data
== 0))
1794 cairo_path_data_t
*data
= path
->cairo
.data
;
1795 int length
= path
->cairo
.num_data
;
1796 // single point polylines are not rendered
1797 if (length
>= MOON_PATH_MOVE_TO_LENGTH
+ MOON_PATH_LINE_TO_LENGTH
) {
1798 // draw line #1 with start cap
1799 if (start
!= PenLineCapFlat
) {
1800 line_draw_cap (cr
, this, start
, data
[1].point
.x
, data
[1].point
.y
, data
[3].point
.x
, data
[3].point
.y
);
1802 // draw last line with end cap
1803 if (end
!= PenLineCapFlat
) {
1804 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
);
1808 cairo_set_line_cap (cr
, convert_line_cap (dash
));
1816 Polyline::BuildPath ()
1818 PointCollection
*col
= GetPoints ();
1820 // the first point is a move to, resulting in an empty shape
1821 if (!col
|| (col
->GetCount() < 2)) {
1822 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1826 int i
, count
= col
->GetCount();
1827 GPtrArray
*points
= col
->Array();
1829 SetShapeFlags (UIElement::SHAPE_NORMAL
);
1831 // 2 data per [move|line]_to
1832 path
= moon_path_renew (path
, count
* 2);
1835 ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->x
,
1836 ((Value
*)g_ptr_array_index(points
, 0))->AsPoint()->y
);
1838 for (i
= 1; i
< count
; i
++)
1840 ((Value
*)g_ptr_array_index(points
, i
))->AsPoint()->x
,
1841 ((Value
*)g_ptr_array_index(points
, i
))->AsPoint()->y
);
1845 Polyline::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1847 if (args
->GetProperty ()->GetOwnerType() != Type::POLYLINE
) {
1848 Shape::OnPropertyChanged (args
, error
);
1852 if (args
->GetId () == Polyline::PointsProperty
) {
1853 InvalidateNaturalBounds ();
1857 NotifyListenersOfPropertyChange (args
, error
);
1861 Polyline::OnCollectionChanged (Collection
*col
, CollectionChangedEventArgs
*args
)
1863 if (col
!= GetPoints ()) {
1864 Shape::OnCollectionChanged (col
, args
);
1868 InvalidateNaturalBounds ();
1872 Polyline::OnCollectionItemChanged (Collection
*col
, DependencyObject
*obj
, PropertyChangedEventArgs
*args
)
1874 Shape::OnCollectionItemChanged (col
, obj
, args
);
1876 InvalidateNaturalBounds ();
1883 Path::SetupLine (cairo_t
* cr
)
1885 // we cannot use the thickness==0 optimization (like Shape::SetupLine provides)
1886 // since we'll be using cairo to compute the path's bounds later
1887 // see bug #352188 for an example of what this breaks
1888 double thickness
= IsDegenerate () ? 1.0 : GetStrokeThickness ();
1889 cairo_set_line_width (cr
, thickness
);
1890 return SetupDashes (cr
, thickness
);
1893 // The Polygon shape can be drawn while ignoring properties:
1895 // FIXME: actually it depends on the geometry, another level of optimization awaits ;-)
1896 // e.g. close geometries don't need to setup line caps,
1898 // line join/miter don't applies to curve, like EllipseGeometry
1900 Path::DrawShape (cairo_t
*cr
, bool do_op
)
1902 bool drawn
= Shape::Fill (cr
, do_op
);
1905 if (!SetupLine (cr
))
1906 return drawn
; // return if we have a path in the cairo_t
1908 SetupLineJoinMiter (cr
);
1919 Path::GetFillRule ()
1923 if (!(geometry
= GetData ()))
1924 return Shape::GetFillRule ();
1926 return geometry
->GetFillRule ();
1930 Path::ComputeShapeBounds (bool logical
, cairo_matrix_t
*matrix
)
1932 Rect shape_bounds
= Rect ();
1935 if (!(geometry
= GetData ())) {
1936 SetShapeFlags (UIElement::SHAPE_EMPTY
);
1937 return shape_bounds
;
1941 return geometry
->GetBounds ();
1943 double thickness
= !IsStroked () ? 0.0 : GetStrokeThickness ();
1945 cairo_t
*cr
= measuring_context_create ();
1946 cairo_set_line_width (cr
, thickness
);
1948 if (thickness
> 0.0) {
1949 //FIXME: still not 100% precise since it could be different from the end cap
1950 PenLineCap cap
= GetStrokeStartLineCap ();
1951 if (cap
== PenLineCapFlat
)
1952 cap
= GetStrokeEndLineCap ();
1953 cairo_set_line_cap (cr
, convert_line_cap (cap
));
1957 cairo_set_matrix (cr
, matrix
);
1958 geometry
->Draw (cr
);
1960 cairo_identity_matrix (cr
);
1962 double x1
, y1
, x2
, y2
;
1964 if (thickness
> 0) {
1965 cairo_stroke_extents (cr
, &x1
, &y1
, &x2
, &y2
);
1967 cairo_fill_extents (cr
, &x1
, &y1
, &x2
, &y2
);
1970 shape_bounds
= Rect (MIN (x1
, x2
), MIN (y1
, y2
), fabs (x2
- x1
), fabs (y2
- y1
));
1972 measuring_context_destroy (cr
);
1974 return shape_bounds
;
1978 Path::Draw (cairo_t
*cr
)
1980 cairo_new_path (cr
);
1984 if (!(geometry
= GetData ()))
1988 cairo_transform (cr
, &stretch_transform
);
1989 geometry
->Draw (cr
);
1994 Path::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
1996 if (args
->GetProperty ()->GetOwnerType() != Type::PATH
) {
1997 Shape::OnPropertyChanged (args
, error
);
2001 InvalidateNaturalBounds ();
2003 NotifyListenersOfPropertyChange (args
, error
);
2007 Path::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2009 if (prop
&& prop
->GetId () == Path::DataProperty
) {
2010 InvalidateNaturalBounds ();
2013 Shape::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2017 * Right now implementing Path::ComputeLargestRectangle doesn't seems like a good idea. That would require
2018 * - checking the path for curves (and either flatten it or return an empty Rect)
2019 * - checking for polygon simplicity (finding intersections)
2020 * - checking for a convex polygon (if concave we can turn it into several convex or return an empty Rect)
2021 * - find the largest rectangle inside the (or each) convex polygon(s)
2022 * http://cgm.cs.mcgill.ca/~athens/cs507/Projects/2003/DanielSud/complete.html