A game framework written with osu! in mind.
at master 5.7 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System.Diagnostics; 5using Foundation; 6using osu.Framework.Input; 7using osu.Framework.Input.Handlers; 8using osu.Framework.Input.StateChanges; 9using osu.Framework.Input.States; 10using osu.Framework.Platform; 11using osuTK; 12using osuTK.Input; 13using UIKit; 14 15namespace osu.Framework.iOS.Input 16{ 17 public class IOSTouchHandler : InputHandler 18 { 19 private readonly IOSGameView view; 20 21 private UIEventButtonMask? lastButtonMask; 22 23 private readonly bool indirectPointerSupported = UIDevice.CurrentDevice.CheckSystemVersion(13, 4); 24 25 private readonly UITouch[] activeTouches = new UITouch[TouchState.MAX_TOUCH_COUNT]; 26 27 public IOSTouchHandler(IOSGameView view) 28 { 29 this.view = view; 30 view.HandleTouches += handleTouches; 31 } 32 33 private void handleTouches(NSSet obj, UIEvent evt) 34 { 35 foreach (var t in obj) 36 handleUITouch((UITouch)t, evt); 37 } 38 39 private void handleUITouch(UITouch touch, UIEvent e) 40 { 41 var cgLocation = touch.LocationInView(null); 42 Vector2 location = new Vector2((float)cgLocation.X * view.Scale, (float)cgLocation.Y * view.Scale); 43 44 if (indirectPointerSupported && touch.Type == UITouchType.IndirectPointer) 45 handleIndirectPointer(touch, e.ButtonMask, location); 46 else 47 handleTouch(touch, location); 48 } 49 50 private void handleIndirectPointer(UITouch touch, UIEventButtonMask buttonMask, Vector2 location) 51 { 52 PendingInputs.Enqueue(new MousePositionAbsoluteInput { Position = location }); 53 54 // Indirect pointer means the touch came from a mouse cursor, and wasn't a physical touch on the screen 55 switch (touch.Phase) 56 { 57 case UITouchPhase.Began: 58 case UITouchPhase.Moved: 59 // only one button can be in a "down" state at once. all previous buttons are automatically released. 60 // we need to handle this assumption at our end. 61 if (lastButtonMask != null && lastButtonMask != buttonMask) 62 PendingInputs.Enqueue(new MouseButtonInput(buttonFromMask(lastButtonMask.Value), false)); 63 64 PendingInputs.Enqueue(new MouseButtonInput(buttonFromMask(buttonMask), true)); 65 lastButtonMask = buttonMask; 66 break; 67 68 case UITouchPhase.Cancelled: 69 case UITouchPhase.Ended: 70 Debug.Assert(lastButtonMask != null); 71 72 PendingInputs.Enqueue(new MouseButtonInput(buttonFromMask(lastButtonMask.Value), false)); 73 lastButtonMask = null; 74 break; 75 } 76 } 77 78 private void handleTouch(UITouch uiTouch, Vector2 location) 79 { 80 TouchSource? existingSource = getTouchSource(uiTouch); 81 82 if (uiTouch.Phase == UITouchPhase.Began) 83 { 84 // need to assign the new touch. 85 Debug.Assert(existingSource == null); 86 87 existingSource = assignNextAvailableTouchSource(uiTouch); 88 } 89 90 if (existingSource == null) 91 return; 92 93 var touch = new Touch(existingSource.Value, location); 94 95 // standard touch handling 96 switch (uiTouch.Phase) 97 { 98 case UITouchPhase.Began: 99 case UITouchPhase.Moved: 100 PendingInputs.Enqueue(new TouchInput(touch, true)); 101 break; 102 103 case UITouchPhase.Cancelled: 104 case UITouchPhase.Ended: 105 PendingInputs.Enqueue(new TouchInput(touch, false)); 106 107 // touch no longer valid, remove from reference array. 108 activeTouches[(int)existingSource] = null; 109 break; 110 } 111 } 112 113 private TouchSource? assignNextAvailableTouchSource(UITouch uiTouch) 114 { 115 for (int i = 0; i < activeTouches.Length; i++) 116 { 117 if (activeTouches[i] != null) continue; 118 119 activeTouches[i] = uiTouch; 120 return (TouchSource)i; 121 } 122 123 // we only handle up to TouchState.MAX_TOUCH_COUNT. Ignore any further touches for now. 124 return null; 125 } 126 127 private TouchSource? getTouchSource(UITouch touch) 128 { 129 for (int i = 0; i < activeTouches.Length; i++) 130 { 131 // The recommended (and only) way to track touches is storing and comparing references of the UITouch objects. 132 // https://stackoverflow.com/questions/39823914/how-to-track-multiple-touches 133 if (ReferenceEquals(activeTouches[i], touch)) 134 return (TouchSource)i; 135 } 136 137 return null; 138 } 139 140 private MouseButton buttonFromMask(UIEventButtonMask buttonMask) 141 { 142 Debug.Assert(indirectPointerSupported); 143 144 switch (buttonMask) 145 { 146 default: 147 return MouseButton.Left; 148 149 case UIEventButtonMask.Secondary: 150 return MouseButton.Right; 151 } 152 } 153 154 protected override void Dispose(bool disposing) 155 { 156 view.HandleTouches -= handleTouches; 157 base.Dispose(disposing); 158 } 159 160 public override bool IsActive => true; 161 162 public override bool Initialize(GameHost host) => true; 163 } 164}