2009-12-03 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / uielement.cpp
blobebd313162f00eb4bca190efdf866d44c3923d95f
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 hidden_desire = Size (-INFINITY, -INFINITY);
48 bounds = Rect (0,0,0,0);
49 cairo_matrix_init_identity (&absolute_xform);
50 cairo_matrix_init_identity (&layout_xform);
51 cairo_matrix_init_identity (&local_xform);
53 emitting_loaded = false;
54 dirty_flags = DirtyMeasure;
55 up_dirty_node = down_dirty_node = NULL;
56 force_invalidate_of_new_bounds = false;
57 dirty_region = new Region ();
59 desired_size = Size (0, 0);
60 render_size = Size (0, 0);
62 ComputeLocalTransform ();
63 ComputeTotalRenderVisibility ();
64 ComputeTotalHitTestVisibility ();
67 UIElement::~UIElement()
69 delete dirty_region;
72 bool
73 UIElement::IsSubtreeLoaded (UIElement *element)
75 while (element && !element->IsLoaded ())
76 element = element->GetVisualParent ();
77 return element;
80 void
81 UIElement::Dispose()
83 TriggerCollection *triggers = GetTriggers ();
85 if (triggers != NULL) {
86 for (int i = 0; i < triggers->GetCount (); i++)
87 triggers->GetValueAt (i)->AsEventTrigger ()->RemoveTarget (this);
90 if (!IsDisposed ()) {
91 VisualTreeWalker walker (this);
92 while (UIElement *child = walker.Step ())
93 child->SetVisualParent (NULL);
96 if (subtree_object) {
97 subtree_object->unref ();
98 subtree_object = NULL;
101 DependencyObject::Dispose();
104 void
105 UIElement::SetIsAttached (bool value)
107 if (IsAttached () == value)
108 return;
110 if (!value && IsAttached ()) {
111 /* we're losing our surface, delete ourselves from the dirty list if we're on it */
112 Surface *surface = GetDeployment ()->GetSurface ();
113 if (surface)
114 surface->RemoveDirtyElement (this);
117 if (subtree_object != NULL && subtree_object->Is(Type::UIELEMENT))
118 subtree_object->SetIsAttached (value);
120 DependencyObject::SetIsAttached (value);
123 Rect
124 UIElement::IntersectBoundsWithClipPath (Rect unclipped, bool transform)
126 Geometry *clip = GetClip ();
127 Geometry *layout_clip = transform ? NULL : LayoutInformation::GetLayoutClip (this);
128 Rect box;
130 if (!clip && !layout_clip)
131 return unclipped;
133 if (clip)
134 box = clip->GetBounds ();
135 else
136 box = layout_clip->GetBounds ();
138 if (layout_clip)
139 box = box.Intersection (layout_clip->GetBounds());
141 if (!GetRenderVisible())
142 box = Rect (0,0,0,0);
144 if (transform)
145 box = box.Transform (&absolute_xform);
147 return box.Intersection (unclipped);
150 void
151 UIElement::RenderClipPath (cairo_t *cr, bool path_only)
153 cairo_new_path (cr);
154 cairo_set_matrix (cr, &absolute_xform);
156 Geometry *geometry = GetClip();
157 if (!geometry)
158 return;
160 geometry->Draw (cr);
161 if (!path_only)
162 cairo_clip (cr);
165 void
166 UIElement::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
168 if (args->GetProperty ()->GetOwnerType() != Type::UIELEMENT) {
169 DependencyObject::OnPropertyChanged (args, error);
170 return;
173 if (args->GetId () == UIElement::OpacityProperty) {
174 InvalidateVisibility ();
175 } else if (args->GetId () == UIElement::VisibilityProperty) {
176 // note: invalid enum values are only validated in 1.1 (managed code),
177 // the default value for VisibilityProperty is VisibilityCollapsed
178 // (see bug #340799 for more details)
179 if (args->GetNewValue()->AsInt32() == VisibilityVisible)
180 flags |= UIElement::RENDER_VISIBLE;
181 else
182 flags &= ~UIElement::RENDER_VISIBLE;
184 InvalidateVisibility ();
185 InvalidateMeasure ();
186 if (GetVisualParent ())
187 GetVisualParent ()->InvalidateMeasure ();
188 } else if (args->GetId () == UIElement::IsHitTestVisibleProperty) {
189 if (args->GetNewValue()->AsBool())
190 flags |= UIElement::HIT_TEST_VISIBLE;
191 else
192 flags &= ~UIElement::HIT_TEST_VISIBLE;
194 UpdateTotalHitTestVisibility();
195 } else if (args->GetId () == UIElement::ClipProperty) {
196 InvalidateClip ();
197 } else if (args->GetId () == UIElement::OpacityMaskProperty) {
198 opacityMask = args->GetNewValue() ? args->GetNewValue()->AsBrush() : NULL;
199 InvalidateMask ();
200 } else if (args->GetId () == UIElement::RenderTransformProperty
201 || args->GetId () == UIElement::RenderTransformOriginProperty) {
202 UpdateTransform ();
204 else if (args->GetId () == UIElement::TriggersProperty) {
205 if (args->GetOldValue()) {
206 // remove the old trigger targets
207 TriggerCollection *triggers = args->GetOldValue()->AsTriggerCollection();
208 for (int i = 0; i < triggers->GetCount (); i++)
209 triggers->GetValueAt (i)->AsEventTrigger ()->RemoveTarget (this);
212 if (args->GetNewValue()) {
213 // set the new ones
214 TriggerCollection *triggers = args->GetNewValue()->AsTriggerCollection();
215 for (int i = 0; i < triggers->GetCount (); i++)
216 triggers->GetValueAt (i)->AsEventTrigger ()->SetTarget (this);
218 } else if (args->GetId () == UIElement::UseLayoutRoundingProperty) {
219 InvalidateMeasure ();
220 InvalidateArrange ();
223 NotifyListenersOfPropertyChange (args, error);
226 void
227 UIElement::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
229 if (col == GetTriggers ()) {
230 switch (args->GetChangedAction()) {
231 case CollectionChangedActionReplace:
232 args->GetOldItem()->AsEventTrigger ()->RemoveTarget (this);
233 // fall thru to Add
234 case CollectionChangedActionAdd:
235 args->GetNewItem()->AsEventTrigger ()->SetTarget (this);
236 break;
237 case CollectionChangedActionRemove:
238 args->GetOldItem()->AsEventTrigger ()->RemoveTarget (this);
239 break;
240 case CollectionChangedActionClearing:
241 for (int i = 0; i < col->GetCount (); i++)
242 col->GetValueAt (i)->AsEventTrigger ()->RemoveTarget (this);
243 break;
244 case CollectionChangedActionCleared:
245 // nothing needed here.
246 break;
249 else {
250 DependencyObject::OnCollectionChanged (col, args);
254 #if 1
256 UIElement::DumpHierarchy (UIElement *obj)
258 if (obj == NULL)
259 return 0;
261 int n = DumpHierarchy (obj->GetVisualParent ());
262 for (int i = 0; i < n; i++)
263 putchar (' ');
264 printf ("%s (%p)\n", obj->GetTypeName(), obj);
265 return n + 4;
267 #endif
269 void
270 UIElement::UpdateBounds (bool force_redraw)
272 //InvalidateMeasure ();
273 //InvalidateArrange ();
275 if (IsAttached ())
276 GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyBounds);
278 force_invalidate_of_new_bounds |= force_redraw;
281 void
282 UIElement::UpdateTotalRenderVisibility ()
284 if (IsAttached ())
285 GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyRenderVisibility);
288 void
289 UIElement::UpdateTotalHitTestVisibility ()
291 VisualTreeWalker walker (this);
292 while (UIElement *child = walker.Step ())
293 child->UpdateTotalHitTestVisibility ();
295 if (IsAttached ())
296 GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyHitTestVisibility);
299 bool
300 UIElement::GetActualTotalRenderVisibility ()
302 bool visible = (flags & UIElement::RENDER_VISIBLE) != 0;
303 bool parent_visible = true;
305 total_opacity = GetOpacity ();
307 if (GetVisualParent ()) {
308 GetVisualParent ()->ComputeTotalRenderVisibility ();
309 parent_visible = visible && GetVisualParent ()->GetRenderVisible ();
310 total_opacity *= GetVisualParent ()->total_opacity;
313 visible = visible && parent_visible;
315 return visible;
318 void
319 UIElement::ComputeTotalRenderVisibility ()
321 if (GetActualTotalRenderVisibility ())
322 flags |= UIElement::TOTAL_RENDER_VISIBLE;
323 else
324 flags &= ~UIElement::TOTAL_RENDER_VISIBLE;
327 bool
328 UIElement::GetActualTotalHitTestVisibility ()
330 bool visible = (flags & UIElement::HIT_TEST_VISIBLE) != 0;
332 if (visible && GetVisualParent ()) {
333 GetVisualParent ()->ComputeTotalHitTestVisibility ();
334 visible = visible && GetVisualParent ()->GetHitTestVisible ();
337 return visible;
340 void
341 UIElement::ComputeTotalHitTestVisibility ()
343 if (GetActualTotalHitTestVisibility ())
344 flags |= UIElement::TOTAL_HIT_TEST_VISIBLE;
345 else
346 flags &= ~UIElement::TOTAL_HIT_TEST_VISIBLE;
349 void
350 UIElement::UpdateTransform ()
352 if (IsAttached ()) {
353 GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyLocalTransform);
357 void
358 UIElement::ComputeLocalTransform ()
360 Transform *transform = GetRenderTransform ();
361 Point transform_origin = GetTransformOrigin ();
362 cairo_matrix_t render;
364 cairo_matrix_init_identity (&render);
365 cairo_matrix_init_identity (&local_xform);
367 if (transform == NULL)
368 return;
370 transform->GetTransform (&render);
371 cairo_matrix_translate (&local_xform, transform_origin.x, transform_origin.y);
372 cairo_matrix_multiply (&local_xform, &render, &local_xform);
373 cairo_matrix_translate (&local_xform, -transform_origin.x, -transform_origin.y);
376 void
377 UIElement::TransformBounds (cairo_matrix_t *old, cairo_matrix_t *current)
379 Rect updated;
381 cairo_matrix_t tween = *old;
382 cairo_matrix_invert (&tween);
383 cairo_matrix_multiply (&tween, &tween, current);
385 Point p0 (0,0);
386 Point p1 (1,0);
387 Point p2 (1,1);
388 Point p3 (0,1);
390 p0 = p0 - p0.Transform (&tween);
391 p1 = p1 - p1.Transform (&tween);
392 p2 = p2 - p2.Transform (&tween);
393 p3 = p3 - p3.Transform (&tween);
395 if (p0 == p1 && p1 == p2 && p2 == p3) {
396 //printf ("shifting position\n");
397 ShiftPosition (bounds.GetTopLeft ().Transform (&tween));
398 return;
401 UpdateBounds ();
404 void
405 UIElement::ComputeTransform ()
407 cairo_matrix_t old = absolute_xform;
408 cairo_matrix_init_identity (&absolute_xform);
410 if (GetVisualParent () != NULL) {
411 absolute_xform = GetVisualParent ()->absolute_xform;
412 } else if (GetParent () != NULL && GetParent ()->Is (Type::POPUP)) {
413 // FIXME we shouldn't be examing a subclass type here but we'll do this
414 // for now to get popups in something approaching the right place while
415 // we figure out a cleaner way to handle it long term.
416 Popup *popup = (Popup *)GetParent ();
417 absolute_xform = popup->absolute_xform;
418 cairo_matrix_translate (&absolute_xform, popup->GetHorizontalOffset (), popup->GetVerticalOffset ());
421 cairo_matrix_multiply (&absolute_xform, &layout_xform, &absolute_xform);
422 cairo_matrix_multiply (&absolute_xform, &local_xform, &absolute_xform);
424 if (moonlight_flags & RUNTIME_INIT_USE_UPDATE_POSITION)
425 TransformBounds (&old, &absolute_xform);
426 else {
427 UpdateBounds ();
431 void
432 UIElement::ComputeBounds ()
434 g_warning ("UIElement:ComputeBounds has been called. The derived class %s should have overridden it.",
435 GetTypeName ());
438 void
439 UIElement::ShiftPosition (Point p)
441 bounds.x = p.x;
442 bounds.y = p.y;
445 void
446 UIElement::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
448 if (prop && prop->GetId () == UIElement::RenderTransformProperty) {
449 UpdateTransform ();
451 else if (prop && prop->GetId () == UIElement::ClipProperty) {
452 InvalidateClip ();
454 else if (prop && prop->GetId () == UIElement::OpacityMaskProperty) {
455 InvalidateMask ();
458 DependencyObject::OnSubPropertyChanged (prop, obj, subobj_args);
461 void
462 UIElement::CacheInvalidateHint ()
464 VisualTreeWalker walker (this);
465 while (UIElement *child = walker.Step ())
466 child->CacheInvalidateHint ();
469 void
470 UIElement::SetVisualParent (UIElement *visual_parent)
472 this->visual_parent = visual_parent;
474 if (visual_parent && visual_parent->IsAttached () != IsAttached ())
475 SetIsAttached (visual_parent->IsAttached ());
478 void
479 UIElement::SetSubtreeObject (DependencyObject *value)
481 if (subtree_object == value)
482 return;
484 if (subtree_object)
485 subtree_object->unref ();
487 subtree_object = value;
489 if (subtree_object)
490 subtree_object->ref ();
493 void
494 UIElement::ElementRemoved (UIElement *item)
496 // Invalidate ourself in the size of the item's subtree
497 Invalidate (item->GetSubtreeBounds());
499 if (IsAttached ())
500 GetDeployment ()->GetSurface ()->RemoveDirtyElement (item);
501 item->SetVisualParent (NULL);
502 item->CacheInvalidateHint ();
503 item->ClearLoaded ();
505 Rect emptySlot (0,0,0,0);
506 LayoutInformation::SetLayoutSlot (item, &emptySlot);
507 item->ClearValue (LayoutInformation::LayoutClipProperty);
509 InvalidateMeasure ();
512 void
513 UIElement::ElementAdded (UIElement *item)
515 item->SetVisualLevel (GetVisualLevel() + 1);
516 item->SetVisualParent (this);
517 item->UpdateTotalRenderVisibility ();
518 item->UpdateTotalHitTestVisibility ();
519 //item->UpdateBounds (true);
520 item->Invalidate ();
522 if (0 != (flags & (UIElement::IS_LOADED | UIElement::PENDING_LOADED))) {
523 InheritedPropertyValueProvider::PropagateInheritedPropertiesOnAddingToTree (item);
525 bool post = false;
527 item->WalkTreeForLoadedHandlers (&post, true, false);
529 if (post)
530 Deployment::GetCurrent()->PostLoaded ();
533 UpdateBounds (true);
535 InvalidateMeasure ();
536 ClearValue (LayoutInformation::LayoutClipProperty);
537 ClearValue (LayoutInformation::PreviousConstraintProperty);
538 item->SetRenderSize (Size (0,0));
539 item->UpdateTransform ();
540 item->InvalidateMeasure ();
541 item->InvalidateArrange ();
542 if (item->ReadLocalValue (LayoutInformation::LastRenderSizeProperty))
543 PropagateFlagUp (DIRTY_SIZE_HINT);
546 void
547 UIElement::InvalidateMeasure ()
549 dirty_flags |= DirtyMeasure;
550 PropagateFlagUp (DIRTY_MEASURE_HINT);
553 void
554 UIElement::InvalidateArrange ()
556 dirty_flags |= DirtyArrange;
557 PropagateFlagUp (DIRTY_ARRANGE_HINT);
560 void
561 UIElement::DoMeasure ()
563 Size *last = LayoutInformation::GetPreviousConstraint (this);
564 UIElement *parent = GetVisualParent ();
565 Size infinite (INFINITY, INFINITY);
567 if (!IsAttached () && !last && !parent && IsLayoutContainer ()) {
568 last = &infinite;
571 if (last) {
572 Size previous_desired = GetDesiredSize ();
574 // This will be a noop on non layout elements
575 Measure (*last);
577 if (previous_desired == GetDesiredSize ())
578 return;
581 // a canvas doesn't care about the child size changing like this
582 if (parent)
583 parent->InvalidateMeasure ();
585 dirty_flags &= ~DirtyMeasure;
588 void
589 UIElement::DoArrange ()
591 Rect *last = LayoutInformation::GetLayoutSlot (this);
592 Size previous_render = Size ();
593 UIElement *parent = GetVisualParent ();
594 Rect viewport;
596 if (!parent) {
597 Size desired = Size ();
598 Size available = Size ();
599 Surface *surface = GetDeployment ()->GetSurface ();
601 if (IsLayoutContainer ()) {
602 desired = GetDesiredSize ();
603 if (IsAttached () && surface->IsTopLevel (this) && !GetParent ()) {
604 Size *measure = LayoutInformation::GetPreviousConstraint (this);
605 if (measure)
606 desired = desired.Max (*LayoutInformation::GetPreviousConstraint (this));
607 else
608 desired = Size (surface->GetWindow ()->GetWidth (), surface->GetWindow ()->GetHeight ());
610 } else {
611 FrameworkElement *fe = (FrameworkElement*)this;
612 desired = Size (fe->GetActualWidth (), fe->GetActualHeight ());
615 viewport = Rect (Canvas::GetLeft (this),
616 Canvas::GetTop (this),
617 desired.width, desired.height);
619 last = &viewport;
622 if (last) {
623 Arrange (*last);
624 } else {
625 if (parent)
626 parent->InvalidateArrange ();
630 bool
631 UIElement::InsideClip (cairo_t *cr, double x, double y)
633 Geometry* clip;
634 bool inside = true;
635 double nx = x;
636 double ny = y;
638 clip = GetClip ();
639 if (!clip)
640 return true;
642 TransformPoint (&nx, &ny);
644 if (!clip->GetBounds ().PointInside (nx, ny))
645 return false;
647 cairo_save (cr);
648 cairo_new_path (cr);
650 clip->Draw (cr);
651 inside = cairo_in_fill (cr, nx, ny);
653 cairo_restore (cr);
655 return inside;
658 bool
659 UIElement::InsideObject (cairo_t *cr, double x, double y)
661 return InsideClip (cr, x, y);
665 UIElement::AddHandler (int event_id, EventHandler handler, gpointer data, GDestroyNotify data_dtor)
667 int rv = DependencyObject::AddHandler (event_id, handler, data, data_dtor);
668 if (event_id == UIElement::LoadedEvent) {
669 UIElement *el = this;
670 while (el && el->HasBeenWalkedForLoaded ()) {
671 el->ClearWalkedForLoaded ();
672 el = el->GetVisualParent ();
675 return rv;
678 void
679 UIElement::PropagateFlagUp (UIElementFlags flag)
681 SetFlag (flag);
682 UIElement *e = this;
683 while ((e = e->GetVisualParent ()) && !e->HasFlag (flag)) {
684 e->SetFlag (flag);
689 UIElement::RemoveHandler (int event_id, EventHandler handler, gpointer data)
691 int token = DependencyObject::RemoveHandler (event_id, handler, data);
693 if (event_id == UIElement::LoadedEvent && token != -1)
694 Deployment::GetCurrent()->RemoveLoadedHandler (this, token);
696 return token;
699 void
700 UIElement::RemoveHandler (int event_id, int token)
702 DependencyObject::RemoveHandler (event_id, token);
704 if (event_id == UIElement::LoadedEvent)
705 Deployment::GetCurrent()->RemoveLoadedHandler (this, token);
708 #if WALK_METRICS
709 int walk_count = 0;
710 #endif
712 void
713 UIElement::WalkTreeForLoadedHandlers (bool *post, bool only_unemitted, bool force_walk_up)
715 List *walk_list = new List();
716 List *subtree_list = new List ();
718 bool post_loaded = false;
719 Deployment *deployment = GetDeployment ();
720 Application *application = deployment->GetCurrentApplication ();
722 DeepTreeWalker *walker = new DeepTreeWalker (this);
724 // we need to make sure to apply the default style to all
725 // controls in the subtree
726 while (UIElement *element = (UIElement*)walker->Step ()) {
727 #if WALK_METRICS
728 walk_count ++;
729 #endif
731 if (element->HasBeenWalkedForLoaded ()) {
732 walker->SkipBranch ();
733 continue;
736 if (element->Is(Type::CONTROL)) {
737 Control *control = (Control*)element;
738 if (!control->default_style_applied) {
739 ManagedTypeInfo *key = control->GetDefaultStyleKey ();
740 if (key) {
741 if (application == NULL)
742 g_warning ("attempting to use a null application when applying default style when emitting Loaded event.");
743 else
744 application->ApplyDefaultStyle (control, key);
748 if (!control->GetTemplateRoot () /* we only need to worry about this if the template hasn't been expanded */
749 && control->GetTemplate())
750 post_loaded = true; //XXX do we need this? control->ReadLocalValue (Control::TemplateProperty) == NULL;
753 element->flags |= UIElement::PENDING_LOADED;
754 element->OnLoaded ();
755 if (element->HasHandlers (UIElement::LoadedEvent)) {
756 post_loaded = true;
757 subtree_list->Prepend (new UIElementNode (element));
759 element->SetWalkedForLoaded ();
762 if (force_walk_up || !post_loaded || HasHandlers (UIElement::LoadedEvent)) {
763 // we need to walk back up to the root to collect all loaded events
764 UIElement *parent = this;
765 while (parent->GetVisualParent())
766 parent = parent->GetVisualParent();
767 delete walker;
768 walker = new DeepTreeWalker (parent, Logical/*Reverse*/);
770 while (UIElement *element = (UIElement*)walker->Step ()) {
771 #if WALK_METRICS
772 walk_count ++;
773 #endif
775 if (element == this) {
776 // we already walked this, so add our subtree list here.
777 walk_list->Prepend (subtree_list);
778 subtree_list->Clear (false);
779 walker->SkipBranch ();
781 else if (element->HasBeenWalkedForLoaded ()) {
782 walker->SkipBranch ();
784 else {
785 walk_list->Prepend (new UIElementNode (element));
786 element->SetWalkedForLoaded ();
790 // if we didn't add the subtree's loaded handlers
791 // (because somewhere up above the subtree we skipped
792 // the branch) add it here.
793 if (walk_list->IsEmpty ()) {
794 walk_list->Prepend (subtree_list);
795 subtree_list->Clear (false);
798 else {
799 // otherwise we only copy the events from the subtree
800 walk_list->Prepend (subtree_list);
801 subtree_list->Clear (false);
804 while (UIElementNode *ui = (UIElementNode*)walk_list->First ()) {
805 // remove it from the walk list
806 walk_list->Unlink (ui);
808 deployment->AddAllLoadedHandlers (ui->uielement, only_unemitted);
810 delete ui;
813 if (post)
814 *post = post_loaded;
816 delete walker;
817 delete walk_list;
818 delete subtree_list;
822 void
823 UIElement::OnLoaded ()
825 flags |= UIElement::IS_LOADED;
826 flags &= ~UIElement::PENDING_LOADED;
829 void
830 UIElement::ClearLoaded ()
832 UIElement *e = NULL;
833 Surface *s = Deployment::GetCurrent ()->GetSurface ();
834 if (s->GetFocusedElement () == this)
835 s->FocusElement (NULL);
837 ClearForeachGeneration (UIElement::LoadedEvent);
838 ClearWalkedForLoaded ();
840 if (!IsLoaded ())
841 return;
843 flags &= ~UIElement::IS_LOADED;
845 VisualTreeWalker walker (this);
846 while ((e = walker.Step ()))
847 e->ClearLoaded ();
850 bool
851 UIElement::Focus (bool recurse)
853 return false;
857 // Queues the invalidate for the current region, performs any
858 // updates to the RenderTransform (optional) and queues a
859 // new redraw with the new bounding box
861 void
862 UIElement::FullInvalidate (bool rendertransform)
864 Invalidate ();
865 if (rendertransform)
866 UpdateTransform ();
867 UpdateBounds (true /* force an invalidate here, even if the bounds don't change */);
870 void
871 UIElement::Invalidate (Rect r)
873 if (!GetRenderVisible() || IS_INVISIBLE(total_opacity))
874 return;
876 #ifdef DEBUG_INVALIDATE
877 printf ("Requesting invalidate for object %p %s (%s) at %f %f - %f %f\n",
878 this, GetName(), GetTypeName(),
879 r.x, r.y,
880 r.w, r.h);
881 #endif
884 if (IsAttached ()) {
885 GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyInvalidate);
887 dirty_region->Union (r);
889 GetTimeManager()->NeedRedraw ();
891 Emit (InvalidatedEvent);
895 void
896 UIElement::Invalidate (Region *region)
898 if (!GetRenderVisible () || IS_INVISIBLE (total_opacity))
899 return;
901 if (IsAttached ()) {
902 GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyInvalidate);
904 dirty_region->Union (region);
906 GetTimeManager()->NeedRedraw ();
908 Emit (InvalidatedEvent);
912 void
913 UIElement::Invalidate ()
915 Invalidate (bounds);
919 void
920 UIElement::InvalidatePaint ()
922 Invalidate ();
926 void
927 UIElement::InvalidateSubtreePaint ()
929 Invalidate (GetSubtreeBounds ());
932 void
933 UIElement::InvalidateClip ()
935 InvalidateSubtreePaint ();
936 UpdateBounds (true);
939 void
940 UIElement::InvalidateMask ()
942 InvalidateSubtreePaint ();
945 void
946 UIElement::InvalidateVisibility ()
948 UpdateTotalRenderVisibility ();
949 InvalidateSubtreePaint ();
953 void
954 UIElement::InvalidateIntrisicSize ()
956 InvalidateMeasure ();
957 InvalidateArrange ();
958 UpdateBounds (true);
962 void
963 UIElement::HitTest (cairo_t *cr, Point p, List *uielement_list)
965 uielement_list->Prepend (new UIElementNode (this));
968 void
969 UIElement::HitTest (cairo_t *cr, Rect r, List *uielement_list)
973 void
974 UIElement::FindElementsInHostCoordinates_p (Point p, HitTestCollection *uielement_list)
976 List *list = new List ();
977 cairo_t *ctx = measuring_context_create ();
979 FindElementsInHostCoordinates (ctx, p, list);
981 UIElementNode *node = (UIElementNode *) list->First ();
982 while (node) {
983 uielement_list->Add (new Value (node->uielement));
984 node = (UIElementNode *) node->next;
987 delete list;
988 measuring_context_destroy (ctx);
992 void
993 UIElement::FindElementsInHostCoordinates (cairo_t *cr, Point P, List *uielement_list)
995 uielement_list->Prepend (new UIElementNode (this));
999 void
1000 UIElement::FindElementsInHostCoordinates_r (Rect r, HitTestCollection *uielement_list)
1002 List *list = new List ();
1003 cairo_t *ctx = measuring_context_create ();
1005 FindElementsInHostCoordinates (ctx, r, list);
1007 UIElementNode *node = (UIElementNode *) list->First ();
1008 while (node) {
1009 uielement_list->Add (new Value (node->uielement));
1010 node = (UIElementNode *) node->next;
1013 delete list;
1014 measuring_context_destroy (ctx);
1017 void
1018 UIElement::FindElementsInHostCoordinates (cairo_t *cr, Rect r, List *uielement_list)
1020 uielement_list->Prepend (new UIElementNode (this));
1023 bool
1024 UIElement::EmitKeyDown (GdkEventKey *event)
1026 return Emit (KeyDownEvent, new KeyEventArgs (event));
1029 bool
1030 UIElement::EmitKeyUp (GdkEventKey *event)
1032 return Emit (KeyUpEvent, new KeyEventArgs (event));
1035 bool
1036 UIElement::EmitGotFocus ()
1038 return Emit (GotFocusEvent, new RoutedEventArgs (this));
1041 bool
1042 UIElement::EmitLostFocus ()
1044 return Emit (LostFocusEvent, new RoutedEventArgs (this));
1047 bool
1048 UIElement::EmitLostMouseCapture ()
1050 MouseEventArgs *e = new MouseEventArgs ();
1051 e->SetSource (this);
1052 return Emit (LostMouseCaptureEvent, e);
1055 bool
1056 UIElement::CaptureMouse ()
1058 if (!IsAttached ())
1059 return false;
1061 return GetDeployment ()->GetSurface ()->SetMouseCapture (this);
1064 void
1065 UIElement::ReleaseMouseCapture ()
1067 if (!IsAttached ())
1068 return;
1070 GetDeployment ()->GetSurface ()->ReleaseMouseCapture (this);
1073 void
1074 UIElement::DoRender (cairo_t *cr, Region *parent_region)
1076 Region *region = new Region (GetSubtreeBounds ());
1077 region->Intersect (parent_region);
1079 if (!GetRenderVisible() || IS_INVISIBLE (total_opacity) || region->IsEmpty ()) {
1080 delete region;
1081 return;
1084 #if FRONT_TO_BACK_STATS
1085 GetSurface()->uielements_rendered_back_to_front ++;
1086 #endif
1088 STARTTIMER (UIElement_render, Type::Find (GetObjectType())->name);
1090 PreRender (cr, region, false);
1092 Render (cr, region);
1094 PostRender (cr, region, false);
1096 ENDTIMER (UIElement_render, Type::Find (GetObjectType())->name);
1098 delete region;
1101 bool
1102 UIElement::UseBackToFront ()
1104 return VisualTreeWalker (this).GetCount () < MIN_FRONT_TO_BACK_COUNT;
1107 void
1108 UIElement::FrontToBack (Region *surface_region, List *render_list)
1110 double local_opacity = GetOpacity ();
1112 if (surface_region->RectIn (GetSubtreeBounds().RoundOut()) == GDK_OVERLAP_RECTANGLE_OUT)
1113 return;
1115 if (!GetRenderVisible ()
1116 || IS_INVISIBLE (local_opacity))
1117 return;
1119 if (!UseBackToFront ()) {
1120 Region *self_region = new Region (surface_region);
1121 self_region->Intersect (GetSubtreeBounds().RoundOut());
1123 // we need to include our children in this one, since
1124 // we'll be rendering them in the PostRender method.
1125 if (!self_region->IsEmpty())
1126 render_list->Prepend (new RenderNode (this, self_region, true,
1127 UIElement::CallPreRender, UIElement::CallPostRender));
1128 // don't remove the region from surface_region because
1129 // there are likely holes in it
1130 return;
1133 Region *region;
1134 bool delete_region;
1135 bool can_subtract_self;
1137 if (!GetClip ()
1138 && !GetOpacityMask ()
1139 && !IS_TRANSLUCENT (GetOpacity ())) {
1140 region = surface_region;
1141 delete_region = false;
1142 can_subtract_self = true;
1144 else {
1145 region = new Region (surface_region);
1146 delete_region = true;
1147 can_subtract_self = false;
1150 RenderNode *cleanup_node = new RenderNode (this, NULL, false, NULL, UIElement::CallPostRender);
1152 render_list->Prepend (cleanup_node);
1154 Region *self_region = new Region (region);
1156 VisualTreeWalker walker (this, ZReverse);
1157 while (UIElement *child = walker.Step ())
1158 child->FrontToBack (region, render_list);
1160 if (!GetOpacityMask () && !IS_TRANSLUCENT (local_opacity)) {
1161 delete self_region;
1162 if (GetRenderBounds().IsEmpty ()) { // empty bounds mean that this element draws nothing itself
1163 self_region = new Region ();
1165 else {
1166 self_region = new Region (region);
1167 self_region->Intersect (GetRenderBounds().RoundOut ()); // note the RoundOut
1169 } else {
1170 self_region->Intersect (GetSubtreeBounds().RoundOut ()); // note the RoundOut
1173 if (self_region->IsEmpty() && render_list->First() == cleanup_node) {
1174 /* we don't intersect the surface region, and none of
1175 our children did either, remove the cleanup node */
1176 render_list->Remove (render_list->First());
1177 delete self_region;
1178 if (delete_region)
1179 delete region;
1180 return;
1183 render_list->Prepend (new RenderNode (this, self_region, !self_region->IsEmpty(), UIElement::CallPreRender, NULL));
1185 if (!self_region->IsEmpty()) {
1186 if (((absolute_xform.yx == 0 && absolute_xform.xy == 0) /* no skew/rotation */
1187 || (absolute_xform.xx == 0 && absolute_xform.yy == 0)) /* allow 90 degree rotations */
1188 && can_subtract_self)
1189 region->Subtract (GetCoverageBounds ());
1192 if (delete_region)
1193 delete region;
1196 void
1197 UIElement::PreRender (cairo_t *cr, Region *region, bool skip_children)
1199 double local_opacity = GetOpacity ();
1201 cairo_save (cr);
1203 cairo_set_matrix (cr, &absolute_xform);
1204 RenderClipPath (cr);
1206 if (opacityMask || IS_TRANSLUCENT (local_opacity)) {
1207 Rect r = GetSubtreeBounds ().RoundOut();
1208 cairo_identity_matrix (cr);
1210 // we need this check because ::PreRender can (and
1211 // will) be called for elements with empty regions.
1213 // The region passed in here is the redraw region
1214 // intersected with the render bounds of a given
1215 // element. For Panels with no width/height specified
1216 // in the xaml, this region will be empty. (check
1217 // panel.cpp::FrontToBack - we insert the ::PreRender
1218 // calling node if either the panel background or any
1219 // of the children intersect the redraw region.) We
1220 // can't clip to the empty region, obviously, as it
1221 // will keep all descendents from drawing to the
1222 // screen.
1224 if (!region->IsEmpty()) {
1225 region->Draw (cr);
1226 cairo_clip (cr);
1228 r.Draw (cr);
1229 cairo_clip (cr);
1231 cairo_set_matrix (cr, &absolute_xform);
1234 if (ClipToExtents ()) {
1235 extents.Draw (cr);
1236 cairo_clip (cr);
1240 if (IS_TRANSLUCENT (local_opacity))
1241 cairo_push_group (cr);
1243 if (opacityMask != NULL)
1244 cairo_push_group (cr);
1247 void
1248 UIElement::PostRender (cairo_t *cr, Region *region, bool front_to_back)
1250 // if we didn't render front to back, then render the children here
1251 if (!front_to_back) {
1252 VisualTreeWalker walker (this, ZForward);
1253 while (UIElement *child = walker.Step ())
1254 child->DoRender (cr, region);
1257 double local_opacity = GetOpacity ();
1259 if (opacityMask != NULL) {
1260 cairo_pattern_t *data = cairo_pop_group (cr);
1261 if (cairo_pattern_status (data) == CAIRO_STATUS_SUCCESS) {
1262 cairo_pattern_t *mask = NULL;
1263 Point p = GetOriginPoint ();
1264 Rect area = Rect (p.x, p.y, 0.0, 0.0);
1265 GetSizeForBrush (cr, &(area.width), &(area.height));
1266 opacityMask->SetupBrush (cr, area);
1267 mask = cairo_get_source (cr);
1268 cairo_pattern_reference (mask);
1269 cairo_set_source (cr, data);
1270 cairo_mask (cr, mask);
1271 cairo_pattern_destroy (mask);
1273 cairo_pattern_destroy (data);
1276 if (IS_TRANSLUCENT (local_opacity)) {
1277 cairo_pattern_t *data = cairo_pop_group (cr);
1278 if (cairo_pattern_status (data) == CAIRO_STATUS_SUCCESS) {
1279 cairo_set_source (cr, data);
1280 cairo_paint_with_alpha (cr, local_opacity);
1282 cairo_pattern_destroy (data);
1285 cairo_restore (cr);
1287 if (moonlight_flags & RUNTIME_INIT_SHOW_CLIPPING) {
1288 cairo_save (cr);
1289 cairo_new_path (cr);
1290 cairo_set_matrix (cr, &absolute_xform);
1291 cairo_set_line_width (cr, 1);
1293 Geometry *geometry = GetClip ();
1294 if (geometry) {
1295 geometry->Draw (cr);
1296 cairo_set_source_rgba (cr, 0.0, 1.0, 1.0, 1.0);
1297 cairo_stroke (cr);
1300 geometry = LayoutInformation::GetClip ((FrameworkElement *)this);
1301 if (geometry) {
1302 geometry->Draw (cr);
1303 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 1.0);
1304 cairo_stroke (cr);
1307 cairo_restore (cr);
1310 if (moonlight_flags & RUNTIME_INIT_SHOW_BOUNDING_BOXES) {
1311 cairo_save (cr);
1312 cairo_new_path (cr);
1313 //RenderClipPath (cr);
1314 cairo_identity_matrix (cr);
1315 cairo_set_source_rgba (cr, 1.0, 0.5, 0.2, 1.0);
1316 cairo_set_line_width (cr, 1);
1317 cairo_rectangle (cr, bounds.x + .5, bounds.y + .5, bounds.width - .5, bounds.height - .5);
1318 cairo_stroke (cr);
1319 cairo_restore (cr);
1323 void
1324 UIElement::Paint (cairo_t *ctx, Region *region, cairo_matrix_t *xform)
1326 // FIXME xform is ignored for now
1327 if (xform)
1328 g_warning ("passing a transform to UIElement::Paint is not yet supported");
1330 #if FRONT_TO_BACK_STATS
1331 uielements_rendered_front_to_back = 0;
1332 uielements_rendered_back_to_front = 0;
1333 #endif
1335 bool did_front_to_back = false;
1336 List *render_list = new List ();
1338 if (moonlight_flags & RUNTIME_INIT_RENDER_FRONT_TO_BACK) {
1339 Region *copy = new Region (region);
1340 FrontToBack (copy, render_list);
1342 if (!render_list->IsEmpty ()) {
1343 while (RenderNode *node = (RenderNode*)render_list->First()) {
1344 #if FRONT_TO_BACK_STATS
1345 uielements_rendered_front_to_back ++;
1346 #endif
1347 node->Render (ctx);
1349 render_list->Remove (node);
1352 did_front_to_back = true;
1355 delete render_list;
1356 delete copy;
1359 if (!did_front_to_back) {
1360 DoRender (ctx, region);
1363 #if FRONT_TO_BACK_STATS
1364 printf ("UIElements rendered front-to-back for: %s(%p)\n", uielements_rendered_front_to_back, GetName (), this);
1365 printf ("UIElements rendered back-to-front for: %s(%p)\n", uielements_rendered_back_to_front, GetName (), this);
1366 #endif
1369 void
1370 UIElement::CallPreRender (cairo_t *cr, UIElement *element, Region *region, bool front_to_back)
1372 element->PreRender (cr, region, front_to_back);
1375 void
1376 UIElement::CallPostRender (cairo_t *cr, UIElement *element, Region *region, bool front_to_back)
1378 element->PostRender (cr, region, front_to_back);
1381 void
1382 UIElement::Render (cairo_t *cr, Region *region, bool path_only)
1384 /* do nothing by default */
1387 void
1388 UIElement::GetSizeForBrush (cairo_t *cr, double *width, double *height)
1390 g_warning ("UIElement:GetSizeForBrush has been called. The derived class %s should have overridden it.",
1391 GetTypeName ());
1392 *height = *width = 0.0;
1395 TimeManager *
1396 UIElement::GetTimeManager ()
1398 return GetDeployment ()->GetSurface ()->GetTimeManager ();
1401 GeneralTransform *
1402 UIElement::GetTransformToUIElementWithError (UIElement *to_element, MoonError *error)
1404 /* walk from this up to the root. if we hit null before we hit the toplevel, it's an error */
1405 UIElement *visual = this;
1406 bool ok = false;
1408 if (visual && IsAttached ()) {
1409 while (visual) {
1410 if (GetDeployment ()->GetSurface()->IsTopLevel (visual))
1411 ok = true;
1412 visual = visual->GetVisualParent ();
1416 if (!ok || (to_element && !to_element->IsAttached ())) {
1417 MoonError::FillIn (error, MoonError::ARGUMENT, 1001,
1418 "visual");
1419 return NULL;
1422 if (to_element && !GetDeployment ()->GetSurface()->IsTopLevel (to_element)) {
1423 /* if @to_element is specified we also need to make sure there's a path to the root from it */
1424 ok = false;
1425 visual = to_element->GetVisualParent ();
1426 if (visual && to_element->IsAttached ()) {
1427 while (visual) {
1428 if (GetDeployment ()->GetSurface()->IsTopLevel (visual))
1429 ok = true;
1430 visual = visual->GetVisualParent ();
1434 if (!ok) {
1435 MoonError::FillIn (error, MoonError::ARGUMENT, 1001,
1436 "visual");
1437 return NULL;
1441 cairo_matrix_t result;
1442 // A = From, B = To, M = what we want
1443 // A = M * B
1444 // => M = A * inv (B)
1445 if (to_element) {
1446 cairo_matrix_t inverse = to_element->absolute_xform;
1447 cairo_matrix_invert (&inverse);
1448 cairo_matrix_multiply (&result, &absolute_xform, &inverse);
1450 else {
1451 result = absolute_xform;
1454 Matrix *matrix = new Matrix (&result);
1456 MatrixTransform *transform = new MatrixTransform ();
1457 transform->SetValue (MatrixTransform::MatrixProperty, matrix);
1458 matrix->unref ();
1460 return transform;
1463 void
1464 UIElement::TransformPoint (double *x, double *y)
1466 cairo_matrix_t inverse = absolute_xform;
1467 cairo_matrix_invert (&inverse);
1469 cairo_matrix_transform_point (&inverse, x, y);