A game framework written with osu! in mind.
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;
5using CoreGraphics;
6using Foundation;
7using JetBrains.Annotations;
8using osu.Framework.Input.Handlers;
9using osu.Framework.Input.StateChanges;
10using osu.Framework.Platform;
11using osuTK;
12using UIKit;
13
14namespace osu.Framework.iOS.Input
15{
16 /// <summary>
17 /// Handles scroll and positional updates for external cursor-based input devices.
18 /// Click / touch handling is still provided by <see cref="IOSTouchHandler"/>.
19 /// </summary>
20 public class IOSMouseHandler : InputHandler
21 {
22 private readonly IOSGameView view;
23 private UIPointerInteraction pointerInteraction;
24 private UIPanGestureRecognizer panGestureRecognizer;
25 private CGPoint lastScrollTranslation;
26
27 [UsedImplicitly]
28 private IOSMouseDelegate mouseDelegate;
29
30 public IOSMouseHandler(IOSGameView view)
31 {
32 this.view = view;
33 }
34
35 public override bool IsActive => true;
36
37 public override bool Initialize(GameHost host)
38 {
39 // UIPointerInteraction is only available on iOS 13.4 and up
40 if (!UIDevice.CurrentDevice.CheckSystemVersion(13, 4))
41 return false;
42
43 view.AddInteraction(pointerInteraction = new UIPointerInteraction(mouseDelegate = new IOSMouseDelegate()));
44 mouseDelegate.LocationUpdated += locationUpdated;
45
46 view.AddGestureRecognizer(panGestureRecognizer = new UIPanGestureRecognizer(panOffsetUpdated)
47 {
48 AllowedScrollTypesMask = UIScrollTypeMask.Continuous,
49 MaximumNumberOfTouches = 0 // Only handle drags when no "touches" are active (ie. no buttons are in a pressed state)
50 });
51
52 return true;
53 }
54
55 private void locationUpdated(CGPoint location)
56 {
57 PendingInputs.Enqueue(new MousePositionAbsoluteInput
58 {
59 Position = new Vector2((float)location.X * view.Scale, (float)location.Y * view.Scale)
60 });
61 }
62
63 private const float scroll_rate_adjust = 0.1f;
64
65 private void panOffsetUpdated()
66 {
67 CGPoint translation = panGestureRecognizer.TranslationInView(view);
68
69 Vector2 delta;
70
71 switch (panGestureRecognizer.State)
72 {
73 case UIGestureRecognizerState.Began:
74 // consume initial value.
75 delta = new Vector2((float)translation.X, (float)translation.Y);
76 break;
77
78 default:
79 // only consider relative change from previous value.
80 delta = new Vector2((float)(translation.X - lastScrollTranslation.X), (float)(translation.Y - lastScrollTranslation.Y));
81 break;
82 }
83
84 lastScrollTranslation = translation;
85
86 PendingInputs.Enqueue(new MouseScrollRelativeInput
87 {
88 IsPrecise = true,
89 Delta = delta * scroll_rate_adjust
90 });
91 }
92
93 protected override void Dispose(bool disposing)
94 {
95 base.Dispose(disposing);
96
97 if (pointerInteraction != null)
98 view.RemoveInteraction(pointerInteraction);
99
100 if (panGestureRecognizer != null)
101 view.RemoveGestureRecognizer(panGestureRecognizer);
102 }
103 }
104
105 public class IOSMouseDelegate : NSObject, IUIPointerInteractionDelegate
106 {
107 public Action<CGPoint> LocationUpdated;
108
109 [Export("pointerInteraction:regionForRequest:defaultRegion:")]
110 public UIPointerRegion GetRegionForRequest(UIPointerInteraction interaction, UIPointerRegionRequest request, UIPointerRegion defaultRegion)
111 {
112 LocationUpdated(request.Location);
113 return defaultRegion;
114 }
115
116 [Export("pointerInteraction:styleForRegion:")]
117 public UIPointerStyle GetStyleForRegion(UIPointerInteraction interaction, UIPointerRegion region) =>
118 UIPointerStyle.CreateHiddenPointerStyle();
119 }
120}