2009-08-20 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / uielement.cpp
blob71c721c78b446614620e95b1a2a3baa1033fea96
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"
32 //#define DEBUG_INVALIDATE 0
33 #define MIN_FRONT_TO_BACK_COUNT 25
35 UIElement::UIElement ()
37 SetObjectType (Type::UIELEMENT);
39 visual_level = 0;
40 visual_parent = NULL;
41 subtree_object = NULL;
42 opacityMask = NULL;
44 flags = UIElement::RENDER_VISIBLE | UIElement::HIT_TEST_VISIBLE;
46 bounds = Rect (0,0,0,0);
47 cairo_matrix_init_identity (&absolute_xform);
49 emitting_loaded = false;
50 dirty_flags = DirtyMeasure;
51 up_dirty_node = down_dirty_node = NULL;
52 force_invalidate_of_new_bounds = false;
53 dirty_region = new Region ();
55 desired_size = Size (0, 0);
56 render_size = Size (0, 0);
58 ComputeLocalTransform ();
59 ComputeTotalRenderVisibility ();
60 ComputeTotalHitTestVisibility ();
63 UIElement::~UIElement()
65 delete dirty_region;
68 bool
69 UIElement::IsSubtreeLoaded (UIElement *element)
71 while (element && !element->IsLoaded ())
72 element = element->GetVisualParent ();
73 return element;
76 void
77 UIElement::Dispose()
79 TriggerCollection *triggers = GetTriggers ();
81 if (triggers != NULL) {
82 for (int i = 0; i < triggers->GetCount (); i++)
83 triggers->GetValueAt (i)->AsEventTrigger ()->RemoveTarget (this);
86 if (!IsDisposed ()) {
87 VisualTreeWalker walker (this);
88 while (UIElement *child = walker.Step ())
89 child->SetVisualParent (NULL);
92 if (subtree_object) {
93 subtree_object->unref ();
94 subtree_object = NULL;
97 DependencyObject::Dispose();
100 void
101 UIElement::SetSurface (Surface *s)
103 if (GetSurface() == s)
104 return;
106 if (s == NULL && GetSurface()) {
107 /* we're losing our surface, delete ourselves from the dirty list if we're on it */
108 GetSurface()->RemoveDirtyElement (this);
111 if (subtree_object != NULL && subtree_object->Is(Type::UIELEMENT))
112 subtree_object->SetSurface (s);
114 DependencyObject::SetSurface (s);
117 Rect
118 UIElement::IntersectBoundsWithClipPath (Rect unclipped, bool transform)
120 Geometry *clip = GetClip ();
121 Geometry *layout_clip = transform ? NULL : LayoutInformation::GetLayoutClip (this);
122 Rect box;
124 if (!clip && !layout_clip)
125 return unclipped;
127 if (clip)
128 box = clip->GetBounds ();
129 else
130 box = layout_clip->GetBounds ();
132 if (layout_clip)
133 box = box.Intersection (layout_clip->GetBounds());
135 if (!GetRenderVisible())
136 box = Rect (0,0,0,0);
138 if (transform)
139 box = box.Transform (&absolute_xform);
141 return box.Intersection (unclipped);
144 void
145 UIElement::RenderClipPath (cairo_t *cr, bool path_only)
147 cairo_new_path (cr);
148 cairo_set_matrix (cr, &absolute_xform);
150 Geometry *geometry = GetClip();
151 if (!geometry)
152 return;
154 geometry->Draw (cr);
155 if (!path_only)
156 cairo_clip (cr);
159 void
160 UIElement::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
162 if (args->GetProperty ()->GetOwnerType() != Type::UIELEMENT) {
163 DependencyObject::OnPropertyChanged (args, error);
164 return;
167 if (args->GetId () == UIElement::OpacityProperty) {
168 InvalidateVisibility ();
169 } else if (args->GetId () == UIElement::VisibilityProperty) {
170 // note: invalid enum values are only validated in 1.1 (managed code),
171 // the default value for VisibilityProperty is VisibilityCollapsed
172 // (see bug #340799 for more details)
173 if (args->GetNewValue()->AsInt32() == VisibilityVisible)
174 flags |= UIElement::RENDER_VISIBLE;
175 else
176 flags &= ~UIElement::RENDER_VISIBLE;
178 InvalidateVisibility ();
179 InvalidateMeasure ();
180 if (GetVisualParent ())
181 GetVisualParent ()->InvalidateMeasure ();
182 } else if (args->GetId () == UIElement::IsHitTestVisibleProperty) {
183 if (args->GetNewValue()->AsBool())
184 flags |= UIElement::HIT_TEST_VISIBLE;
185 else
186 flags &= ~UIElement::HIT_TEST_VISIBLE;
188 UpdateTotalHitTestVisibility();
189 } else if (args->GetId () == UIElement::ClipProperty) {
190 InvalidateClip ();
191 } else if (args->GetId () == UIElement::OpacityMaskProperty) {
192 opacityMask = args->GetNewValue() ? args->GetNewValue()->AsBrush() : NULL;
193 InvalidateMask ();
194 } else if (args->GetId () == UIElement::RenderTransformProperty
195 || args->GetId () == UIElement::RenderTransformOriginProperty) {
196 UpdateTransform ();
198 else if (args->GetId () == UIElement::TriggersProperty) {
199 if (args->GetOldValue()) {
200 // remove the old trigger targets
201 TriggerCollection *triggers = args->GetOldValue()->AsTriggerCollection();
202 for (int i = 0; i < triggers->GetCount (); i++)
203 triggers->GetValueAt (i)->AsEventTrigger ()->RemoveTarget (this);
206 if (args->GetNewValue()) {
207 // set the new ones
208 TriggerCollection *triggers = args->GetNewValue()->AsTriggerCollection();
209 for (int i = 0; i < triggers->GetCount (); i++)
210 triggers->GetValueAt (i)->AsEventTrigger ()->SetTarget (this);
212 } else if (args->GetId () == UIElement::UseLayoutRoundingProperty) {
213 InvalidateMeasure ();
214 InvalidateArrange ();
217 NotifyListenersOfPropertyChange (args, error);
220 void
221 UIElement::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
223 if (col == GetTriggers ()) {
224 switch (args->GetChangedAction()) {
225 case CollectionChangedActionReplace:
226 args->GetOldItem()->AsEventTrigger ()->RemoveTarget (this);
227 // fall thru to Add
228 case CollectionChangedActionAdd:
229 args->GetNewItem()->AsEventTrigger ()->SetTarget (this);
230 break;
231 case CollectionChangedActionRemove:
232 args->GetOldItem()->AsEventTrigger ()->RemoveTarget (this);
233 break;
234 case CollectionChangedActionClearing:
235 for (int i = 0; i < col->GetCount (); i++)
236 col->GetValueAt (i)->AsEventTrigger ()->RemoveTarget (this);
237 break;
238 case CollectionChangedActionCleared:
239 // nothing needed here.
240 break;
243 else {
244 DependencyObject::OnCollectionChanged (col, args);
248 #if 1
250 UIElement::DumpHierarchy (UIElement *obj)
252 if (obj == NULL)
253 return 0;
255 int n = DumpHierarchy (obj->GetVisualParent ());
256 for (int i = 0; i < n; i++)
257 putchar (' ');
258 printf ("%s (%p)\n", obj->GetTypeName(), obj);
259 return n + 4;
261 #endif
263 void
264 UIElement::UpdateBounds (bool force_redraw)
266 //InvalidateMeasure ();
267 //InvalidateArrange ();
269 Surface *surface = GetSurface ();
270 if (surface)
271 surface->AddDirtyElement (this, DirtyBounds);
273 force_invalidate_of_new_bounds |= force_redraw;
276 void
277 UIElement::UpdateTotalRenderVisibility ()
279 Surface *surface = GetSurface ();
280 if (surface)
281 surface->AddDirtyElement (this, DirtyRenderVisibility);
284 void
285 UIElement::UpdateTotalHitTestVisibility ()
287 VisualTreeWalker walker (this);
288 while (UIElement *child = walker.Step ())
289 child->UpdateTotalHitTestVisibility ();
291 if (GetSurface())
292 GetSurface ()->AddDirtyElement (this, DirtyHitTestVisibility);
295 bool
296 UIElement::GetActualTotalRenderVisibility ()
298 bool visible = (flags & UIElement::RENDER_VISIBLE) != 0;
299 bool parent_visible = true;
301 total_opacity = GetOpacity ();
303 if (GetVisualParent ()) {
304 GetVisualParent ()->ComputeTotalRenderVisibility ();
305 parent_visible = visible && GetVisualParent ()->GetRenderVisible ();
306 total_opacity *= GetVisualParent ()->total_opacity;
309 visible = visible && parent_visible;
311 return visible;
314 void
315 UIElement::ComputeTotalRenderVisibility ()
317 if (GetActualTotalRenderVisibility ())
318 flags |= UIElement::TOTAL_RENDER_VISIBLE;
319 else
320 flags &= ~UIElement::TOTAL_RENDER_VISIBLE;
323 bool
324 UIElement::GetActualTotalHitTestVisibility ()
326 bool visible = (flags & UIElement::HIT_TEST_VISIBLE) != 0;
328 if (visible && GetVisualParent ()) {
329 GetVisualParent ()->ComputeTotalHitTestVisibility ();
330 visible = visible && GetVisualParent ()->GetHitTestVisible ();
333 return visible;
336 void
337 UIElement::ComputeTotalHitTestVisibility ()
339 if (GetActualTotalHitTestVisibility ())
340 flags |= UIElement::TOTAL_HIT_TEST_VISIBLE;
341 else
342 flags &= ~UIElement::TOTAL_HIT_TEST_VISIBLE;
345 void
346 UIElement::UpdateTransform ()
348 InvalidateArrange ();
350 if (GetSurface()) {
351 GetSurface()->AddDirtyElement (this, DirtyLocalTransform);
355 void
356 UIElement::ComputeLocalTransform ()
358 Transform *transform = GetRenderTransform ();
359 Point transform_origin = GetTransformOrigin ();
360 cairo_matrix_t render;
362 cairo_matrix_init_identity (&render);
363 cairo_matrix_init_identity (&local_xform);
365 if (transform == NULL)
366 return;
368 transform->GetTransform (&render);
369 cairo_matrix_translate (&local_xform, transform_origin.x, transform_origin.y);
370 cairo_matrix_multiply (&local_xform, &render, &local_xform);
371 cairo_matrix_translate (&local_xform, -transform_origin.x, -transform_origin.y);
374 void
375 UIElement::TransformBounds (cairo_matrix_t *old, cairo_matrix_t *current)
377 Rect updated;
379 cairo_matrix_t tween = *old;
380 cairo_matrix_invert (&tween);
381 cairo_matrix_multiply (&tween, &tween, current);
383 Point p0 (0,0);
384 Point p1 (1,0);
385 Point p2 (1,1);
386 Point p3 (0,1);
388 p0 = p0 - p0.Transform (&tween);
389 p1 = p1 - p1.Transform (&tween);
390 p2 = p2 - p2.Transform (&tween);
391 p3 = p3 - p3.Transform (&tween);
393 if (p0 == p1 && p1 == p2 && p2 == p3) {
394 //printf ("shifting position\n");
395 ShiftPosition (bounds.GetTopLeft ().Transform (&tween));
396 return;
399 UpdateBounds ();
402 void
403 UIElement::ComputeTransform ()
405 cairo_matrix_t old = absolute_xform;
406 cairo_matrix_init_identity (&absolute_xform);
408 if (GetVisualParent () != NULL) {
409 absolute_xform = GetVisualParent ()->absolute_xform;
410 } else if (GetParent () != NULL && GetParent ()->Is (Type::POPUP)) {
411 // FIXME we shouldn't be examing a subclass type here but we'll do this
412 // for now to get popups in something approaching the right place while
413 // we figure out a cleaner way to handle it long term.
414 Popup *popup = (Popup *)GetParent ();
415 absolute_xform = popup->absolute_xform;
416 cairo_matrix_translate (&absolute_xform, popup->GetHorizontalOffset (), popup->GetVerticalOffset ());
419 cairo_matrix_multiply (&absolute_xform, &layout_xform, &absolute_xform);
420 cairo_matrix_multiply (&absolute_xform, &local_xform, &absolute_xform);
422 if (moonlight_flags & RUNTIME_INIT_USE_UPDATE_POSITION)
423 TransformBounds (&old, &absolute_xform);
424 else {
425 UpdateBounds ();
429 void
430 UIElement::ComputeBounds ()
432 g_warning ("UIElement:ComputeBounds has been called. The derived class %s should have overridden it.",
433 GetTypeName ());
436 void
437 UIElement::ShiftPosition (Point p)
439 bounds.x = p.x;
440 bounds.y = p.y;
443 void
444 UIElement::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
446 if (prop && prop->GetId () == UIElement::RenderTransformProperty) {
447 UpdateTransform ();
449 else if (prop && prop->GetId () == UIElement::ClipProperty) {
450 InvalidateClip ();
452 else if (prop && prop->GetId () == UIElement::OpacityMaskProperty) {
453 InvalidateMask ();
456 DependencyObject::OnSubPropertyChanged (prop, obj, subobj_args);
459 void
460 UIElement::CacheInvalidateHint ()
462 VisualTreeWalker walker (this);
463 while (UIElement *child = walker.Step ())
464 child->CacheInvalidateHint ();
467 void
468 UIElement::SetVisualParent (UIElement *visual_parent)
470 this->visual_parent = visual_parent;
472 if (visual_parent && visual_parent->GetSurface () != GetSurface())
473 SetSurface (visual_parent->GetSurface());
476 void
477 UIElement::SetSubtreeObject (DependencyObject *value)
479 if (subtree_object == value)
480 return;
482 if (subtree_object)
483 subtree_object->unref ();
485 subtree_object = value;
487 if (subtree_object)
488 subtree_object->ref ();
491 void
492 UIElement::ElementRemoved (UIElement *item)
494 // Invalidate ourself in the size of the item's subtree
495 Invalidate (item->GetSubtreeBounds());
497 if (GetSurface ())
498 GetSurface()->RemoveDirtyElement (item);
499 item->SetVisualParent (NULL);
500 item->CacheInvalidateHint ();
501 item->ClearLoaded ();
503 InvalidateMeasure ();
506 void
507 UIElement::ElementAdded (UIElement *item)
509 item->SetVisualLevel (GetVisualLevel() + 1);
510 item->SetVisualParent (this);
511 item->UpdateTotalRenderVisibility ();
512 item->UpdateTotalHitTestVisibility ();
513 //item->UpdateBounds (true);
514 item->Invalidate ();
516 if (0 != (flags & (UIElement::IS_LOADED | UIElement::PENDING_LOADED))) {
517 bool delay = false;
518 List *load_list = item->WalkTreeForLoaded (&delay);
520 // if we're loaded, key the delay state off of the
521 // WalkTreeForLoaded call.
523 // if we're actually pending loaded, forcibly delay
524 // the new element's Loaded firing as well.
526 if (0 != (flags & UIElement::PENDING_LOADED))
527 delay = true;
529 if (delay) {
530 PostSubtreeLoad (load_list);
531 // PostSubtreeLoad will take care of deleting the list for us.
533 else {
534 EmitSubtreeLoad (load_list);
535 delete load_list;
539 UpdateBounds (true);
541 InvalidateMeasure ();
542 item->SetRenderSize (Size (0,0));
543 item->UpdateTransform ();
544 item->InvalidateMeasure ();
545 item->InvalidateArrange ();
548 void
549 UIElement::InvalidateMeasure ()
551 dirty_flags |= DirtyMeasure;
553 Surface *surface;
554 if ((surface = GetSurface ()))
555 surface->needs_measure = true;
559 void
560 UIElement::InvalidateArrange ()
562 dirty_flags |= DirtyArrange;
564 Surface *surface;
565 if ((surface = GetSurface ()))
566 surface->needs_arrange = true;
569 void
570 UIElement::DoMeasure ()
572 Size *last = LayoutInformation::GetPreviousConstraint (this);
573 UIElement *parent = GetVisualParent ();
574 Size infinite (INFINITY, INFINITY);
576 if (!GetSurface () && !last && !parent && IsLayoutContainer ()) {
577 last = &infinite;
580 if (last) {
581 Size previous_desired = GetDesiredSize ();
583 // This will be a noop on non layout elements
584 Measure (*last);
586 if (previous_desired == GetDesiredSize ())
587 return;
590 // a canvas doesn't care about the child size changing like this
591 if (parent && (!parent->Is (Type::CANVAS) || IsLayoutContainer ()))
592 parent->InvalidateMeasure ();
594 dirty_flags &= ~DirtyMeasure;
597 void
598 UIElement::DoArrange ()
600 Rect *last = LayoutInformation::GetLayoutSlot (this);
601 Size previous_render = Size ();
602 UIElement *parent = GetVisualParent ();
603 Rect viewport;
605 if (!parent) {
606 Size desired = Size ();
607 Size available = Size ();
608 Surface *surface = GetSurface ();
610 if (IsLayoutContainer ()) {
611 desired = GetDesiredSize ();
612 if (surface && this == surface->GetToplevel ()) {
613 Size *measure = LayoutInformation::GetPreviousConstraint (this);
614 if (measure)
615 desired = desired.Max (*LayoutInformation::GetPreviousConstraint (this));
616 else
617 desired = Size (surface->GetWindow ()->GetWidth (), surface->GetWindow ()->GetHeight ());
619 } else {
620 FrameworkElement *fe = (FrameworkElement*)this;
621 desired = Size (fe->GetActualWidth (), fe->GetActualHeight ());
624 viewport = Rect (Canvas::GetLeft (this),
625 Canvas::GetTop (this),
626 desired.width, desired.height);
628 last = &viewport;
631 if (last) {
632 previous_render = GetRenderSize ();
633 Arrange (*last);
635 if (previous_render == GetRenderSize ())
636 return;
639 if (parent && (!parent->Is (Type::CANVAS) || (IsLayoutContainer () || !last)))
640 parent->InvalidateArrange ();
642 if (!last)
643 return;
645 LayoutInformation::SetLastRenderSize (this, &previous_render);
648 bool
649 UIElement::InsideClip (cairo_t *cr, double x, double y)
651 Geometry* clip;
652 bool ret = false;
653 double nx = x;
654 double ny = y;
656 clip = GetClip ();
658 if (clip == NULL) {
659 return true;
662 TransformPoint (&nx, &ny);
663 if (!clip->GetBounds ().PointInside (nx, ny))
664 return false;
666 cairo_save (cr);
667 clip->Draw (cr);
669 if (cairo_in_fill (cr, nx, ny))
670 ret = true;
672 cairo_new_path (cr);
673 cairo_restore (cr);
675 return ret;
678 bool
679 UIElement::InsideObject (cairo_t *cr, double x, double y)
681 return InsideClip (cr, x, y);
684 class LoadedState : public EventObject {
685 public:
686 LoadedState (List *list) { this->list = list; }
687 ~LoadedState () { delete list; }
689 List* GetUIElementList () { return list; }
691 private:
692 List* list;
695 void
696 UIElement::emit_delayed_loaded (EventObject *data)
698 LoadedState *state = (LoadedState *)data;
700 EmitSubtreeLoad (state->GetUIElementList ());
703 void
704 UIElement::EmitSubtreeLoad (List *l)
706 while (UIElementNode* node = (UIElementNode*)l->First()) {
707 l->Unlink (node);
709 node->uielement->OnLoaded ();
711 delete node;
715 void
716 UIElement::PostSubtreeLoad (List *load_list)
718 LoadedState *state = new LoadedState (load_list);
720 GetDeployment()->GetSurface()->GetTimeManager()->AddTickCall (emit_delayed_loaded, state);
722 state->unref ();
725 List*
726 UIElement::WalkTreeForLoaded (bool *delay)
728 List *walk_list = new List();
729 List *load_list = new List();
731 if (delay)
732 *delay = false;
734 walk_list->Append (new UIElementNode (this));
736 while (UIElementNode *next = (UIElementNode*)walk_list->First ()) {
737 // remove it from the walk list
738 walk_list->Unlink (next);
740 if (next->uielement->Is(Type::CONTROL)) {
741 Control *control = (Control*)next->uielement;
742 if (!control->default_style_applied) {
743 ManagedTypeInfo *key = control->GetDefaultStyleKey ();
744 if (key) {
745 if (Application::GetCurrent () == NULL)
746 g_warning ("attempting to use a null application when applying default style when emitting Loaded event.");
747 else
748 Application::GetCurrent()->ApplyDefaultStyle (control, key);
752 if (control->GetStyle() || control->default_style_applied)
753 if (delay)
754 *delay = true;
758 // add it to the front of the load list, and mark it as PENDING_LOADED
759 next->uielement->flags |= UIElement::PENDING_LOADED;
760 load_list->Prepend (next);
762 // and add its children to the front of the walk list
763 VisualTreeWalker walker (next->uielement);
764 while (UIElement *child = walker.Step ())
765 walk_list->Prepend (new UIElementNode (child));
768 delete walk_list;
770 return load_list;
774 void
775 UIElement::OnLoaded ()
777 if (emitting_loaded || IsLoaded())
778 return;
780 emitting_loaded = true;
782 flags |= UIElement::IS_LOADED;
784 flags &= ~UIElement::PENDING_LOADED;
786 Emit (LoadedEvent, NULL, true);
788 emitting_loaded = false;
791 void
792 UIElement::ClearLoaded ()
794 UIElement *e = NULL;
795 Surface *s = Deployment::GetCurrent ()->GetSurface ();
796 if (s->GetFocusedElement () == this)
797 s->FocusElement (NULL);
799 if (!IsLoaded ())
800 return;
802 flags &= ~UIElement::IS_LOADED;
804 Emit (UnloadedEvent, NULL, true);
805 VisualTreeWalker walker (this);
806 while ((e = walker.Step ()))
807 e->ClearLoaded ();
810 bool
811 UIElement::Focus (bool recurse)
813 return false;
817 // Queues the invalidate for the current region, performs any
818 // updates to the RenderTransform (optional) and queues a
819 // new redraw with the new bounding box
821 void
822 UIElement::FullInvalidate (bool rendertransform)
824 Invalidate ();
825 if (rendertransform)
826 UpdateTransform ();
827 UpdateBounds (true /* force an invalidate here, even if the bounds don't change */);
830 void
831 UIElement::Invalidate (Rect r)
833 if (!GetRenderVisible() || IS_INVISIBLE(total_opacity))
834 return;
836 #ifdef DEBUG_INVALIDATE
837 printf ("Requesting invalidate for object %p %s (%s) at %f %f - %f %f\n",
838 this, GetName(), GetTypeName(),
839 r.x, r.y,
840 r.w, r.h);
841 #endif
844 if (GetSurface ()) {
845 GetSurface()->AddDirtyElement (this, DirtyInvalidate);
847 dirty_region->Union (r);
849 GetSurface()->GetTimeManager()->NeedRedraw ();
851 Emit (InvalidatedEvent);
855 void
856 UIElement::Invalidate (Region *region)
858 if (!GetRenderVisible () || IS_INVISIBLE (total_opacity))
859 return;
861 if (GetSurface ()) {
862 GetSurface()->AddDirtyElement (this, DirtyInvalidate);
864 dirty_region->Union (region);
866 GetSurface()->GetTimeManager()->NeedRedraw ();
868 Emit (InvalidatedEvent);
872 void
873 UIElement::Invalidate ()
875 Invalidate (bounds);
879 void
880 UIElement::InvalidatePaint ()
882 Invalidate ();
886 void
887 UIElement::InvalidateSubtreePaint ()
889 Invalidate (GetSubtreeBounds ());
892 void
893 UIElement::InvalidateClip ()
895 InvalidateSubtreePaint ();
896 UpdateBounds (true);
899 void
900 UIElement::InvalidateMask ()
902 InvalidateSubtreePaint ();
905 void
906 UIElement::InvalidateVisibility ()
908 UpdateTotalRenderVisibility ();
909 InvalidateSubtreePaint ();
913 void
914 UIElement::InvalidateIntrisicSize ()
916 InvalidateMeasure ();
917 InvalidateArrange ();
918 UpdateBounds (true);
922 void
923 UIElement::HitTest (cairo_t *cr, Point p, List *uielement_list)
925 uielement_list->Prepend (new UIElementNode (this));
928 void
929 UIElement::HitTest (cairo_t *cr, Rect r, List *uielement_list)
933 void
934 UIElement::FindElementsInHostCoordinates_p (Point p, HitTestCollection *uielement_list)
936 List *list = new List ();
937 cairo_t *ctx = measuring_context_create ();
939 FindElementsInHostCoordinates (ctx, p, list);
941 UIElementNode *node = (UIElementNode *) list->First ();
942 while (node) {
943 uielement_list->Add (new Value (node->uielement));
944 node = (UIElementNode *) node->next;
947 delete list;
948 measuring_context_destroy (ctx);
952 void
953 UIElement::FindElementsInHostCoordinates (cairo_t *cr, Point P, List *uielement_list)
955 uielement_list->Prepend (new UIElementNode (this));
959 void
960 UIElement::FindElementsInHostCoordinates_r (Rect r, HitTestCollection *uielement_list)
962 List *list = new List ();
963 cairo_t *ctx = measuring_context_create ();
965 FindElementsInHostCoordinates (ctx, r, list);
967 UIElementNode *node = (UIElementNode *) list->First ();
968 while (node) {
969 uielement_list->Add (new Value (node->uielement));
970 node = (UIElementNode *) node->next;
973 delete list;
974 measuring_context_destroy (ctx);
977 void
978 UIElement::FindElementsInHostCoordinates (cairo_t *cr, Rect r, List *uielement_list)
980 uielement_list->Prepend (new UIElementNode (this));
983 bool
984 UIElement::EmitKeyDown (GdkEventKey *event)
986 return Emit (KeyDownEvent, new KeyEventArgs (event));
989 bool
990 UIElement::EmitKeyUp (GdkEventKey *event)
992 return Emit (KeyUpEvent, new KeyEventArgs (event));
995 bool
996 UIElement::EmitGotFocus ()
998 return Emit (GotFocusEvent, new EventArgs ());
1001 bool
1002 UIElement::EmitLostFocus ()
1004 return Emit (LostFocusEvent, new EventArgs ());
1007 bool
1008 UIElement::EmitLostMouseCapture ()
1010 MouseEventArgs *e = new MouseEventArgs ();
1011 e->SetSource (this);
1012 return Emit (LostMouseCaptureEvent, e);
1015 bool
1016 UIElement::CaptureMouse ()
1018 Surface *s = GetSurface ();
1019 if (s == NULL)
1020 return false;
1022 return s->SetMouseCapture (this);
1025 void
1026 UIElement::ReleaseMouseCapture ()
1028 Surface *s = GetSurface ();
1029 if (s == NULL)
1030 return;
1032 s->ReleaseMouseCapture (this);
1035 void
1036 UIElement::DoRender (cairo_t *cr, Region *parent_region)
1038 Region *region = new Region (GetSubtreeBounds ());
1039 region->Intersect (parent_region);
1041 if (!GetRenderVisible() || IS_INVISIBLE (total_opacity) || region->IsEmpty ()) {
1042 delete region;
1043 return;
1046 #if FRONT_TO_BACK_STATS
1047 GetSurface()->uielements_rendered_back_to_front ++;
1048 #endif
1050 STARTTIMER (UIElement_render, Type::Find (GetObjectType())->name);
1052 PreRender (cr, region, false);
1054 Render (cr, region);
1056 PostRender (cr, region, false);
1058 ENDTIMER (UIElement_render, Type::Find (GetObjectType())->name);
1060 delete region;
1063 bool
1064 UIElement::UseBackToFront ()
1066 return VisualTreeWalker (this).GetCount () < MIN_FRONT_TO_BACK_COUNT;
1069 void
1070 UIElement::FrontToBack (Region *surface_region, List *render_list)
1072 double local_opacity = GetOpacity ();
1074 if (surface_region->RectIn (GetSubtreeBounds().RoundOut()) == GDK_OVERLAP_RECTANGLE_OUT)
1075 return;
1077 if (!GetRenderVisible ()
1078 || IS_INVISIBLE (local_opacity))
1079 return;
1081 if (!UseBackToFront ()) {
1082 Region *self_region = new Region (surface_region);
1083 self_region->Intersect (GetSubtreeBounds().RoundOut());
1085 // we need to include our children in this one, since
1086 // we'll be rendering them in the PostRender method.
1087 if (!self_region->IsEmpty())
1088 render_list->Prepend (new RenderNode (this, self_region, true,
1089 UIElement::CallPreRender, UIElement::CallPostRender));
1090 // don't remove the region from surface_region because
1091 // there are likely holes in it
1092 return;
1095 Region *region;
1096 bool delete_region;
1097 bool can_subtract_self;
1099 if (!GetClip ()
1100 && !GetOpacityMask ()
1101 && !IS_TRANSLUCENT (GetOpacity ())) {
1102 region = surface_region;
1103 delete_region = false;
1104 can_subtract_self = true;
1106 else {
1107 region = new Region (surface_region);
1108 delete_region = true;
1109 can_subtract_self = false;
1112 RenderNode *cleanup_node = new RenderNode (this, NULL, false, NULL, UIElement::CallPostRender);
1114 render_list->Prepend (cleanup_node);
1116 Region *self_region = new Region (region);
1118 VisualTreeWalker walker (this, ZReverse);
1119 while (UIElement *child = walker.Step ())
1120 child->FrontToBack (region, render_list);
1122 if (!GetOpacityMask () && !IS_TRANSLUCENT (local_opacity)) {
1123 delete self_region;
1124 if (GetRenderBounds().IsEmpty ()) { // empty bounds mean that this element draws nothing itself
1125 self_region = new Region ();
1127 else {
1128 self_region = new Region (region);
1129 self_region->Intersect (GetRenderBounds().RoundOut ()); // note the RoundOut
1131 } else {
1132 self_region->Intersect (GetSubtreeBounds().RoundOut ()); // note the RoundOut
1135 if (self_region->IsEmpty() && render_list->First() == cleanup_node) {
1136 /* we don't intersect the surface region, and none of
1137 our children did either, remove the cleanup node */
1138 render_list->Remove (render_list->First());
1139 delete self_region;
1140 if (delete_region)
1141 delete region;
1142 return;
1145 render_list->Prepend (new RenderNode (this, self_region, !self_region->IsEmpty(), UIElement::CallPreRender, NULL));
1147 if (!self_region->IsEmpty()) {
1148 if (((absolute_xform.yx == 0 && absolute_xform.xy == 0) /* no skew/rotation */
1149 || (absolute_xform.xx == 0 && absolute_xform.yy == 0)) /* allow 90 degree rotations */
1150 && can_subtract_self)
1151 region->Subtract (GetCoverageBounds ());
1154 if (delete_region)
1155 delete region;
1158 void
1159 UIElement::PreRender (cairo_t *cr, Region *region, bool skip_children)
1161 double local_opacity = GetOpacity ();
1163 cairo_save (cr);
1165 cairo_set_matrix (cr, &absolute_xform);
1166 RenderClipPath (cr);
1168 if (opacityMask || IS_TRANSLUCENT (local_opacity)) {
1169 Rect r = GetSubtreeBounds ().RoundOut();
1170 cairo_identity_matrix (cr);
1172 // we need this check because ::PreRender can (and
1173 // will) be called for elements with empty regions.
1175 // The region passed in here is the redraw region
1176 // intersected with the render bounds of a given
1177 // element. For Panels with no width/height specified
1178 // in the xaml, this region will be empty. (check
1179 // panel.cpp::FrontToBack - we insert the ::PreRender
1180 // calling node if either the panel background or any
1181 // of the children intersect the redraw region.) We
1182 // can't clip to the empty region, obviously, as it
1183 // will keep all descendents from drawing to the
1184 // screen.
1186 if (!region->IsEmpty()) {
1187 region->Draw (cr);
1188 cairo_clip (cr);
1190 r.Draw (cr);
1191 cairo_clip (cr);
1193 cairo_set_matrix (cr, &absolute_xform);
1196 if (ClipToExtents ()) {
1197 extents.Draw (cr);
1198 cairo_clip (cr);
1202 if (IS_TRANSLUCENT (local_opacity))
1203 cairo_push_group (cr);
1205 if (opacityMask != NULL)
1206 cairo_push_group (cr);
1209 void
1210 UIElement::PostRender (cairo_t *cr, Region *region, bool front_to_back)
1212 // if we didn't render front to back, then render the children here
1213 if (!front_to_back) {
1214 VisualTreeWalker walker (this, ZForward);
1215 while (UIElement *child = walker.Step ())
1216 child->DoRender (cr, region);
1219 double local_opacity = GetOpacity ();
1221 if (opacityMask != NULL) {
1222 cairo_pattern_t *mask;
1223 cairo_pattern_t *data = cairo_pop_group (cr);
1224 Point p = GetOriginPoint ();
1225 Rect area = Rect (p.x, p.y, 0.0, 0.0);
1226 GetSizeForBrush (cr, &(area.width), &(area.height));
1227 opacityMask->SetupBrush (cr, area);
1228 mask = cairo_get_source (cr);
1229 cairo_pattern_reference (mask);
1230 cairo_set_source (cr, data);
1231 cairo_mask (cr, mask);
1232 cairo_pattern_destroy (mask);
1233 cairo_pattern_destroy (data);
1236 if (IS_TRANSLUCENT (local_opacity)) {
1237 cairo_pop_group_to_source (cr);
1238 cairo_paint_with_alpha (cr, local_opacity);
1241 cairo_restore (cr);
1243 if (moonlight_flags & RUNTIME_INIT_SHOW_CLIPPING) {
1244 cairo_save (cr);
1245 cairo_new_path (cr);
1246 cairo_set_matrix (cr, &absolute_xform);
1247 cairo_set_line_width (cr, 1);
1249 Geometry *geometry = GetClip ();
1250 if (geometry) {
1251 geometry->Draw (cr);
1252 cairo_set_source_rgba (cr, 0.0, 1.0, 1.0, 1.0);
1253 cairo_stroke (cr);
1256 cairo_restore (cr);
1259 if (moonlight_flags & RUNTIME_INIT_SHOW_BOUNDING_BOXES) {
1260 cairo_save (cr);
1261 cairo_new_path (cr);
1262 //RenderClipPath (cr);
1263 cairo_identity_matrix (cr);
1264 cairo_set_source_rgba (cr, 1.0, 0.5, 0.2, 1.0);
1265 cairo_set_line_width (cr, 1);
1266 cairo_rectangle (cr, bounds.x + .5, bounds.y + .5, bounds.width - .5, bounds.height - .5);
1267 cairo_stroke (cr);
1268 cairo_restore (cr);
1272 void
1273 UIElement::Paint (cairo_t *ctx, Region *region, cairo_matrix_t *xform)
1275 // FIXME xform is ignored for now
1276 if (xform)
1277 g_warning ("passing a transform to UIElement::Paint is not yet supported");
1279 #if FRONT_TO_BACK_STATS
1280 uielements_rendered_front_to_back = 0;
1281 uielements_rendered_back_to_front = 0;
1282 #endif
1284 bool did_front_to_back = false;
1285 List *render_list = new List ();
1286 Region *copy = new Region (region);
1288 if (moonlight_flags & RUNTIME_INIT_RENDER_FRONT_TO_BACK) {
1289 FrontToBack (copy, render_list);
1291 if (!render_list->IsEmpty ()) {
1292 while (RenderNode *node = (RenderNode*)render_list->First()) {
1293 #if FRONT_TO_BACK_STATS
1294 uielements_rendered_front_to_back ++;
1295 #endif
1296 node->Render (ctx);
1298 render_list->Remove (node);
1301 did_front_to_back = true;
1304 delete render_list;
1305 delete copy;
1308 if (!did_front_to_back) {
1309 DoRender (ctx, region);
1312 #if FRONT_TO_BACK_STATS
1313 printf ("UIElements rendered front-to-back for: %s(%p)\n", uielements_rendered_front_to_back, GetName (), this);
1314 printf ("UIElements rendered back-to-front for: %s(%p)\n", uielements_rendered_back_to_front, GetName (), this);
1315 #endif
1318 void
1319 UIElement::CallPreRender (cairo_t *cr, UIElement *element, Region *region, bool front_to_back)
1321 element->PreRender (cr, region, front_to_back);
1324 void
1325 UIElement::CallPostRender (cairo_t *cr, UIElement *element, Region *region, bool front_to_back)
1327 element->PostRender (cr, region, front_to_back);
1330 void
1331 UIElement::Render (cairo_t *cr, Region *region, bool path_only)
1333 /* do nothing by default */
1336 void
1337 UIElement::GetSizeForBrush (cairo_t *cr, double *width, double *height)
1339 g_warning ("UIElement:GetSizeForBrush has been called. The derived class %s should have overridden it.",
1340 GetTypeName ());
1341 *height = *width = 0.0;
1344 TimeManager *
1345 UIElement::GetTimeManager ()
1347 Surface *surface = GetSurface ();
1348 Deployment *deployment;
1350 if (surface == NULL) {
1351 deployment = GetDeployment ();
1352 if (deployment != NULL)
1353 surface = deployment->GetSurface ();
1356 return surface ? surface->GetTimeManager() : NULL;
1359 GeneralTransform *
1360 UIElement::GetTransformToUIElementWithError (UIElement *to_element, MoonError *error)
1362 /* walk from this up to the root. if we hit null before we hit the toplevel, it's an error */
1363 UIElement *visual = this;
1364 bool ok = false;
1366 if (visual && GetSurface()) {
1367 while (visual) {
1368 if (GetSurface()->IsTopLevel (visual))
1369 ok = true;
1370 visual = visual->GetVisualParent ();
1374 if (!ok || (to_element && !to_element->GetSurface ())) {
1375 MoonError::FillIn (error, MoonError::ARGUMENT, 1001,
1376 "visual");
1377 return NULL;
1380 if (to_element && !to_element->GetSurface()->IsTopLevel (to_element)) {
1381 /* if @to_element is specified we also need to make sure there's a path to the root from it */
1382 ok = false;
1383 visual = to_element->GetVisualParent ();
1384 if (visual && to_element->GetSurface()) {
1385 while (visual) {
1386 if (to_element->GetSurface()->IsTopLevel (visual))
1387 ok = true;
1388 visual = visual->GetVisualParent ();
1392 if (!ok) {
1393 MoonError::FillIn (error, MoonError::ARGUMENT, 1001,
1394 "visual");
1395 return NULL;
1399 // Force a dirty pass to make sure the elements are in their proper positions.
1400 GetSurface ()->ProcessDirtyElements ();
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);