/// <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; } } } } }
public override void OnClickEvent(MouseEvent clickEvent) { // Focus it: focus(); if (Type == InputType.Submit) { // Find the form and then attempt to submit it. HtmlFormElement f = form; if (f != null) { f.submit(this); } } else if (Type == InputType.Radio) { Select(); } else if (Type == InputType.Checkbox) { if (Checked_) { Unselect(); } else { Select(); } } else if (IsTextInput()) { // Move the caret to the clicked point. // Get the text content: RenderableTextNode htn = TextHolder; if (htn == null) { // Index is just 0. return; } // Get the letter index: int index = htn.LetterIndex(clickEvent.clientX, clickEvent.clientY); // Move the caret there (requesting a redraw): MoveCaret(index, true); } }
/// <summary>Gets the next/previous suitable newline index.</summary> public int FindNewline(int direction) { // Get the text content: RenderableTextNode htn = TextHolder; if (htn == null || htn.RenderData.Text == null || htn.RenderData.Text.Characters == null) { return(CaretIndex); } // Get the renderer so we can access the chars: TextRenderingProperty trp = htn.RenderData.Text; // First index to check is.. int index = CaretIndex + direction; // Safety check: int max = trp.Characters.Length; if (index >= max) { index = max - 1; } else if (index < 0) { index = 0; } while (index > 0 && index < max) { // Check if char[index] is a newline: InfiniText.Glyph glyph = trp.Characters[index]; if (glyph != null && glyph.Charcode == (int)'\n') { // Got it! if (direction == 1) { index++; } break; } // Next one: index += direction; } return(index); }
public override void OnClickEvent(MouseEvent clickEvent) { // Ensure it's focused: focus(); // Get the text content: RenderableTextNode htn = TextHolder; if (htn == null) { // Index is just 0. return; } // Get the letter index: int index = htn.LetterIndex(clickEvent.clientX, clickEvent.clientY); // Move the caret there (requesting a redraw): MoveCaret(index, true); }
public override void OnComputeBox(Renderman renderer, Css.LayoutBox box, ref bool widthUndefined, ref bool heightUndefined) { // Locate the caret if we need to: if (Locate) { Locate = false; RenderableTextNode htn = TextHolder; Vector2 position; if (htn == null) { // Just at 0,0: position = Vector2.zero; } else { // Clip: if (Index >= htn.length) { Index = htn.length; } // Get the position of the given letter: position = htn.GetPosition(Index); } // Scroll it if position is out of range: ScrollIfBeyond(ref position); // Set it in for this pass: box.Position.Top = position.y; box.Position.Left = position.x; // Write it out: Style.Computed.ChangeTagProperty("left", new Css.Units.DecimalUnit(position.x), false); Style.Computed.ChangeTagProperty("top", new Css.Units.DecimalUnit(position.y), false); } }
/// <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>Selects/ Deselects a whole range.</summary> internal void UpdateSelection(bool select, Range r) { if (r.startContainer == null) { return; } // Get as text node: RenderableTextNode htn = (r.startContainer as RenderableTextNode); if (htn == null) { // Can't select this at the moment. Dom.Log.Add("Note: Attempted to select something that isn't text. If you want this to work, let us know!"); return; } // Text selection from r.startOffset -> r.endOffset // Get the selection renderer: SelectionRenderingProperty srp = htn.RenderData.GetProperty( typeof(SelectionRenderingProperty) ) as SelectionRenderingProperty; if (select) { // Create if it doesn't exist: if (srp == null) { // Create: srp = new SelectionRenderingProperty(htn.RenderData); srp.Text = htn.RenderData.Text; // Add it: htn.RenderData.AddOrReplaceProperty(srp, typeof(SelectionRenderingProperty)); } // Update range: if (r.endOffset < r.startOffset) { // Dragged upwards: srp.StartIndex = r.endOffset; srp.EndIndex = r.startOffset; } else { srp.StartIndex = r.startOffset; srp.EndIndex = r.endOffset; } // Layout: srp.RequestLayout(); } else if (srp != null) { // Remove: htn.RenderData.AddOrReplaceProperty(null, typeof(SelectionRenderingProperty)); // Layout: srp.RequestLayout(); } }