in plugin/:
[moon.git] / src / uielement.cpp
blobacc80f352fe389e7d8bef7111e6e73e0546a1a4a
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * uielement.cpp
5 * Copyright 2007 Novell, Inc. (http://www.novell.com)
7 * See the LICENSE file included with the distribution for details.
8 *
9 */
11 #include <config.h>
13 #include <stdlib.h>
14 #include <math.h>
16 #include "trigger.h"
17 #include "uielement.h"
18 #include "collection.h"
19 #include "canvas.h"
20 #include "brush.h"
21 #include "transform.h"
22 #include "runtime.h"
23 #include "geometry.h"
24 #include "shape.h"
25 #include "dirty.h"
26 #include "eventargs.h"
27 #include "timemanager.h"
28 #include "media.h"
29 #include "resources.h"
30 #include "popup.h"
31 #include "provider.h"
33 //#define DEBUG_INVALIDATE 0
34 #define MIN_FRONT_TO_BACK_COUNT 25
36 UIElement::UIElement ()
38 SetObjectType (Type::UIELEMENT);
40 visual_level = 0;
41 visual_parent = NULL;
42 subtree_object = NULL;
43 opacityMask = NULL;
45 flags = UIElement::RENDER_VISIBLE | UIElement::HIT_TEST_VISIBLE;
47 bounds = Rect (0,0,0,0);
48 cairo_matrix_init_identity (&absolute_xform);
50 emitting_loaded = false;
51 dirty_flags = DirtyMeasure;
52 up_dirty_node = down_dirty_node = NULL;
53 force_invalidate_of_new_bounds = false;
54 dirty_region = new Region ();
56 desired_size = Size (0, 0);
57 render_size = Size (0, 0);
59 ComputeLocalTransform ();
60 ComputeTotalRenderVisibility ();
61 ComputeTotalHitTestVisibility ();
64 UIElement::~UIElement()
66 delete dirty_region;
69 bool
70 UIElement::IsSubtreeLoaded (UIElement *element)
72 while (element && !element->IsLoaded ())
73 element = element->GetVisualParent ();
74 return element;
77 void
78 UIElement::Dispose()
80 TriggerCollection *triggers = GetTriggers ();
82 if (triggers != NULL) {
83 for (int i = 0; i < triggers->GetCount (); i++)
84 triggers->GetValueAt (i)->AsEventTrigger ()->RemoveTarget (this);
87 if (!IsDisposed ()) {
88 VisualTreeWalker walker (this);
89 while (UIElement *child = walker.Step ())
90 child->SetVisualParent (NULL);
93 if (subtree_object) {
94 subtree_object->unref ();
95 subtree_object = NULL;
98 DependencyObject::Dispose();
101 void
102 UIElement::SetSurface (Surface *s)
104 if (GetSurface() == s)
105 return;
107 if (s == NULL && GetSurface()) {
108 /* we're losing our surface, delete ourselves from the dirty list if we're on it */
109 GetSurface()->RemoveDirtyElement (this);
112 if (subtree_object != NULL && subtree_object->Is(Type::UIELEMENT))
113 subtree_object->SetSurface (s);
115 DependencyObject::SetSurface (s);
118 Rect
119 UIElement::IntersectBoundsWithClipPath (Rect unclipped, bool transform)
121 Geometry *clip = GetClip ();
122 Geometry *layout_clip = transform ? NULL : LayoutInformation::GetLayoutClip (this);
123 Rect box;
125 if (!clip && !layout_clip)
126 return unclipped;
128 if (clip)
129 box = clip->GetBounds ();
130 else
131 box = layout_clip->GetBounds ();
133 if (layout_clip)
134 box = box.Intersection (layout_clip->GetBounds());
136 if (!GetRenderVisible())
137 box = Rect (0,0,0,0);
139 if (transform)
140 box = box.Transform (&absolute_xform);
142 return box.Intersection (unclipped);
145 void
146 UIElement::RenderClipPath (cairo_t *cr, bool path_only)
148 cairo_new_path (cr);
149 cairo_set_matrix (cr, &absolute_xform);
151 Geometry *geometry = GetClip();
152 if (!geometry)
153 return;
155 geometry->Draw (cr);
156 if (!path_only)
157 cairo_clip (cr);
160 void
161 UIElement::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
163 if (args->GetProperty ()->GetOwnerType() != Type::UIELEMENT) {
164 DependencyObject::OnPropertyChanged (args, error);
165 return;
168 if (args->GetId () == UIElement::OpacityProperty) {
169 InvalidateVisibility ();
170 } else if (args->GetId () == UIElement::VisibilityProperty) {
171 // note: invalid enum values are only validated in 1.1 (managed code),
172 // the default value for VisibilityProperty is VisibilityCollapsed
173 // (see bug #340799 for more details)
174 if (args->GetNewValue()->AsInt32() == VisibilityVisible)
175 flags |= UIElement::RENDER_VISIBLE;
176 else
177 flags &= ~UIElement::RENDER_VISIBLE;
179 InvalidateVisibility ();
180 InvalidateMeasure ();
181 if (GetVisualParent ())
182 GetVisualParent ()->InvalidateMeasure ();
183 } else if (args->GetId () == UIElement::IsHitTestVisibleProperty) {
184 if (args->GetNewValue()->AsBool())
185 flags |= UIElement::HIT_TEST_VISIBLE;
186 else
187 flags &= ~UIElement::HIT_TEST_VISIBLE;
189 UpdateTotalHitTestVisibility();
190 } else if (args->GetId () == UIElement::ClipProperty) {
191 InvalidateClip ();
192 } else if (args->GetId () == UIElement::OpacityMaskProperty) {
193 opacityMask = args->GetNewValue() ? args->GetNewValue()->AsBrush() : NULL;
194 InvalidateMask ();
195 } else if (args->GetId () == UIElement::RenderTransformProperty
196 || args->GetId () == UIElement::RenderTransformOriginProperty) {
197 UpdateTransform ();
199 else if (args->GetId () == UIElement::TriggersProperty) {
200 if (args->GetOldValue()) {
201 // remove the old trigger targets
202 TriggerCollection *triggers = args->GetOldValue()->AsTriggerCollection();
203 for (int i = 0; i < triggers->GetCount (); i++)
204 triggers->GetValueAt (i)->AsEventTrigger ()->RemoveTarget (this);
207 if (args->GetNewValue()) {
208 // set the new ones
209 TriggerCollection *triggers = args->GetNewValue()->AsTriggerCollection();
210 for (int i = 0; i < triggers->GetCount (); i++)
211 triggers->GetValueAt (i)->AsEventTrigger ()->SetTarget (this);
213 } else if (args->GetId () == UIElement::UseLayoutRoundingProperty) {
214 InvalidateMeasure ();
215 InvalidateArrange ();
218 NotifyListenersOfPropertyChange (args, error);
221 void
222 UIElement::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
224 if (col == GetTriggers ()) {
225 switch (args->GetChangedAction()) {
226 case CollectionChangedActionReplace:
227 args->GetOldItem()->AsEventTrigger ()->RemoveTarget (this);
228 // fall thru to Add
229 case CollectionChangedActionAdd:
230 args->GetNewItem()->AsEventTrigger ()->SetTarget (this);
231 break;
232 case CollectionChangedActionRemove:
233 args->GetOldItem()->AsEventTrigger ()->RemoveTarget (this);
234 break;
235 case CollectionChangedActionClearing:
236 for (int i = 0; i < col->GetCount (); i++)
237 col->GetValueAt (i)->AsEventTrigger ()->RemoveTarget (this);
238 break;
239 case CollectionChangedActionCleared:
240 // nothing needed here.
241 break;
244 else {
245 DependencyObject::OnCollectionChanged (col, args);
249 #if 1
251 UIElement::DumpHierarchy (UIElement *obj)
253 if (obj == NULL)
254 return 0;
256 int n = DumpHierarchy (obj->GetVisualParent ());
257 for (int i = 0; i < n; i++)
258 putchar (' ');
259 printf ("%s (%p)\n", obj->GetTypeName(), obj);
260 return n + 4;
262 #endif
264 void
265 UIElement::UpdateBounds (bool force_redraw)
267 //InvalidateMeasure ();
268 //InvalidateArrange ();
270 Surface *surface = GetSurface ();
271 if (surface)
272 surface->AddDirtyElement (this, DirtyBounds);
274 force_invalidate_of_new_bounds |= force_redraw;
277 void
278 UIElement::UpdateTotalRenderVisibility ()
280 Surface *surface = GetSurface ();
281 if (surface)
282 surface->AddDirtyElement (this, DirtyRenderVisibility);
285 void
286 UIElement::UpdateTotalHitTestVisibility ()
288 VisualTreeWalker walker (this);
289 while (UIElement *child = walker.Step ())
290 child->UpdateTotalHitTestVisibility ();
292 if (GetSurface())
293 GetSurface ()->AddDirtyElement (this, DirtyHitTestVisibility);
296 bool
297 UIElement::GetActualTotalRenderVisibility ()
299 bool visible = (flags & UIElement::RENDER_VISIBLE) != 0;
300 bool parent_visible = true;
302 total_opacity = GetOpacity ();
304 if (GetVisualParent ()) {
305 GetVisualParent ()->ComputeTotalRenderVisibility ();
306 parent_visible = visible && GetVisualParent ()->GetRenderVisible ();
307 total_opacity *= GetVisualParent ()->total_opacity;
310 visible = visible && parent_visible;
312 return visible;
315 void
316 UIElement::ComputeTotalRenderVisibility ()
318 if (GetActualTotalRenderVisibility ())
319 flags |= UIElement::TOTAL_RENDER_VISIBLE;
320 else
321 flags &= ~UIElement::TOTAL_RENDER_VISIBLE;
324 bool
325 UIElement::GetActualTotalHitTestVisibility ()
327 bool visible = (flags & UIElement::HIT_TEST_VISIBLE) != 0;
329 if (visible && GetVisualParent ()) {
330 GetVisualParent ()->ComputeTotalHitTestVisibility ();
331 visible = visible && GetVisualParent ()->GetHitTestVisible ();
334 return visible;
337 void
338 UIElement::ComputeTotalHitTestVisibility ()
340 if (GetActualTotalHitTestVisibility ())
341 flags |= UIElement::TOTAL_HIT_TEST_VISIBLE;
342 else
343 flags &= ~UIElement::TOTAL_HIT_TEST_VISIBLE;
346 void
347 UIElement::UpdateTransform ()
349 InvalidateArrange ();
351 if (GetSurface()) {
352 GetSurface()->AddDirtyElement (this, DirtyLocalTransform);
356 void
357 UIElement::ComputeLocalTransform ()
359 Transform *transform = GetRenderTransform ();
360 Point transform_origin = GetTransformOrigin ();
361 cairo_matrix_t render;
363 cairo_matrix_init_identity (&render);
364 cairo_matrix_init_identity (&local_xform);
366 if (transform == NULL)
367 return;
369 transform->GetTransform (&render);
370 cairo_matrix_translate (&local_xform, transform_origin.x, transform_origin.y);
371 cairo_matrix_multiply (&local_xform, &render, &local_xform);
372 cairo_matrix_translate (&local_xform, -transform_origin.x, -transform_origin.y);
375 void
376 UIElement::TransformBounds (cairo_matrix_t *old, cairo_matrix_t *current)
378 Rect updated;
380 cairo_matrix_t tween = *old;
381 cairo_matrix_invert (&tween);
382 cairo_matrix_multiply (&tween, &tween, current);
384 Point p0 (0,0);
385 Point p1 (1,0);
386 Point p2 (1,1);
387 Point p3 (0,1);
389 p0 = p0 - p0.Transform (&tween);
390 p1 = p1 - p1.Transform (&tween);
391 p2 = p2 - p2.Transform (&tween);
392 p3 = p3 - p3.Transform (&tween);
394 if (p0 == p1 && p1 == p2 && p2 == p3) {
395 //printf ("shifting position\n");
396 ShiftPosition (bounds.GetTopLeft ().Transform (&tween));
397 return;
400 UpdateBounds ();
403 void
404 UIElement::ComputeTransform ()
406 cairo_matrix_t old = absolute_xform;
407 cairo_matrix_init_identity (&absolute_xform);
409 if (GetVisualParent () != NULL) {
410 absolute_xform = GetVisualParent ()->absolute_xform;
411 } else if (GetParent () != NULL && GetParent ()->Is (Type::POPUP)) {
412 // FIXME we shouldn't be examing a subclass type here but we'll do this
413 // for now to get popups in something approaching the right place while
414 // we figure out a cleaner way to handle it long term.
415 Popup *popup = (Popup *)GetParent ();
416 absolute_xform = popup->absolute_xform;
417 cairo_matrix_translate (&absolute_xform, popup->GetHorizontalOffset (), popup->GetVerticalOffset ());
420 cairo_matrix_multiply (&absolute_xform, &layout_xform, &absolute_xform);
421 cairo_matrix_multiply (&absolute_xform, &local_xform, &absolute_xform);
423 if (moonlight_flags & RUNTIME_INIT_USE_UPDATE_POSITION)
424 TransformBounds (&old, &absolute_xform);
425 else {
426 UpdateBounds ();
430 void
431 UIElement::ComputeBounds ()
433 g_warning ("UIElement:ComputeBounds has been called. The derived class %s should have overridden it.",
434 GetTypeName ());
437 void
438 UIElement::ShiftPosition (Point p)
440 bounds.x = p.x;
441 bounds.y = p.y;
444 void
445 UIElement::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
447 if (prop && prop->GetId () == UIElement::RenderTransformProperty) {
448 UpdateTransform ();
450 else if (prop && prop->GetId () == UIElement::ClipProperty) {
451 InvalidateClip ();
453 else if (prop && prop->GetId () == UIElement::OpacityMaskProperty) {
454 InvalidateMask ();
457 DependencyObject::OnSubPropertyChanged (prop, obj, subobj_args);
460 void
461 UIElement::CacheInvalidateHint ()
463 VisualTreeWalker walker (this);
464 while (UIElement *child = walker.Step ())
465 child->CacheInvalidateHint ();
468 void
469 UIElement::SetVisualParent (UIElement *visual_parent)
471 this->visual_parent = visual_parent;
473 if (visual_parent && visual_parent->GetSurface () != GetSurface())
474 SetSurface (visual_parent->GetSurface());
477 void
478 UIElement::SetSubtreeObject (DependencyObject *value)
480 if (subtree_object == value)
481 return;
483 if (subtree_object)
484 subtree_object->unref ();
486 subtree_object = value;
488 if (subtree_object)
489 subtree_object->ref ();
492 void
493 UIElement::ElementRemoved (UIElement *item)
495 // Invalidate ourself in the size of the item's subtree
496 Invalidate (item->GetSubtreeBounds());
498 if (GetSurface ())
499 GetSurface()->RemoveDirtyElement (item);
500 item->SetVisualParent (NULL);
501 item->CacheInvalidateHint ();
502 item->ClearLoaded ();
504 InvalidateMeasure ();
507 void
508 UIElement::ElementAdded (UIElement *item)
510 item->SetVisualLevel (GetVisualLevel() + 1);
511 item->SetVisualParent (this);
512 item->UpdateTotalRenderVisibility ();
513 item->UpdateTotalHitTestVisibility ();
514 //item->UpdateBounds (true);
515 item->Invalidate ();
517 if (0 != (flags & (UIElement::IS_LOADED | UIElement::PENDING_LOADED))) {
518 InheritedPropertyValueProvider::PropagateInheritedPropertiesOnAddingToTree (item);
520 bool delay = false;
521 List *load_list = item->WalkTreeForLoaded (&delay);
523 // if we're loaded, key the delay state off of the
524 // WalkTreeForLoaded call.
526 // if we're actually pending loaded, forcibly delay
527 // the new element's Loaded firing as well.
529 if (0 != (flags & UIElement::PENDING_LOADED))
530 delay = true;
532 if (delay) {
533 PostSubtreeLoad (load_list);
534 // PostSubtreeLoad will take care of deleting the list for us.
536 else {
537 EmitSubtreeLoad (load_list);
538 delete load_list;
542 UpdateBounds (true);
544 InvalidateMeasure ();
545 item->SetRenderSize (Size (0,0));
546 item->UpdateTransform ();
547 item->InvalidateMeasure ();
548 item->InvalidateArrange ();
551 void
552 UIElement::InvalidateMeasure ()
554 dirty_flags |= DirtyMeasure;
556 Surface *surface;
557 if ((surface = GetSurface ()))
558 surface->needs_measure = true;
562 void
563 UIElement::InvalidateArrange ()
565 dirty_flags |= DirtyArrange;
567 Surface *surface;
568 if ((surface = GetSurface ()))
569 surface->needs_arrange = true;
572 void
573 UIElement::DoMeasure ()
575 Size *last = LayoutInformation::GetPreviousConstraint (this);
576 UIElement *parent = GetVisualParent ();
577 Size infinite (INFINITY, INFINITY);
579 if (!GetSurface () && !last && !parent && IsLayoutContainer ()) {
580 last = &infinite;
583 if (last) {
584 Size previous_desired = GetDesiredSize ();
586 // This will be a noop on non layout elements
587 Measure (*last);
589 if (previous_desired == GetDesiredSize ())
590 return;
593 // a canvas doesn't care about the child size changing like this
594 if (parent && (!parent->Is (Type::CANVAS) || IsLayoutContainer ()))
595 parent->InvalidateMeasure ();
597 dirty_flags &= ~DirtyMeasure;
600 void
601 UIElement::DoArrange ()
603 Rect *last = LayoutInformation::GetLayoutSlot (this);
604 Size previous_render = Size ();
605 UIElement *parent = GetVisualParent ();
606 Rect viewport;
608 if (!parent) {
609 Size desired = Size ();
610 Size available = Size ();
611 Surface *surface = GetSurface ();
613 if (IsLayoutContainer ()) {
614 desired = GetDesiredSize ();
615 if (surface && this == surface->GetToplevel ()) {
616 Size *measure = LayoutInformation::GetPreviousConstraint (this);
617 if (measure)
618 desired = desired.Max (*LayoutInformation::GetPreviousConstraint (this));
619 else
620 desired = Size (surface->GetWindow ()->GetWidth (), surface->GetWindow ()->GetHeight ());
622 } else {
623 FrameworkElement *fe = (FrameworkElement*)this;
624 desired = Size (fe->GetActualWidth (), fe->GetActualHeight ());
627 viewport = Rect (Canvas::GetLeft (this),
628 Canvas::GetTop (this),
629 desired.width, desired.height);
631 last = &viewport;
634 if (last) {
635 previous_render = GetRenderSize ();
636 Arrange (*last);
638 if (previous_render == GetRenderSize ())
639 return;
642 if (parent && (!parent->Is (Type::CANVAS) || (IsLayoutContainer () || !last)))
643 parent->InvalidateArrange ();
645 if (!last)
646 return;
648 LayoutInformation::SetLastRenderSize (this, &previous_render);
651 bool
652 UIElement::InsideClip (cairo_t *cr, double x, double y)
654 Geometry* clip;
655 bool ret = false;
656 double nx = x;
657 double ny = y;
659 clip = GetClip ();
661 if (clip == NULL) {
662 return true;
665 TransformPoint (&nx, &ny);
666 if (!clip->GetBounds ().PointInside (nx, ny))
667 return false;
669 cairo_save (cr);
670 clip->Draw (cr);
672 if (cairo_in_fill (cr, nx, ny))
673 ret = true;
675 cairo_new_path (cr);
676 cairo_restore (cr);
678 return ret;
681 bool
682 UIElement::InsideObject (cairo_t *cr, double x, double y)
684 return InsideClip (cr, x, y);
687 class LoadedState : public EventObject {
688 public:
689 LoadedState (List *list) { this->list = list; }
690 ~LoadedState () { delete list; }
692 List* GetUIElementList () { return list; }
694 private:
695 List* list;
698 void
699 UIElement::emit_delayed_loaded (EventObject *data)
701 LoadedState *state = (LoadedState *)data;
703 EmitSubtreeLoad (state->GetUIElementList ());
706 void
707 UIElement::EmitSubtreeLoad (List *l)
709 while (UIElementNode* node = (UIElementNode*)l->First()) {
710 l->Unlink (node);
712 node->uielement->OnLoaded ();
714 delete node;
718 void
719 UIElement::PostSubtreeLoad (List *load_list)
721 LoadedState *state = new LoadedState (load_list);
723 GetDeployment()->GetSurface()->GetTimeManager()->AddTickCall (emit_delayed_loaded, state);
725 state->unref ();
728 List*
729 UIElement::WalkTreeForLoaded (bool *delay)
731 List *walk_list = new List();
732 List *load_list = new List();
734 if (delay)
735 *delay = false;
737 walk_list->Append (new UIElementNode (this));
739 while (UIElementNode *next = (UIElementNode*)walk_list->First ()) {
740 // remove it from the walk list
741 walk_list->Unlink (next);
743 if (next->uielement->Is(Type::CONTROL)) {
744 Control *control = (Control*)next->uielement;
745 if (!control->default_style_applied) {
746 ManagedTypeInfo *key = control->GetDefaultStyleKey ();
747 if (key) {
748 if (Application::GetCurrent () == NULL)
749 g_warning ("attempting to use a null application when applying default style when emitting Loaded event.");
750 else
751 Application::GetCurrent()->ApplyDefaultStyle (control, key);
755 if (control->GetStyle() || control->default_style_applied)
756 if (delay)
757 *delay = true;
761 // add it to the front of the load list, and mark it as PENDING_LOADED
762 next->uielement->flags |= UIElement::PENDING_LOADED;
763 load_list->Prepend (next);
765 // and add its children to the front of the walk list
766 VisualTreeWalker walker (next->uielement);
767 while (UIElement *child = walker.Step ())
768 walk_list->Prepend (new UIElementNode (child));
771 delete walk_list;
773 return load_list;
777 void
778 UIElement::OnLoaded ()
780 if (emitting_loaded || IsLoaded())
781 return;
783 emitting_loaded = true;
785 flags |= UIElement::IS_LOADED;
787 flags &= ~UIElement::PENDING_LOADED;
789 Emit (LoadedEvent, new RoutedEventArgs(this));
791 emitting_loaded = false;
794 void
795 UIElement::ClearLoaded ()
797 UIElement *e = NULL;
798 Surface *s = Deployment::GetCurrent ()->GetSurface ();
799 if (s->GetFocusedElement () == this)
800 s->FocusElement (NULL);
802 if (!IsLoaded ())
803 return;
805 flags &= ~UIElement::IS_LOADED;
807 Emit (UnloadedEvent, new RoutedEventArgs(this), true);
808 VisualTreeWalker walker (this);
809 while ((e = walker.Step ()))
810 e->ClearLoaded ();
813 bool
814 UIElement::Focus (bool recurse)
816 return false;
820 // Queues the invalidate for the current region, performs any
821 // updates to the RenderTransform (optional) and queues a
822 // new redraw with the new bounding box
824 void
825 UIElement::FullInvalidate (bool rendertransform)
827 Invalidate ();
828 if (rendertransform)
829 UpdateTransform ();
830 UpdateBounds (true /* force an invalidate here, even if the bounds don't change */);
833 void
834 UIElement::Invalidate (Rect r)
836 if (!GetRenderVisible() || IS_INVISIBLE(total_opacity))
837 return;
839 #ifdef DEBUG_INVALIDATE
840 printf ("Requesting invalidate for object %p %s (%s) at %f %f - %f %f\n",
841 this, GetName(), GetTypeName(),
842 r.x, r.y,
843 r.w, r.h);
844 #endif
847 if (GetSurface ()) {
848 GetSurface()->AddDirtyElement (this, DirtyInvalidate);
850 dirty_region->Union (r);
852 GetSurface()->GetTimeManager()->NeedRedraw ();
854 Emit (InvalidatedEvent);
858 void
859 UIElement::Invalidate (Region *region)
861 if (!GetRenderVisible () || IS_INVISIBLE (total_opacity))
862 return;
864 if (GetSurface ()) {
865 GetSurface()->AddDirtyElement (this, DirtyInvalidate);
867 dirty_region->Union (region);
869 GetSurface()->GetTimeManager()->NeedRedraw ();
871 Emit (InvalidatedEvent);
875 void
876 UIElement::Invalidate ()
878 Invalidate (bounds);
882 void
883 UIElement::InvalidatePaint ()
885 Invalidate ();
889 void
890 UIElement::InvalidateSubtreePaint ()
892 Invalidate (GetSubtreeBounds ());
895 void
896 UIElement::InvalidateClip ()
898 InvalidateSubtreePaint ();
899 UpdateBounds (true);
902 void
903 UIElement::InvalidateMask ()
905 InvalidateSubtreePaint ();
908 void
909 UIElement::InvalidateVisibility ()
911 UpdateTotalRenderVisibility ();
912 InvalidateSubtreePaint ();
916 void
917 UIElement::InvalidateIntrisicSize ()
919 InvalidateMeasure ();
920 InvalidateArrange ();
921 UpdateBounds (true);
925 void
926 UIElement::HitTest (cairo_t *cr, Point p, List *uielement_list)
928 uielement_list->Prepend (new UIElementNode (this));
931 void
932 UIElement::HitTest (cairo_t *cr, Rect r, List *uielement_list)
936 void
937 UIElement::FindElementsInHostCoordinates_p (Point p, HitTestCollection *uielement_list)
939 List *list = new List ();
940 cairo_t *ctx = measuring_context_create ();
942 FindElementsInHostCoordinates (ctx, p, list);
944 UIElementNode *node = (UIElementNode *) list->First ();
945 while (node) {
946 uielement_list->Add (new Value (node->uielement));
947 node = (UIElementNode *) node->next;
950 delete list;
951 measuring_context_destroy (ctx);
955 void
956 UIElement::FindElementsInHostCoordinates (cairo_t *cr, Point P, List *uielement_list)
958 uielement_list->Prepend (new UIElementNode (this));
962 void
963 UIElement::FindElementsInHostCoordinates_r (Rect r, HitTestCollection *uielement_list)
965 List *list = new List ();
966 cairo_t *ctx = measuring_context_create ();
968 FindElementsInHostCoordinates (ctx, r, list);
970 UIElementNode *node = (UIElementNode *) list->First ();
971 while (node) {
972 uielement_list->Add (new Value (node->uielement));
973 node = (UIElementNode *) node->next;
976 delete list;
977 measuring_context_destroy (ctx);
980 void
981 UIElement::FindElementsInHostCoordinates (cairo_t *cr, Rect r, List *uielement_list)
983 uielement_list->Prepend (new UIElementNode (this));
986 bool
987 UIElement::EmitKeyDown (GdkEventKey *event)
989 return Emit (KeyDownEvent, new KeyEventArgs (event));
992 bool
993 UIElement::EmitKeyUp (GdkEventKey *event)
995 return Emit (KeyUpEvent, new KeyEventArgs (event));
998 bool
999 UIElement::EmitGotFocus ()
1001 return Emit (GotFocusEvent, new RoutedEventArgs (this));
1004 bool
1005 UIElement::EmitLostFocus ()
1007 return Emit (LostFocusEvent, new RoutedEventArgs (this));
1010 bool
1011 UIElement::EmitLostMouseCapture ()
1013 MouseEventArgs *e = new MouseEventArgs ();
1014 e->SetSource (this);
1015 return Emit (LostMouseCaptureEvent, e);
1018 bool
1019 UIElement::CaptureMouse ()
1021 Surface *s = GetSurface ();
1022 if (s == NULL)
1023 return false;
1025 return s->SetMouseCapture (this);
1028 void
1029 UIElement::ReleaseMouseCapture ()
1031 Surface *s = GetSurface ();
1032 if (s == NULL)
1033 return;
1035 s->ReleaseMouseCapture (this);
1038 void
1039 UIElement::DoRender (cairo_t *cr, Region *parent_region)
1041 Region *region = new Region (GetSubtreeBounds ());
1042 region->Intersect (parent_region);
1044 if (!GetRenderVisible() || IS_INVISIBLE (total_opacity) || region->IsEmpty ()) {
1045 delete region;
1046 return;
1049 #if FRONT_TO_BACK_STATS
1050 GetSurface()->uielements_rendered_back_to_front ++;
1051 #endif
1053 STARTTIMER (UIElement_render, Type::Find (GetObjectType())->name);
1055 PreRender (cr, region, false);
1057 Render (cr, region);
1059 PostRender (cr, region, false);
1061 ENDTIMER (UIElement_render, Type::Find (GetObjectType())->name);
1063 delete region;
1066 bool
1067 UIElement::UseBackToFront ()
1069 return VisualTreeWalker (this).GetCount () < MIN_FRONT_TO_BACK_COUNT;
1072 void
1073 UIElement::FrontToBack (Region *surface_region, List *render_list)
1075 double local_opacity = GetOpacity ();
1077 if (surface_region->RectIn (GetSubtreeBounds().RoundOut()) == GDK_OVERLAP_RECTANGLE_OUT)
1078 return;
1080 if (!GetRenderVisible ()
1081 || IS_INVISIBLE (local_opacity))
1082 return;
1084 if (!UseBackToFront ()) {
1085 Region *self_region = new Region (surface_region);
1086 self_region->Intersect (GetSubtreeBounds().RoundOut());
1088 // we need to include our children in this one, since
1089 // we'll be rendering them in the PostRender method.
1090 if (!self_region->IsEmpty())
1091 render_list->Prepend (new RenderNode (this, self_region, true,
1092 UIElement::CallPreRender, UIElement::CallPostRender));
1093 // don't remove the region from surface_region because
1094 // there are likely holes in it
1095 return;
1098 Region *region;
1099 bool delete_region;
1100 bool can_subtract_self;
1102 if (!GetClip ()
1103 && !GetOpacityMask ()
1104 && !IS_TRANSLUCENT (GetOpacity ())) {
1105 region = surface_region;
1106 delete_region = false;
1107 can_subtract_self = true;
1109 else {
1110 region = new Region (surface_region);
1111 delete_region = true;
1112 can_subtract_self = false;
1115 RenderNode *cleanup_node = new RenderNode (this, NULL, false, NULL, UIElement::CallPostRender);
1117 render_list->Prepend (cleanup_node);
1119 Region *self_region = new Region (region);
1121 VisualTreeWalker walker (this, ZReverse);
1122 while (UIElement *child = walker.Step ())
1123 child->FrontToBack (region, render_list);
1125 if (!GetOpacityMask () && !IS_TRANSLUCENT (local_opacity)) {
1126 delete self_region;
1127 if (GetRenderBounds().IsEmpty ()) { // empty bounds mean that this element draws nothing itself
1128 self_region = new Region ();
1130 else {
1131 self_region = new Region (region);
1132 self_region->Intersect (GetRenderBounds().RoundOut ()); // note the RoundOut
1134 } else {
1135 self_region->Intersect (GetSubtreeBounds().RoundOut ()); // note the RoundOut
1138 if (self_region->IsEmpty() && render_list->First() == cleanup_node) {
1139 /* we don't intersect the surface region, and none of
1140 our children did either, remove the cleanup node */
1141 render_list->Remove (render_list->First());
1142 delete self_region;
1143 if (delete_region)
1144 delete region;
1145 return;
1148 render_list->Prepend (new RenderNode (this, self_region, !self_region->IsEmpty(), UIElement::CallPreRender, NULL));
1150 if (!self_region->IsEmpty()) {
1151 if (((absolute_xform.yx == 0 && absolute_xform.xy == 0) /* no skew/rotation */
1152 || (absolute_xform.xx == 0 && absolute_xform.yy == 0)) /* allow 90 degree rotations */
1153 && can_subtract_self)
1154 region->Subtract (GetCoverageBounds ());
1157 if (delete_region)
1158 delete region;
1161 void
1162 UIElement::PreRender (cairo_t *cr, Region *region, bool skip_children)
1164 double local_opacity = GetOpacity ();
1166 cairo_save (cr);
1168 cairo_set_matrix (cr, &absolute_xform);
1169 RenderClipPath (cr);
1171 if (opacityMask || IS_TRANSLUCENT (local_opacity)) {
1172 Rect r = GetSubtreeBounds ().RoundOut();
1173 cairo_identity_matrix (cr);
1175 // we need this check because ::PreRender can (and
1176 // will) be called for elements with empty regions.
1178 // The region passed in here is the redraw region
1179 // intersected with the render bounds of a given
1180 // element. For Panels with no width/height specified
1181 // in the xaml, this region will be empty. (check
1182 // panel.cpp::FrontToBack - we insert the ::PreRender
1183 // calling node if either the panel background or any
1184 // of the children intersect the redraw region.) We
1185 // can't clip to the empty region, obviously, as it
1186 // will keep all descendents from drawing to the
1187 // screen.
1189 if (!region->IsEmpty()) {
1190 region->Draw (cr);
1191 cairo_clip (cr);
1193 r.Draw (cr);
1194 cairo_clip (cr);
1196 cairo_set_matrix (cr, &absolute_xform);
1199 if (ClipToExtents ()) {
1200 extents.Draw (cr);
1201 cairo_clip (cr);
1205 if (IS_TRANSLUCENT (local_opacity))
1206 cairo_push_group (cr);
1208 if (opacityMask != NULL)
1209 cairo_push_group (cr);
1212 void
1213 UIElement::PostRender (cairo_t *cr, Region *region, bool front_to_back)
1215 // if we didn't render front to back, then render the children here
1216 if (!front_to_back) {
1217 VisualTreeWalker walker (this, ZForward);
1218 while (UIElement *child = walker.Step ())
1219 child->DoRender (cr, region);
1222 double local_opacity = GetOpacity ();
1224 if (opacityMask != NULL) {
1225 cairo_pattern_t *mask;
1226 cairo_pattern_t *data = cairo_pop_group (cr);
1227 Point p = GetOriginPoint ();
1228 Rect area = Rect (p.x, p.y, 0.0, 0.0);
1229 GetSizeForBrush (cr, &(area.width), &(area.height));
1230 opacityMask->SetupBrush (cr, area);
1231 mask = cairo_get_source (cr);
1232 cairo_pattern_reference (mask);
1233 cairo_set_source (cr, data);
1234 cairo_mask (cr, mask);
1235 cairo_pattern_destroy (mask);
1236 cairo_pattern_destroy (data);
1239 if (IS_TRANSLUCENT (local_opacity)) {
1240 cairo_pop_group_to_source (cr);
1241 cairo_paint_with_alpha (cr, local_opacity);
1244 cairo_restore (cr);
1246 if (moonlight_flags & RUNTIME_INIT_SHOW_CLIPPING) {
1247 cairo_save (cr);
1248 cairo_new_path (cr);
1249 cairo_set_matrix (cr, &absolute_xform);
1250 cairo_set_line_width (cr, 1);
1252 Geometry *geometry = GetClip ();
1253 if (geometry) {
1254 geometry->Draw (cr);
1255 cairo_set_source_rgba (cr, 0.0, 1.0, 1.0, 1.0);
1256 cairo_stroke (cr);
1259 cairo_restore (cr);
1262 if (moonlight_flags & RUNTIME_INIT_SHOW_BOUNDING_BOXES) {
1263 cairo_save (cr);
1264 cairo_new_path (cr);
1265 //RenderClipPath (cr);
1266 cairo_identity_matrix (cr);
1267 cairo_set_source_rgba (cr, 1.0, 0.5, 0.2, 1.0);
1268 cairo_set_line_width (cr, 1);
1269 cairo_rectangle (cr, bounds.x + .5, bounds.y + .5, bounds.width - .5, bounds.height - .5);
1270 cairo_stroke (cr);
1271 cairo_restore (cr);
1275 void
1276 UIElement::Paint (cairo_t *ctx, Region *region, cairo_matrix_t *xform)
1278 // FIXME xform is ignored for now
1279 if (xform)
1280 g_warning ("passing a transform to UIElement::Paint is not yet supported");
1282 #if FRONT_TO_BACK_STATS
1283 uielements_rendered_front_to_back = 0;
1284 uielements_rendered_back_to_front = 0;
1285 #endif
1287 bool did_front_to_back = false;
1288 List *render_list = new List ();
1290 if (moonlight_flags & RUNTIME_INIT_RENDER_FRONT_TO_BACK) {
1291 Region *copy = new Region (region);
1292 FrontToBack (copy, render_list);
1294 if (!render_list->IsEmpty ()) {
1295 while (RenderNode *node = (RenderNode*)render_list->First()) {
1296 #if FRONT_TO_BACK_STATS
1297 uielements_rendered_front_to_back ++;
1298 #endif
1299 node->Render (ctx);
1301 render_list->Remove (node);
1304 did_front_to_back = true;
1307 delete render_list;
1308 delete copy;
1311 if (!did_front_to_back) {
1312 DoRender (ctx, region);
1315 #if FRONT_TO_BACK_STATS
1316 printf ("UIElements rendered front-to-back for: %s(%p)\n", uielements_rendered_front_to_back, GetName (), this);
1317 printf ("UIElements rendered back-to-front for: %s(%p)\n", uielements_rendered_back_to_front, GetName (), this);
1318 #endif
1321 void
1322 UIElement::CallPreRender (cairo_t *cr, UIElement *element, Region *region, bool front_to_back)
1324 element->PreRender (cr, region, front_to_back);
1327 void
1328 UIElement::CallPostRender (cairo_t *cr, UIElement *element, Region *region, bool front_to_back)
1330 element->PostRender (cr, region, front_to_back);
1333 void
1334 UIElement::Render (cairo_t *cr, Region *region, bool path_only)
1336 /* do nothing by default */
1339 void
1340 UIElement::GetSizeForBrush (cairo_t *cr, double *width, double *height)
1342 g_warning ("UIElement:GetSizeForBrush has been called. The derived class %s should have overridden it.",
1343 GetTypeName ());
1344 *height = *width = 0.0;
1347 TimeManager *
1348 UIElement::GetTimeManager ()
1350 Surface *surface = GetSurface ();
1351 Deployment *deployment;
1353 if (surface == NULL) {
1354 deployment = GetDeployment ();
1355 if (deployment != NULL)
1356 surface = deployment->GetSurface ();
1359 return surface ? surface->GetTimeManager() : NULL;
1362 GeneralTransform *
1363 UIElement::GetTransformToUIElementWithError (UIElement *to_element, MoonError *error)
1365 /* walk from this up to the root. if we hit null before we hit the toplevel, it's an error */
1366 UIElement *visual = this;
1367 bool ok = false;
1369 if (visual && GetSurface()) {
1370 while (visual) {
1371 if (GetSurface()->IsTopLevel (visual))
1372 ok = true;
1373 visual = visual->GetVisualParent ();
1377 if (!ok || (to_element && !to_element->GetSurface ())) {
1378 MoonError::FillIn (error, MoonError::ARGUMENT, 1001,
1379 "visual");
1380 return NULL;
1383 if (to_element && !to_element->GetSurface()->IsTopLevel (to_element)) {
1384 /* if @to_element is specified we also need to make sure there's a path to the root from it */
1385 ok = false;
1386 visual = to_element->GetVisualParent ();
1387 if (visual && to_element->GetSurface()) {
1388 while (visual) {
1389 if (to_element->GetSurface()->IsTopLevel (visual))
1390 ok = true;
1391 visual = visual->GetVisualParent ();
1395 if (!ok) {
1396 MoonError::FillIn (error, MoonError::ARGUMENT, 1001,
1397 "visual");
1398 return NULL;
1402 cairo_matrix_t result;
1403 // A = From, B = To, M = what we want
1404 // A = M * B
1405 // => M = A * inv (B)
1406 if (to_element) {
1407 cairo_matrix_t inverse = to_element->absolute_xform;
1408 cairo_matrix_invert (&inverse);
1409 cairo_matrix_multiply (&result, &absolute_xform, &inverse);
1411 else {
1412 result = absolute_xform;
1415 Matrix *matrix = new Matrix (&result);
1417 MatrixTransform *transform = new MatrixTransform ();
1418 transform->SetValue (MatrixTransform::MatrixProperty, matrix);
1419 matrix->unref ();
1421 return transform;
1424 void
1425 UIElement::TransformPoint (double *x, double *y)
1427 cairo_matrix_t inverse = absolute_xform;
1428 cairo_matrix_invert (&inverse);
1430 cairo_matrix_transform_point (&inverse, x, y);