/// <summary>Removes the pointers marked for removal.</summary> internal static void Tidy() { int tidyIndex = 0; for (int i = 0; i < PointerCount; i++) { // Get the pointer: InputPointer pointer = AllRaw[i]; if (pointer.Removed) { continue; } // Transfer: if (tidyIndex != i) { AllRaw[tidyIndex] = pointer; } // Move index up: tidyIndex++; } // Update count: PointerCount = tidyIndex; }
/// <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; } } } } }
/// <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 }
/// <summary>Finds an EventTarget from the given screen location. /// This fires a ray into the scene if the point isn't on the main UI and caches the hit in the given pointer. /// X and Y are updated with the document-relative point if it's on a WorldUI/ on the Unity UI.</summary> public static EventTarget TargetFromPoint(ref float x,ref float y,InputPointer pointer){ Element result=null; if(UI.document!=null){ // Test the main UI: result=UI.document.elementFromPointOnScreen(x,y); if(result!=null){ // Is it transparent or not html/body? if(AcceptsInput(result)){ // Great, we're done! if(pointer!=null){ pointer.LatestHitSuccess=false; } return result; } } } // Handle a Unity UI if needed. Vector2 point; #if HANDLE_UNITY_UI if(UnityUICaster!=null){ // Try hitting the Unity UI first. point=new Vector2(x,ScreenInfo.ScreenY-1-y); HtmlDocument panelDoc=MapToUIPanel(ref point); if(panelDoc!=null){ // Test for an element there: result=panelDoc.elementFromPointOnScreen(point.x,point.y); if(result!=null){ // Is it transparent? // We're still checking a UI so we might still pass "through" it. if(AcceptsInput(result)){ // Got a hit! Update x/y: x=point.x; y=point.y; if(pointer!=null){ pointer.LatestHitSuccess=false; } return result; } } } } #endif // Go after WorldUI's next. RaycastHit worldUIHit; Camera cam=CameraFor3DInput; if(cam==null){ // No camera! if(pointer!=null){ pointer.LatestHitSuccess=false; } return null; } Vector2 screenPoint = new Vector2(x,ScreenInfo.ScreenY-1-y); if(pointer == null){ // Cast into the scene now: Ray ray=cam.ScreenPointToRay(screenPoint); if(!Physics.Raycast(ray,out worldUIHit)){ // Nothing hit. return null; } }else if(!pointer.Raycast(out worldUIHit,cam,screenPoint)){ // Nothing hit. pointer.LatestHitSuccess=false; return null; } // Cache it: if(pointer!=null){ pointer.LatestHit=worldUIHit; pointer.LatestHitSuccess=true; } // Did it hit a worldUI? WorldUI worldUI=WorldUI.Find(worldUIHit); if(worldUI==null){ // Nope! Get the eventTarget for the gameObject: #if Enable3DInput return worldUIHit.transform.gameObject.getEventTarget(); #else return null; #endif } // Resolve the hit into a -0.5 to +0.5 point: // (we're ok to reuse x/y here): worldUI.ResolvePoint(worldUIHit,out x,out y); // Map it from a relative point to a 'real' one: point=worldUI.RelativePoint(x,y); // Apply to x/y: x=point.x; y=point.y; // Pull an element from that worldUI: return worldUI.document.elementFromPointOnScreen(x,y); }
/// <summary>Finds an Element from the given screen location. /// This fires a ray into the scene if the point isn't on the main UI and caches the hit in the given pointer. /// X and Y are updated with the document-relative point if it's on a WorldUI/ on the Unity UI.</summary> public static Element ElementFromPoint(ref float x,ref float y,InputPointer pointer){ return TargetFromPoint(ref x,ref y,pointer) as Element; }
/// <summary>Doubles the size of the pointer stack, AllRaw.</summary> private static void ResizePointerStack() { InputPointer[] newStack = new InputPointer[AllRaw.Length * 2]; Array.Copy(AllRaw, 0, newStack, 0, AllRaw.Length); AllRaw = newStack; }