Esempio n. 1
0
        public void ReEvaluate(Node context, CssProperty property)
        {
            Property = property;
            IRenderableNode rdr = (context.parentNode_ as IRenderableNode);

            if (rdr != null)
            {
                // Inherit right now:
                Context = rdr.RenderData;
                From_   = rdr.ComputedStyle[property];

                Inherit inher = From_ as Inherit;

                if (inher != null)
                {
                    // Collapse the cascade - Pull the inner value instead:
                    From_   = inher.From;
                    Context = inher.Context;
                }
            }

            if (From_ == null)
            {
                From_ = property.InitialValue;
            }

            if (Context == null)
            {
                Context = (context as IRenderableNode).RenderData;
            }

            Type = From_.Type;
        }
Esempio n. 2
0
        /// <summary>Called when a selection starts using the given input pointer (typically a mouse).</summary>
        public static void BeginSelect(PowerUI.InputPointer pointer, Css.Value mode)
        {
            // Selection starting at the position the pointer started dragging

            // Firstly, resolve our doc coords to a selection range start.

            // Get as first containing child node (should be text or null):
            NodeList kids = pointer.ActivePressed.childNodes_;

            if (kids != null)
            {
                for (int i = 0; i < kids.length; i++)
                {
                    IRenderableNode irn = (kids[i] as IRenderableNode);

                    if (irn != null)
                    {
                        // Contains it?
                        LayoutBox box = irn.RenderData.BoxAt(pointer.DownDocumentX, pointer.DownDocumentY);

                        if (box == null)
                        {
                            // Use the last one:
                            box = irn.RenderData.LastBox;
                        }

                        if (box != null)
                        {
                            // Great! Try it as a text node (should always be one):
                            PowerUI.RenderableTextNode htn = (kids[i] as PowerUI.RenderableTextNode);

                            if (htn != null)
                            {
                                // Awesome - get the letter indices:
                                int startIndex = htn.LetterIndex(pointer.DownDocumentX, box);
                                int endIndex   = htn.LetterIndex(pointer.DocumentX, pointer.DocumentY);

                                // Create a range:
                                Range range = new Range();
                                range.startOffset    = startIndex;
                                range.endOffset      = endIndex;
                                range.startContainer = htn;
                                range.endContainer   = htn;

                                // Get the current selection:
                                Selection s = (kids[i].document as HtmlDocument).getSelection();

                                // Clear all:
                                s.removeAllRanges();

                                // Add range:
                                s.addRange(range);
                            }

                            break;
                        }
                    }
                }
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Gives the values of all the CSS properties of an element after
        /// applying the active stylesheets and resolving any basic computation
        /// those values may contain.
        /// </summary>
        /// <param name="pseudo">The optional pseudo selector to use.</param>
        /// <returns>The style declaration describing the element.</returns>
        public Css.ComputedStyle getComputedStyle(string pseudo)
        {
            if (string.IsNullOrEmpty(pseudo))
            {
                return(Style.Computed);
            }

            // Get the particular pseudo-element:
            pseudo = pseudo.ToLower().Trim();

            // Check if it contains :
            int index = pseudo.LastIndexOf(':');

            if (index != -1)
            {
                // Chop from there:
                pseudo = pseudo.Substring(index + 1);
            }

            IRenderableNode el = Style.Computed.GetVirtualChild(pseudo) as IRenderableNode;

            if (el == null)
            {
                return(null);
            }

            return(el.ComputedStyle);
        }
Esempio n. 4
0
        public void AddRenderableNode(string name, IRenderableNode value)
        {
            ClearBonesAndMeshList();

            if (value == null)
            {
                return;
            }

            var newNode = value.GetRenderableNode();

            // Prevent duplicates. Paths should be unique.
            if (!renderableNodeNames.Contains(name))
            {
                renderableNodes.Add(newNode);
                renderableNodeNames.Add(name);
            }

            // Duplicate nodes should still update the mesh list.
            if (newNode is RSkeleton skeleton)
            {
                DisplaySkeleton(skeleton);
            }
            else if (newNode is IRenderableModel renderableModel)
            {
                DisplayMeshes(renderableModel.GetModel());
                DisplaySkeleton(renderableModel.GetSkeleton());
            }

            if (value is NUMDL_Node)
            {
                var rnumdl = (RNUMDL)newNode;
                FrameSelection(rnumdl.Model);
            }
        }
Esempio n. 5
0
        public void ClearFiles()
        {
            animationBar.Model    = null;
            animationBar.Skeleton = null;
            RenderableNode        = null;

            GC.WaitForPendingFinalizers();
            GLObjectManager.DeleteUnusedGLObjects();
        }
Esempio n. 6
0
        /// <summary>Doesn't create the virtual if it doesn't exist.</summary>
        internal void GetVirtual(CssEvent e, int priority)
        {
            // Get the CS:
            ComputedStyle cs = e.SelectorTarget.computedStyle;

            VirtualElements virts = cs.RenderData.Virtuals;

            IRenderableNode node = null;

            if (virts != null)
            {
                // Get and apply:
                node = virts.Get(priority) as IRenderableNode;
            }

            // Update the target:
            e.SelectorTarget = (node == null) ? null : node.RenderData;
        }
        /// <summary>Gets all child elements with the given tag.</summary>
        /// <param name="selectors">The selectors to match.</param>
        /// <returns>The set of all tags with this tag.</returns>
        public void querySelectorAll(Selector[] selectors, INodeList results, CssEvent e, bool one)
        {
            if (childNodes_ == null)
            {
                return;
            }

            for (int i = 0; i < childNodes_.length; i++)
            {
                Node            node  = childNodes_[i];
                Element         child = node as Element;
                IRenderableNode irn   = (child as IRenderableNode);

                if (child == null || irn == null)
                {
                    continue;
                }

                ComputedStyle cs = irn.ComputedStyle;

                for (int s = 0; s < selectors.Length; s++)
                {
                    // Match?
                    if (selectors[s].StructureMatch(cs, e))
                    {
                        // Yep!
                        results.push(node);

                        if (one)
                        {
                            return;
                        }
                    }
                }

                irn.querySelectorAll(selectors, results, e, one);

                if (one && results.length == 1)
                {
                    return;
                }
            }
        }
        /// <summary>Called when an @font-face font loads.</summary>
        public void FontLoaded(DynamicFont font)
        {
            if (childNodes_ == null)
            {
                return;
            }

            int count = childNodes_.length;

            for (int i = 0; i < count; i++)
            {
                IRenderableNode node = (childNodes_[i] as IRenderableNode);

                if (node == null)
                {
                    continue;
                }

                node.FontLoaded(font);
            }
        }
        /// <summary>Called when this element goes offscreen.</summary>
        public void WentOffScreen()
        {
            RenderableData renderable = RenderData;

            renderable.WentOffScreen();

            // Apply to all virtual elements:
            VirtualElements virts = renderable.Virtuals;

            if (virts != null)
            {
                foreach (KeyValuePair <int, Node> kvp in virts.Elements)
                {
                    // Tell it that it's gone offscreen:
                    IRenderableNode irn = (kvp.Value as IRenderableNode);

                    if (irn != null)
                    {
                        irn.WentOffScreen();
                    }
                }
            }

            if (childNodes_ != null)
            {
                for (int i = 0; i < childNodes_.length; i++)
                {
                    // Get as a HTML node:
                    IRenderableNode htmlNode = (childNodes_[i] as IRenderableNode);

                    if (htmlNode == null)
                    {
                        return;
                    }

                    // Call offscreen:
                    htmlNode.WentOffScreen();
                }
            }
        }
Esempio n. 10
0
		/// <summary>Updates mouse overs, touches and the mouse position.</summary>
		public static void Update(){
			
			InputPointer pointer;
			
			// Look out for any 'new' touch events:
			int touchCount=UnityEngine.Input.touchCount;
			
			if(touchCount>0){
				
				// For each one..
				for(int i=0;i<touchCount;i++){
					
					// Get the info:
					Touch touch=UnityEngine.Input.GetTouch(i);
					
					pointer=null;
					
					// Already seen this one?
					// There won't be many touches so a straight linear scan is by far the fastest option here.
					// (It's much better than a dictionary).
					for(int p=InputPointer.PointerCount-1;p>=0;p--){
						
						// Get the pointer:
						InputPointer current=InputPointer.AllRaw[p];
						
						if(current.ID==touch.fingerId){
							
							// Got it!
							pointer=current;
							break;
							
						}
						
					}
					
					TouchPointer tp=null;
					
					if(pointer==null){
						
						// Touch start! A new finger has been seen for the first time.
						#if UNITY_5_3_OR_NEWER
							
							// Could be a stylus:
							if(touch.type==TouchType.Stylus){
								tp=new StylusPointer();
							}else{
								tp=new FingerPointer();
							}
							
						#else
							
							// Can only assume it's a finger here:
							tp=new FingerPointer();
						
						#endif
						
						// Add it to the available set:
						tp.ID=touch.fingerId;
						tp.Add();
						
					}else{
						
						// Apply latest info:
						tp=(pointer as TouchPointer);
						
					}
					
					// Mark it as still alive so it doesn't get removed shortly.
					tp.StillAlive=true;
					tp.LatestPosition=touch.position;
					
					#if UNITY_5_3_OR_NEWER
					
					tp.Radius=touch.radius;
					tp.RadiusVariance=touch.radiusVariance;
					tp.LatestPressure=touch.pressure;
					
					// Is it a stylus?
					if(tp is StylusPointer){
						tp.UpdateStylus(touch.azimuthAngle,touch.altitudeAngle);
					}
					
					// Always a pressure of 1 otherwise (the default).
					#endif
				}
				
			}
			
			// Update each pointer, invalidating itself only if it has moved:
			bool pointerRemoved=false;
			
			for(int i=InputPointer.PointerCount-1;i>=0;i--){
				
				// Get the pointer:
				pointer=InputPointer.AllRaw[i];
				
				// Update its position and state now:
				Vector2 delta;
				bool moved=pointer.Relocate(out delta);
				
				if(pointer.Removed){
					// It got removed! (E.g. a finger left the screen).
					pointerRemoved=true;
					
					// Clear pressure (mouseup etc):
					pointer.SetPressure(0f);
					
					if(pointer.ActiveOverTarget!=null){
						
						// Shared event:
						MouseEvent mouseEvent=new MouseEvent(pointer.ScreenX,pointer.ScreenY,pointer.ButtonID,false);
						mouseEvent.trigger=pointer;
						mouseEvent.clientX=pointer.DocumentX;
						mouseEvent.clientY=pointer.DocumentY;
						mouseEvent.SetModifiers();
						mouseEvent.SetTrusted();
						
						// Trigger a mouseout (bubbles):
						mouseEvent.EventType="mouseout";
						mouseEvent.SetTrusted();
						pointer.ActiveOverTarget.dispatchEvent(mouseEvent);
						
						// And a mouseleave (doesn't bubble).
						mouseEvent.Reset();
						mouseEvent.bubbles=false;
						mouseEvent.EventType="mouseleave";
						pointer.ActiveOverTarget.dispatchEvent(mouseEvent);
						
						// Update the CSS (hover; typically out):
						IRenderableNode irn = (pointer.ActiveOverTarget as IRenderableNode);
						
						if(irn!=null){
							irn.ComputedStyle.RefreshLocal(true);
						}
						
					}
					
					continue;
				}
				
				// If the pointer is invalid or they all are:
				if(moved || PointersInvalid){
					
					// Figure out what's under it. This takes its pos on the screen
					// and figures out what's there, as well as converting the position
					// to one which is relative to the document (used by e.g. a WorldUI).
					float documentX=pointer.ScreenX;
					float documentY=pointer.ScreenY;
					EventTarget newActiveOver=TargetFromPoint(ref documentX,ref documentY,pointer);
					
					// Update docX/Y:
					pointer.DocumentX=documentX;
					pointer.DocumentY=documentY;
					
					// Get the old one:
					EventTarget oldActiveOver=pointer.ActiveOverTarget;
					
					// Shared event:
					MouseEvent mouseEvent=new MouseEvent(documentX,documentY,pointer.ButtonID,pointer.IsDown);
					mouseEvent.trigger=pointer;
					mouseEvent.clientX=pointer.DocumentX;
					mouseEvent.clientY=pointer.DocumentY;
					mouseEvent.SetModifiers();
					mouseEvent.SetTrusted();
					
					// If overElement has changed from the previous one..
					if(newActiveOver!=oldActiveOver){
						
						if(oldActiveOver!=null){
							
							// Trigger a mouseout (bubbles):
							mouseEvent.EventType="mouseout";
							mouseEvent.SetTrusted();
							mouseEvent.relatedTarget=newActiveOver;
							oldActiveOver.dispatchEvent(mouseEvent);
							
							// And a mouseleave (doesn't bubble).
							// Only triggered if newActiveOver is *not* the parent of oldActiveOver.
							if(oldActiveOver.eventTargetParentNode!=newActiveOver){
								mouseEvent.Reset();
								mouseEvent.bubbles=false;
								mouseEvent.EventType="mouseleave";
								mouseEvent.relatedTarget=newActiveOver;
								oldActiveOver.dispatchEvent(mouseEvent);
							}
							
						}
						
						// Update it:
						pointer.ActiveOverTarget=newActiveOver;
						
						if(oldActiveOver!=null){
							// Update the CSS (hover; typically out):
							IRenderableNode irn = (oldActiveOver as IRenderableNode);
							
							if(irn!=null){
								irn.ComputedStyle.RefreshLocal(true);
							}
						}
						
						if(newActiveOver!=null){
							// Update the CSS (hover; typically in)
							IRenderableNode irn = (newActiveOver as IRenderableNode);
							
							if(irn!=null){
								irn.ComputedStyle.RefreshLocal(true);
							}
						}
						
						// Trigger a mouseover (bubbles):
						mouseEvent.Reset();
						mouseEvent.bubbles=true;
						mouseEvent.relatedTarget=oldActiveOver;
						mouseEvent.EventType="mouseover";
						
						if(newActiveOver==null){
							
							// Clear the main UI tooltip:
							UI.document.tooltip=null;
							
							// Dispatch to unhandled:
							Unhandled.dispatchEvent(mouseEvent);
							
						}else{
							
							newActiveOver.dispatchEvent(mouseEvent);
							
							// Set the tooltip if we've got one:
							UI.document.tooltip=newActiveOver.getTitle();
							
							// And a mouseenter (doesn't bubble).
							// Only triggered if newActiveOver is *not* a child of oldActiveOver.
							if(newActiveOver.eventTargetParentNode!=oldActiveOver){
								mouseEvent.Reset();
								mouseEvent.bubbles=false;
								mouseEvent.EventType="mouseenter";
								mouseEvent.relatedTarget=oldActiveOver;
								newActiveOver.dispatchEvent(mouseEvent);
							}
							
						}
						
					}
					
					if(moved){
						
						// Trigger a mousemove event:
						mouseEvent.Reset();
						mouseEvent.bubbles=true;
						mouseEvent.EventType="mousemove";
						
						if(newActiveOver==null){
							Unhandled.dispatchEvent(mouseEvent);
						}else{
							newActiveOver.dispatchEvent(mouseEvent);
						}
						
					}
					
				}
				
				// If the pointer requires pressure changes..
				if(pointer.FireTouchEvents){
					
					// Set the pressure (which triggers mousedown/up for us too):
					TouchPointer tp=(pointer as TouchPointer);
					
					tp.SetPressure(tp.LatestPressure);
					
					// We test touch move down here because it must happen after we've
					// transferred 'ActiveOver' to 'ActiveDown' which happens inside SetPressure.
					
					// Touch events are triggered on the element that was pressed down on
					// (even if we've moved beyond it's bounds).
					if(moved){
						
						// Trigger a touchmove event too:
						TouchEvent te=new TouchEvent("touchmove");
						te.trigger=pointer;
						te.SetTrusted();
						te.clientX=pointer.DocumentX;
						te.clientY=pointer.DocumentY;
						te.SetModifiers();
						
						if(pointer.ActivePressedTarget==null){
							
							// Trigger on unhandled:
							Unhandled.dispatchEvent(te);
							
						}else{
						
							pointer.ActivePressedTarget.dispatchEvent(te);
						
						}
						
					}
					
				}
				
				// Test for dragstart
				if(pointer.IsDown && moved){
					
					// How far has it moved since it went down?
					if(pointer.DragStatus==InputPointer.DRAG_UNKNOWN){
						
						// Is the element we pressed (or any of its parents) draggable?
						if(pointer.MinDragDistance==0f){
							
							// Find if we've got a draggable element:
							pointer.ActiveUpdatingTarget=GetDraggable(pointer.ActivePressedTarget as Element);
							
							// Find the min drag distance:
							pointer.MinDragDistance=pointer.GetMinDragDistance();
							
						}
						
						if(pointer.MovedBeyondDragDistance){
							
							// Possibly dragging.
							
							// Actually marked as 'draggable'?
							if(pointer.ActiveUpdatingTarget!=null){
								
								// Try start drag:
								DragEvent de=new DragEvent("dragstart");
								de.trigger=pointer;
								de.SetModifiers();
								de.SetTrusted();
								de.deltaX=delta.x;
								de.deltaY=delta.y;
								de.clientX=pointer.DocumentX;
								de.clientY=pointer.DocumentY;
								
								if(pointer.ActivePressedTarget.dispatchEvent(de)){
									
									// We're now dragging!
									pointer.DragStatus=InputPointer.DRAGGING;
									
								}else{
									
									// It didn't allow it. This status prevents it from spamming dragstart.
									pointer.DragStatus=InputPointer.DRAG_NOT_AVAILABLE;
									
								}
								
							}else{
								
								// Selectable?
								IRenderableNode irn = (pointer.ActivePressedTarget as IRenderableNode);
								
								if(irn!=null){
									// It's renderable; can it be selected?
									ComputedStyle cs=irn.ComputedStyle;
									Css.Value userSelect=cs[Css.Properties.UserSelect.GlobalProperty];
									
									if(userSelect!=null && !(userSelect.IsType(typeof(Css.Keywords.None))) && !userSelect.IsAuto){
										
										// Selectable!
										Css.Properties.UserSelect.BeginSelect(pointer,userSelect);
										
										// Set status:
										pointer.DragStatus=InputPointer.SELECTING;
										
										// Focus it if needed:
										HtmlElement html=(pointer.ActivePressedTarget as HtmlElement);
										
										if(html!=null){
											html.focus();
										}
										
									}else{
										
										// This status prevents it from spamming, at least until we release.
										pointer.DragStatus=InputPointer.DRAG_NOT_AVAILABLE;
										
									}
								
								}
								
							}
							
						}
						
					}else if(pointer.DragStatus==InputPointer.DRAGGING){
						
						// Move the dragged element (the event goes to everybody):
						if(pointer.ActivePressedTarget!=null){
							
							DragEvent de=new DragEvent("drag");
							de.trigger=pointer;
							de.SetModifiers();
							de.SetTrusted();
							de.deltaX=delta.x;
							de.deltaY=delta.y;
							de.clientX=pointer.DocumentX;
							de.clientY=pointer.DocumentY;
							
							if(pointer.ActivePressedTarget.dispatchEvent(de)){
								
								// Note that onDrag runs on the *dragging* element only.
								Element dragging;
								
								if(pointer.ActiveUpdatingTarget==null){
									dragging = (Element)pointer.ActivePressedTarget;
								}else{
									dragging = (Element)pointer.ActiveUpdatingTarget;
								}
								
								if(dragging.OnDrag(de)){
									
									// Run the PowerUI default - move the element:
									RenderableData renderData=(dragging as IRenderableNode).RenderData;
									
									// Get the "actual" left/top values:
									if(renderData.FirstBox!=null){
										
										// Get computed style:
										ComputedStyle cs=renderData.computedStyle;
										
										// Fix if it isn't already:
										if(renderData.FirstBox.PositionMode!=PositionMode.Fixed){
											
											cs.ChangeTagProperty("position","fixed");
											
										}
										
										// Get top/left pos:
										float left=renderData.FirstBox.X+delta.x;
										float top=renderData.FirstBox.Y+delta.y;
										
										// Write it back out:
										cs.ChangeTagProperty("left",new Css.Units.DecimalUnit(left));
										cs.ChangeTagProperty("top",new Css.Units.DecimalUnit(top));
										
									}
									
								}
							}
							
						}
						
					}else if(pointer.DragStatus==InputPointer.SELECTING){
						
						// Update the selection.
						
						if(pointer.ActivePressedTarget!=null){
							
							DragEvent de=new DragEvent("drag");
							de.trigger=pointer;
							de.SetModifiers();
							de.SetTrusted();
							de.clientX=pointer.DocumentX;
							de.clientY=pointer.DocumentY;
							
							if(pointer.ActivePressedTarget.dispatchEvent(de)){
								
								// We can only select elements:
								Element pressedElement = (Element)pointer.ActivePressedTarget;
								
								// Get the current selection:
								Selection s=(pressedElement.document as HtmlDocument).getSelection();
								
								// Safety check:
								if(s.ranges.Count>0){
									
									// Get the range:
									Range range=s.ranges[0];
									
									// Get text node:
									RenderableTextNode htn=(range.startContainer as RenderableTextNode);
									
									if(htn!=null){
										
										// Get the new end index:
										int endIndex=htn.LetterIndex(pointer.DocumentX,pointer.DocumentY);
										
										// Update:
										range.endOffset=endIndex;
										
										// Flush:
										s.UpdateSelection(true,range);
										
									}
									
								}
								
							}
							
						}
						
					}
					
				}
				
			}
			
			if(pointerRemoved){
				// Tidy the removed ones:
				InputPointer.Tidy();
			}
			
			// Clear invalidated state:
			PointersInvalid=false;
			
			#if MOBILE
			// Handle mobile keyboard:
			if(MobileKeyboard!=null){
				
				HandleMobileKeyboardInput();
				
			}
			#endif
			
		}
Esempio n. 11
0
        /// <summary>Part of shrink-to-fit. Computes the maximum and minimum possible width for an element.
        /// This does not include the elements own padding/margin/border.</summary>
        public void GetWidthBounds(out float min, out float max)
        {
            min = 0f;
            max = 0f;

            // For each child, get its width bounds too.
            if (RenderData.FirstBox == null)
            {
                return;
            }

            if (childNodes_ != null)
            {
                // Current line:
                float cMin = 0f;
                float cMax = 0f;

                for (int i = 0; i < childNodes_.length; i++)
                {
                    Node child = childNodes_[i];

                    IRenderableNode renderable = (child as IRenderableNode);

                    if (renderable == null)
                    {
                        continue;
                    }

                    float bMin;
                    float bMax;

                    if (child is PowerUI.RenderableTextNode)
                    {
                        // Always get bounds:
                        renderable.GetWidthBounds(out bMin, out bMax);
                    }
                    else
                    {
                        // Get the first box from the render data:
                        RenderableData rd  = renderable.RenderData;
                        LayoutBox      box = rd.FirstBox;

                        if (box == null)
                        {
                            continue;
                        }

                        // If it's inline (or float) then it's additive to the current line.
                        if ((box.DisplayMode & DisplayMode.OutsideBlock) != 0 && box.FloatMode == FloatMode.None)
                        {
                            // Line break!
                            cMin = 0f;
                            cMax = 0f;
                        }

                        // Get an explicit width:
                        bool wasAuto;
                        bMin = rd.GetWidth(true, out wasAuto);

                        if (bMin == float.MinValue)
                        {
                            // Get the bounds:
                            renderable.GetWidthBounds(out bMin, out bMax);
                        }
                        else
                        {
                            bMax = bMin;
                        }

                        // Add margins etc:
                        float extraStyle = (
                            box.Border.Left + box.Border.Right +
                            box.Padding.Left + box.Padding.Right +
                            box.Margin.Left + box.Margin.Right
                            );

                        bMin += extraStyle;
                        bMax += extraStyle;
                    }

                    // Apply to line:
                    cMin += bMin;
                    cMax += bMax;

                    // Longest line?
                    if (cMin > min)
                    {
                        min = cMin;
                    }

                    if (cMax > max)
                    {
                        max = cMax;
                    }
                }
            }
        }
Esempio n. 12
0
        /*
         * public override void OnChildrenLoaded(){
         *
         *      // Construct the selector structure now:
         *      // Style.Computed.RefreshStructure();
         *
         * }
         */

        /// <summary>Part of shrink-to-fit. Computes the maximum and minimum possible width for an element.
        /// This does not include the elements own padding/margin/border.</summary>
        public virtual void GetWidthBounds(out float min, out float max)
        {
            min = 0f;
            max = 0f;

            // For each child, get its width bounds too.
            if (RenderData.FirstBox == null)
            {
                return;
            }

            if (childNodes_ != null)
            {
                // Current line:
                float cMin = 0f;
                float cMax = 0f;

                for (int i = 0; i < childNodes_.length; i++)
                {
                    Node child = childNodes_[i];

                    IRenderableNode renderable = (child as IRenderableNode);

                    if (renderable == null)
                    {
                        continue;
                    }

                    float bMin;
                    float bMax;

                    if (child is RenderableTextNode)
                    {
                        // Always get bounds:
                        renderable.GetWidthBounds(out bMin, out bMax);
                    }
                    else
                    {
                        // Get the first box from the render data:
                        ComputedStyle  cs  = renderable.ComputedStyle;
                        RenderableData rd  = renderable.RenderData;
                        LayoutBox      box = rd.FirstBox;

                        if (box == null)
                        {
                            continue;
                        }

                        int displayMode = box.DisplayMode;

                        // If it's inline (or float) then it's additive to the current line.
                        if ((displayMode & DisplayMode.OutsideBlock) != 0 && box.FloatMode == FloatMode.None)
                        {
                            // Line break!
                            cMin = 0f;
                            cMax = 0f;
                        }

                        // Get an explicit width:
                        bool wasAuto;
                        bMin = rd.GetWidth(true, out wasAuto);

                        if (bMin == float.MinValue)
                        {
                            // Get the bounds:
                            renderable.GetWidthBounds(out bMin, out bMax);
                        }
                        else
                        {
                            bMax = bMin;
                        }

                        // Add margins etc (NB: These are calculated twice due to %):
                        BoxStyle padding = cs.GetPaddingBox(displayMode);
                        BoxStyle border  = cs.GetBorderBox(displayMode);

                        // Compute the initial margin:
                        bool     marginAuto = false;
                        BoxStyle margin     = cs.GetMarginBox(displayMode, box.FloatMode, ref marginAuto);

                        float extraStyle = (
                            border.Left + border.Right +
                            padding.Left + padding.Right +
                            margin.Left + margin.Right
                            );

                        bMin += extraStyle;
                        bMax += extraStyle;
                    }

                    // Apply to line:
                    cMin += bMin;
                    cMax += bMax;

                    // Longest line?
                    if (cMin > min)
                    {
                        min = cMin;
                    }

                    if (cMax > max)
                    {
                        max = cMax;
                    }
                }
            }
        }
Esempio n. 13
0
        /// <summary>Applies a structurally matched selector to the DOM.
        /// Occurs shortly after StructureMatch.</summary>
        public MatchingSelector BakeToTarget(ComputedStyle cs, CssEvent e)
        {
            // Get the node:
            Node node = cs.Element;

            // First, generate our instance:
            MatchingSelector ms = new MatchingSelector();

            // Update it:
            ms.Selector     = this;
            ms.MatchedRoots = new MatchingRoot[RootCount];

            // For each root, create a MatchingRoot object.

            // Apply target - this helps track which element we're actually testing:
            e.CurrentNode    = node;
            e.SelectorTarget = null;

            // We always start from the tail and work backwards.
            // If we get a match, then the caller can do whatever it wants to the target.

            for (int i = RootCount - 1; i >= 0; i--)
            {
                // Get the matcher:
                RootMatcher rm = Roots[i];

                // Try matching this root:
                if (!rm.TryMatch(e.CurrentNode))
                {
                    // Failed! If we had a matcher and it has Repeat set true, try again:
                    if (rm.NextMatcher != null && rm.NextMatcher.Repeat)
                    {
                        // Move target:
                        rm.NextMatcher.MoveUpwards(e);

                        // Try matching again:
                        i++;
                        continue;
                    }
                }
                else
                {
                    // Match! e.CurrentNode is the node to add.

                    // Create the instance:
                    MatchingRoot matchedRoot = new MatchingRoot();
                    matchedRoot.Root     = rm;
                    matchedRoot.Selector = ms;
                    matchedRoot.Node     = e.CurrentNode;

                    // Get renderable node:
                    IRenderableNode renderable = (e.CurrentNode as IRenderableNode);

                    // Add to selector:
                    ms.MatchedRoots[i] = matchedRoot;

                    // Add:
                    ComputedStyle nodeCs = renderable.ComputedStyle;

                    // Push the match now into the linked list:
                    if (nodeCs.FirstMatch == null)
                    {
                        nodeCs.FirstMatch = matchedRoot;
                        nodeCs.LastMatch  = matchedRoot;
                    }
                    else
                    {
                        matchedRoot.PreviousInStyle  = nodeCs.LastMatch;
                        nodeCs.LastMatch.NextInStyle = matchedRoot;
                        nodeCs.LastMatch             = matchedRoot;
                    }

                    if (rm.IsTarget)
                    {
                        // Update the target now:
                        e.SelectorTarget = renderable.RenderData;
                    }
                }

                // If we have a structure matcher, run it now. It'll move CurrentNode for us:
                if (rm.PreviousMatcher != null)
                {
                    // Move target:
                    rm.PreviousMatcher.MoveUpwards(e);
                }
            }

            // Final pass - if we have a pseudo-element, apply it now:
            if (PseudoElement != null)
            {
                PseudoElement.Select(e);
            }

            // Apply target:
            ms.Target = e.SelectorTarget;

            // Finally, refresh all:
            ms.ResetActive();

            return(ms);
        }
Esempio n. 14
0
 public void Clear()
 {
     RenderableNode = null;
 }
Esempio n. 15
0
        /// <summary>Computes the box for the given element now.</summary>
        public BoxStyle Compute(Css.Value value, RenderableData context, ComputedStyle parent, int display, int floatMode, ref bool dimsRequired)
        {
            // Result:
            BoxStyle result = new BoxStyle();

            if (value == null)
            {
                return(result);
            }

            float marginSpace;

            // Right and left:
            Css.Value a = value[1];
            Css.Value b = value[3];

            // Default here is '0':
            if (a == null)
            {
                a = Css.Value.Empty;
            }

            if (b == null)
            {
                b = Css.Value.Empty;
            }

            if (a.IsAuto || b.IsAuto)
            {
                // One or both are auto.

                // If we are an inline level element or we're floating then any 'auto' values are 0.
                if (floatMode != 0 || (display & DisplayMode.OutsideInline) != 0)
                {
                    if (!a.IsAuto)
                    {
                        // Left auto, right is a number.
                        result.Right = a.GetDecimal(context, Right);
                    }
                    else if (!b.IsAuto)
                    {
                        // Right auto, left is a number.
                        result.Left = b.GetDecimal(context, Left);
                    }
                }
                else
                {
                    if (!dimsRequired)
                    {
                        // Dimensions are required - quit here.
                        dimsRequired = true;
                        return(result);
                    }

                    if (parent != null)
                    {
                        while (parent.DisplayX == DisplayMode.Inline)
                        {
                            // Special case - InnerWidth is invalid.

                            // Go up the DOM to reach a flow-level element:
                            IRenderableNode parentNode = parent.Element.parentNode as IRenderableNode;

                            if (parentNode == null)
                            {
                                break;
                            }

                            parent = parentNode.ComputedStyle;
                        }
                    }

                    if (parent == null)
                    {
                        // Parent is actually the document.
                        marginSpace = context.Document.Viewport.Width - context.FirstBox.BorderedWidth;
                    }
                    else
                    {
                        // Get available margin space.
                        marginSpace = parent.InnerWidth - context.FirstBox.BorderedWidth;
                    }

                    if (!a.IsAuto)
                    {
                        // Left auto, right is a number.
                        result.Right = a.GetDecimal(context, Right);
                        result.Left  = marginSpace - result.Right;

                        // Auto doesn't create negatives:
                        if (result.Left < 0f)
                        {
                            result.Left = 0f;
                        }
                    }
                    else if (!b.IsAuto)
                    {
                        // Right auto, left is a number.
                        result.Left  = b.GetDecimal(context, Left);
                        result.Right = marginSpace - result.Left;

                        // Auto doesn't create negatives:
                        if (result.Right < 0f)
                        {
                            result.Right = 0f;
                        }
                    }
                    else if (marginSpace > 0f)                    // No negatives allowed

                    // Centering:
                    {
                        result.Right = result.Left = marginSpace / 2f;
                    }
                }
            }
            else
            {
                // Both are ordinary numbers:
                result.Right = a.GetDecimal(context, Right);
                result.Left  = b.GetDecimal(context, Left);
            }

            // Ignore margin top/bottom entirely on inline, in-flow elements.
            if (display != DisplayMode.Inline)
            {
                // Top and bottom:
                a = value[0];
                b = value[2];

                // Default here is '0':
                if (a == null)
                {
                    a = Css.Value.Empty;
                }

                if (b == null)
                {
                    b = Css.Value.Empty;
                }

                if (a.IsAuto || b.IsAuto)
                {
                    // One or both are auto.

                    // If we are an inline level element or floating then any 'auto' values are 0.
                    if (floatMode != 0 || (display & DisplayMode.OutsideInline) != 0)
                    {
                        if (!a.IsAuto)
                        {
                            // Bottom auto, top is a number.
                            result.Top = a.GetDecimal(context, Top);
                        }
                        else if (!b.IsAuto)
                        {
                            // Top auto, bottom is a number.
                            result.Bottom = b.GetDecimal(context, Bottom);
                        }
                    }
                    else
                    {
                        if (!dimsRequired)
                        {
                            // Dimensions are required - quit here.
                            dimsRequired = true;
                            return(result);
                        }

                        if (parent != null)
                        {
                            while (parent.DisplayX == DisplayMode.Inline)
                            {
                                // Special case - InnerHeight is invalid.

                                // Go up the DOM to reach a flow-level element:
                                IRenderableNode parentNode = parent.Element.parentNode as IRenderableNode;

                                if (parentNode == null)
                                {
                                    break;
                                }

                                parent = parentNode.ComputedStyle;
                            }
                        }

                        if (parent == null)
                        {
                            // Parent is actually the document.
                            marginSpace = context.Document.Viewport.Height - context.FirstBox.BorderedHeight;
                        }
                        else
                        {
                            // Get available margin space.
                            marginSpace = parent.InnerHeight - context.FirstBox.BorderedHeight;
                        }

                        if (!a.IsAuto)
                        {
                            // Bottom auto, top is a number.
                            result.Top    = a.GetDecimal(context, Top);
                            result.Bottom = marginSpace - result.Top;

                            // Auto doesn't create negatives:
                            if (result.Bottom < 0f)
                            {
                                result.Bottom = 0f;
                            }
                        }
                        else if (!b.IsAuto)
                        {
                            // Top auto, bottom is a number.
                            result.Bottom = b.GetDecimal(context, Bottom);
                            result.Top    = marginSpace - result.Bottom;

                            // Auto doesn't create negatives:
                            if (result.Top < 0f)
                            {
                                result.Top = 0f;
                            }
                        }
                        else if (marginSpace > 0f)                        // No negatives allowed

                        // Centering:
                        {
                            result.Top = result.Bottom = marginSpace / 2f;
                        }
                    }
                }
                else
                {
                    // Both are ordinary numbers:
                    result.Top    = a.GetDecimal(context, Top);
                    result.Bottom = b.GetDecimal(context, Bottom);
                }
            }

            return(result);
        }
Esempio n. 16
0
        /// <summary>Sets the pressure level.</summary>
        public void SetPressure(float v)
        {
            // Was it up before?
            bool wasUp = (Pressure == 0f);

            // Set pressure:
            Pressure = v;

            // If it's non-zero then we'll need to grab the clicked object:
            if (v == 0f)
            {
                if (wasUp)
                {
                    // No change.
                }
                else
                {
                    // It's up now. Clear:
                    EventTarget oldActivePressed = ActivePressedTarget;

                    // Clear:
                    ActivePressedTarget = null;

                    if (oldActivePressed != null)
                    {
                        // Refresh CSS (active; applies to parents too):
                        EventTarget current = oldActivePressed;

                        while (current != null)
                        {
                            // Get it as a renderable node:
                            IRenderableNode irn = (current as IRenderableNode);

                            if (irn != null)
                            {
                                irn.ComputedStyle.RefreshLocal();
                            }

                            current = current.eventTargetParentNode;
                        }
                    }

                    // Trigger up event.
                    MouseEvent e = new MouseEvent(DocumentX, DocumentY, ButtonID, false);
                    e.trigger = this;
                    e.SetModifiers();
                    e.EventType = "mouseup";

                    if (oldActivePressed == null)
                    {
                        Input.Unhandled.dispatchEvent(e);
                    }
                    else
                    {
                        oldActivePressed.dispatchEvent(e);
                    }

                    // Click if needed:
                    if (oldActivePressed == ActiveOverTarget && DragStatus == 0)
                    {
                        // Click!
                        e.Reset();
                        e.trigger = this;
                        e.SetModifiers();
                        e.EventType = "click";

                        if (oldActivePressed == null)
                        {
                            Input.Unhandled.dispatchEvent(e);
                        }
                        else if (oldActivePressed.dispatchEvent(e))
                        {
                            // Clear the selection if necessary:
                            HtmlElement h = (oldActivePressed as HtmlElement);

                            if (h != null)
                            {
                                // Clear selection if there is one:
                                (h.document as HtmlDocument).clearSelection();
                            }
                        }
                    }

                    if (FireTouchEvents)
                    {
                        // Trigger a touchend event too:
                        TouchEvent te = new TouchEvent("touchend");
                        te.trigger = this;
                        te.SetModifiers();
                        te.SetTrusted();
                        te.clientX = DocumentX;
                        te.clientY = DocumentY;

                        if (oldActivePressed == null)
                        {
                            Input.Unhandled.dispatchEvent(te);
                        }
                        else
                        {
                            oldActivePressed.dispatchEvent(te);
                        }
                    }

                    if (DragStatus == DRAGGING)
                    {
                        // Trigger dragend:
                        DragEvent de = new DragEvent("dragend");
                        de.trigger = this;
                        de.SetModifiers();
                        de.SetTrusted();
                        de.clientX = ScreenX;
                        de.clientY = ScreenY;

                        if (oldActivePressed.dispatchEvent(de))
                        {
                            // Trigger a drop event next:
                            de.Reset();
                            de.EventType = "drop";
                            if (ActiveOverTarget != null && ActiveOverTarget.dispatchEvent(de))
                            {
                                // Proceed to try and drop it into the dropzone (ActiveOver).
                            }
                        }
                    }
                    else if (DragStatus == SELECTING)
                    {
                        // Finished selection - trigger selectionend:
                        Dom.Event sc = new Dom.Event("selectionend");
                        sc.SetTrusted();

                        // Dispatch on the element:
                        oldActivePressed.dispatchEvent(sc);
                    }

                    // Always clear drag status:
                    DragStatus      = 0;
                    MinDragDistance = 0f;
                }
            }
            else if (wasUp)
            {
                // It was up and it's now just gone down.

                // Cache position:
                DownDocumentX = DocumentX;
                DownDocumentY = DocumentY;

                // Cache down:
                ActivePressedTarget = ActiveOverTarget;

                // Trigger down event.

                if (ActivePressedTarget != null)
                {
                    // Refresh CSS (active; applies to parents too):

                    EventTarget current = ActivePressedTarget;

                    while (current != null)
                    {
                        // Get it as a renderable node:
                        IRenderableNode irn = (current as IRenderableNode);

                        if (irn != null)
                        {
                            irn.ComputedStyle.RefreshLocal();
                        }

                        current = current.eventTargetParentNode;
                    }
                }

                // Trigger down event.
                MouseEvent e = new MouseEvent(DocumentX, DocumentY, ButtonID, true);
                e.trigger   = this;
                e.EventType = "mousedown";
                e.SetModifiers();

                if (ActivePressedTarget == null)
                {
                    Input.Unhandled.dispatchEvent(e);
                }
                else
                {
                    ActivePressedTarget.dispatchEvent(e);
                }

                if (FireTouchEvents)
                {
                    // Trigger a touchend event too:
                    TouchEvent te = new TouchEvent("touchstart");
                    te.trigger = this;
                    te.clientX = DocumentX;
                    te.clientY = DocumentY;
                    te.SetTrusted();
                    te.SetModifiers();

                    if (ActivePressedTarget == null)
                    {
                        Input.Unhandled.dispatchEvent(te);
                    }
                    else
                    {
                        ActivePressedTarget.dispatchEvent(te);
                    }
                }
            }
        }
Esempio n. 17
0
        /// <summary>Relocates all DOM elements by calculating their onscreen position.
        /// Each element may allocate sections of the 3D mesh (blocks) which are then flushed out
        /// into the unity mesh and onto the screen.</summary>
        public void Layout()
        {
            DoLayout          = false;
            FullReflow        = true;
            HighestUpdateMode = UpdateMode.None;

            Reset();

            // Invalidate input pointers:
            // (So they figure out what elements are under the mouse/fingers)
            PowerUI.Input.PointersInvalid = true;

            // First, push all batches to the pool - inlined for speed:
            // Note that no isolated batches enter either the queue or the pool until their no longer isolated.
            if (FirstBatch != null)
            {
                LastBatch.BatchAfter = UIBatchPool.First;
                UIBatchPool.First    = FirstBatch;
            }

            FirstBatch = LastBatch = null;

            // Note: Batches are Prepared For Layout as they are added.

            LayoutOccuring = true;

            // Position elements locally.
            // This sets their ParentOffset values and as a result finds their PixelWidth.
            IRenderableNode root = RootDocument.documentElement as IRenderableNode;

            if (root != null)
            {
                // Perform the initial reflow:
                RenderableData rd = root.RenderData;

                rd.UpdateCss(this);
                rd.Reflow(this);

                // Next up, position them globally:
                // This calculates OffsetLeft/Top and also fires the render event on the computed style object.
                rd.Render(this);
            }

            LayoutOccuring = false;

            // Tell each batch we're done laying them out:
            UIBatch currentBatch = FirstBatch;

            while (currentBatch != null)
            {
                currentBatch.CompletedLayout();
                currentBatch = currentBatch.BatchAfter;
            }

            if (StylesToUpdate != null)
            {
                // Clear the isPainting flag.
                RenderableData style = StylesToUpdate;
                StylesToUpdate = null;

                while (style != null)
                {
                    style.NextUpdateMode = UpdateMode.None;
                    style = style.Next;
                }
            }

            // Hide all pool entries:
            UIBatchPool.HideAll();
            FullReflow = false;
        }
Esempio n. 18
0
        /// <summary>Update causes all changes to be applied and layouts to occur.</summary>
        public void Update()
        {
            if (DoLayout && AllowLayout)
            {
                // Layout RootDocument.
                Layout();
            }
            else if (StylesToUpdate != null)
            {
                // Local update - these events typically fire from changes to things like colour/z-index etc
                // as well as for reflows of "flow root" nodes.
                // It's done down here incase a full layout request is made (above).
                // If a full layout request was made, it would cover all of these anyway.

                UpdateMode modeToUse = HighestUpdateMode;
                HighestUpdateMode = UpdateMode.None;

                bool anyReflowMode = ((int)modeToUse > (int)UpdateMode.PaintAll);

                if (anyReflowMode)
                {
                    // We'll be re-rendering.
                    Reset();

                    // Invalidate input pointers:
                    // (So they figure out what elements are under the mouse/fingers)
                    PowerUI.Input.PointersInvalid = true;

                    // First, push all batches to the pool - inlined for speed:
                    // Note that no isolated batches enter either the queue or the pool until their no longer isolated.
                    if (FirstBatch != null)
                    {
                        LastBatch.BatchAfter = UIBatchPool.First;
                        UIBatchPool.First    = FirstBatch;
                    }

                    FirstBatch = LastBatch = null;

                    // Note: Batches are Prepared For Layout as they are added.
                    LayoutOccuring = true;
                }

                Css.RenderableData style = StylesToUpdate;
                StylesToUpdate = null;

                while (style != null)
                {
                    UpdateMode mode = style.NextUpdateMode;

                    switch (mode)
                    {
                    case UpdateMode.PaintAll:

                        // Don't bother if we're doing either kind of reflow:
                        if (anyReflowMode)
                        {
                            continue;
                        }

                        // Repaint it:
                        style.RepaintAll(this);

                        break;

                    case UpdateMode.Paint:

                        // Repaint it:
                        style.Repaint(this);

                        // Must also repaint the nodes child text nodes too (as they share the same CS):
                        NodeList kids = style.Node.childNodes_;

                        if (kids != null)
                        {
                            // For each child node..
                            for (int i = 0; i < kids.length; i++)
                            {
                                // Get it as a text node:
                                Node child = kids[i];

                                if (child is TextNode)
                                {
                                    // Repaint it too:
                                    (child as IRenderableNode).RenderData.Repaint(this);
                                }
                            }
                        }

                        break;

                    case UpdateMode.Reflow:

                        // Flow root reflow request.

                        // Must setup stacks and any other renderer settings here!

                        // Perform the initial reflow now:
                        style.UpdateCss(this);
                        style.Reflow(this);

                        break;

                    case UpdateMode.Render:

                        // Only call render:
                        style.Render(this);

                        break;

                        /*
                         * case UpdateMode.FastReflow:
                         *
                         *      // Fast reflow request only requires a repaint.
                         *
                         * break;
                         */
                    }

                    // Clear its update mode:
                    style.NextUpdateMode = UpdateMode.None;
                    style = style.Next;
                }


                if (!anyReflowMode)
                {
                    // Only a flush is required:
                    UIBatch toFlush = FirstBatch;

                    while (toFlush != null)
                    {
                        toFlush.Flush();
                        toFlush = toFlush.BatchAfter;
                    }
                }
                else
                {
                    // Position elements locally.
                    // This sets their ParentOffset values and as a result finds their PixelWidth.
                    IRenderableNode root = RootDocument.documentElement as IRenderableNode;

                    if (root != null)
                    {
                        // Finally, position them globally:
                        // This calculates OffsetLeft/Top and also fires the render event on the computed style object.
                        root.RenderData.Render(this);
                    }

                    LayoutOccuring = false;

                    // Tell each batch we're done laying them out:
                    UIBatch currentBatch = FirstBatch;

                    while (currentBatch != null)
                    {
                        currentBatch.CompletedLayout();
                        currentBatch = currentBatch.BatchAfter;
                    }

                    // Hide all pool entries:
                    UIBatchPool.HideAll();
                }
            }
        }
Esempio n. 19
0
        internal void BuildString(Node node)
        {
            ComputedDataFor = node;
            string result = "";

            if (node.Properties != null && node.Properties.Count > 0)
            {
                result += "Attributes:\r\n";

                foreach (KeyValuePair <string, string> kvp in node.Properties)
                {
                    result += kvp.Key + ": " + kvp.Value + "\r\n";
                }

                result += "\r\n";
            }

            // Is the node renderable?
            IRenderableNode renderable = (node as IRenderableNode);

            if (renderable != null)
            {
                result += "Applied Selectors:\r\n";

                // Get the computed style:
                ComputedStyle cs = renderable.ComputedStyle;

                // All applied styles (without allocating a new list):
                MatchingRoot match = cs.FirstMatch;

                while (match != null)
                {
                    // Note! These nodes can be participants from other nearby elements.
                    // That happens with any combined selector.
                    // To filter those ones out, check if this element is the actual target:
                    if (match.IsTarget)
                    {
                        // Rule is simply matching.Rule:
                        StyleRule rule = match.Rule;

                        // To get the underlying selector, it's rule.Selector:
                        Selector selector = rule.Selector;

                        // Note that this builds the selector text - avoid calling from OnGUI!
                        result += selector.selectorText + "\r\n";
                    }

                    // Next one:
                    match = match.NextInStyle;
                }

                result += "\r\n";

                result += "Computed style:\r\n";

                // Computed values:
                foreach (KeyValuePair <CssProperty, Css.Value> kvp in cs.Properties)
                {
                    // kvp.Key (a CSS property) is set to kvp.Value (a CSS value)
                    result += kvp.Key.Name + ": ";

                    if (kvp.Value is Css.Keywords.Inherit)
                    {
                        result += (kvp.Value as Css.Keywords.Inherit).From + " (inherit)";
                    }
                    else
                    {
                        result += kvp.Value;
                    }

                    result += "\r\n";
                }

                result += "\r\n";
                result += "Computed boxes:\r\n";

                // Computed values:
                RenderableData renderData = renderable.RenderData;

                if (renderData.FirstBox == null)
                {
                    result += "There's no boxes. The element isn't visible.\r\n";
                }
                else
                {
                    LayoutBox box = renderData.FirstBox;

                    while (box != null)
                    {
                        result += box.ToString() + " (Scroll: " + box.Scroll.Left + ", " + box.Scroll.Top + ")\r\n";
                        box     = box.NextInElement;
                    }
                }

                result += "\r\n";
            }

            // JS event hooks:
            EventTarget target = (node as EventTarget);

            if (target != null)
            {
                result += "Events:\r\n";

                if (target.Events == null || target.Events.Handlers == null || target.Events.Handlers.Count == 0)
                {
                    result += "No events hooked up to this element.\r\n";
                }
                else
                {
                    // Grab the JS event handlers:
                    Dictionary <string, List <EventListener> > allHandlers = target.Events.Handlers;

                    // Key is e.g. "mousedown"
                    // Value is all the things that'll run when the event triggers.
                    foreach (KeyValuePair <string, List <EventListener> > kvp in allHandlers)
                    {
                        int count = kvp.Value.Count;

                        string plural = count == 1?"":"s";

                        result += kvp.Key + ": (" + count + " listener" + plural + ")\r\n";
                    }
                }
            }

            ComputedNodeData = result;
        }