/// <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>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); } } } }