/// <summary>Fires an event into the scene of the camera.</summary> internal void FireIntoScene(UIEvent e) { if (e == null) { return; } // Get coords relative to the element. These are non-standard convenience properties: float localX = e.localX; float localY = e.localY; // Flip Y because Unity is upside down relative to input coords: localY = ScreenInfo.ScreenY - 1 - localY; // Fire off an input event at that point: RaycastHit worldUIHit; if (Physics.Raycast(Camera.ScreenPointToRay(new Vector2(localX, localY)), out worldUIHit)) { // Did it hit a worldUI? WorldUI worldUI = WorldUI.Find(worldUIHit); if (worldUI == null) { // Nope! return; } // Resolve the hit into a -0.5 to +0.5 point: float x; float y; worldUI.ResolvePoint(worldUIHit, out x, out y); // Map it from a relative point to a 'real' one: Vector2 point = worldUI.RelativePoint(x, y); // Apply to x/y: x = point.x; y = point.y; // Pull an element from that worldUI: Element el = worldUI.document.elementFromPointOnScreen(x, y); if (el != null) { // Fire the event for that element: el.dispatchEvent(e); } } }
/// <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) { Debug.Log("HiInput TouchCount: " + touchCount.ToString()); // 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: pointer = InputPointer.AllRaw[p]; if (pointer.ID == touch.fingerId) { // Got it! 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.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.LatestPressure = touch.pressure; tp.Radius = touch.radius; tp.RadiusVariance = touch.radiusVariance; // Is it a stylus? if (tp is StylusPointer) { tp.UpdateStylus(touch.azimuthAngle, touch.altitudeAngle); } // Always a pressure of 1 otherwise (which is set by 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; 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; Element newActiveOver = ElementFromPoint(ref documentX, ref documentY, pointer); // Update docX/Y: pointer.DocumentX = documentX; pointer.DocumentY = documentY; // Get the old one: Element oldActiveOver = pointer.ActiveOver; // 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.parentNode_ != newActiveOver) { mouseEvent.Reset(); mouseEvent.bubbles = false; mouseEvent.EventType = "mouseleave"; mouseEvent.relatedTarget = newActiveOver; oldActiveOver.dispatchEvent(mouseEvent); } } // Update it: pointer.ActiveOver = newActiveOver; if (oldActiveOver != null) { // Update the CSS (hover; typically out): (oldActiveOver as IRenderableNode).ComputedStyle.RefreshLocal(true); } if (newActiveOver != null) { // Update the CSS (hover; typically in) (newActiveOver as IRenderableNode).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["title"]; // And a mouseenter (doesn't bubble). // Only triggered if newActiveOver is *not* a child of oldActiveOver. if (newActiveOver.parentNode_ != 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); if (tp.LatestPressure != tp.Pressure) { 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.ActivePressed == null) { // Trigger on unhandled: Unhandled.dispatchEvent(te); } else { pointer.ActivePressed.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.ActiveUpdating = GetDraggable(pointer.ActivePressed); // Find the min drag distance: pointer.MinDragDistance = pointer.GetMinDragDistance(); } if (pointer.MovedBeyondDragDistance) { // Possibly dragging. // Actually marked as 'draggable'? if (pointer.ActiveUpdating != 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.ActivePressed.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? ComputedStyle cs = (pointer.ActivePressed as IRenderableNode).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.ActivePressed 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.ActivePressed != 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.ActivePressed.dispatchEvent(de)) { // Note that onDrag runs on the *dragging* element only. Element dragging = (pointer.ActiveUpdating == null)? pointer.ActivePressed : pointer.ActiveUpdating; 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.ActivePressed != null) { DragEvent de = new DragEvent("drag"); de.trigger = pointer; de.SetModifiers(); de.SetTrusted(); de.clientX = pointer.DocumentX; de.clientY = pointer.DocumentY; if (pointer.ActivePressed.dispatchEvent(de)) { // Get the current selection: Selection s = (pointer.ActivePressed.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: Element oldActivePressed = ActivePressed; // Clear: ActivePressed = null; if (oldActivePressed != null) { // Refresh CSS (active): (oldActivePressed as IRenderableNode).ComputedStyle.RefreshLocal(); } // 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 == ActiveOver && 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)) { // Perform the default: HtmlElement h = (oldActivePressed as HtmlElement); if (h != null) { h.OnClickEvent(e); } // Clear selection if there is one: (oldActivePressed.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 (ActiveOver != null && ActiveOver.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: ActivePressed = ActiveOver; // Trigger down event. if (ActivePressed != null) { // Refresh CSS (active): (ActivePressed as IRenderableNode).ComputedStyle.RefreshLocal(); } // Trigger down event. MouseEvent e = new MouseEvent(DocumentX, DocumentY, ButtonID, true); e.trigger = this; e.EventType = "mousedown"; e.SetModifiers(); if (ActivePressed == null) { Input.Unhandled.dispatchEvent(e); } else { ActivePressed.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 (ActivePressed == null) { Input.Unhandled.dispatchEvent(te); } else { ActivePressed.dispatchEvent(te); } } } }