Created
August 21, 2014 18:08
-
-
Save jonlipsky/a960e8b91550b5407171 to your computer and use it in GitHub Desktop.
TouchWindow.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Drawing; | |
using MonoTouch.CoreGraphics; | |
using MonoTouch.UIKit; | |
using MonoTouch.Foundation; | |
using MonoTouch.ObjCRuntime; | |
/** | |
* Copyright (c) 2014 Jon Lipsky | |
* | |
* Licensed under The MIT License (MIT) | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software | |
* and associated documentation files (the "Software"), to deal in the Software without | |
* restriction, including without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom | |
* the Software is furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all copies or | |
* substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING | |
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
* | |
* | |
* This is loosely based on the Objective-C library "Fingertips" which can be found here: | |
* https://github.com/mapbox/Fingertips | |
* | |
* Usage: | |
* | |
* Update your app delegate to create a "TouchWindow" as opposed to a "UIWindow" | |
* | |
* public override bool FinishedLaunching(UIApplication app, NSDictionary options) | |
* { | |
* window = new TouchWindow (UIScreen.MainScreen.Bounds); | |
* | |
* applicationController = new ApplicationController (); | |
* window.RootViewController = applicationController; | |
* window.MakeKeyAndVisible (); | |
* | |
* return true; | |
* } | |
* | |
* By default, it will only display the touchpoints when the screen is mirrored (such as when using AirPlay); | |
* however you can use the Mode property to configure that. | |
*/ | |
namespace Elevenworks.MonoTouch | |
{ | |
/// <summary> | |
/// The display mode for the TouchWindow. | |
/// </summary> | |
public enum TouchWindowMode | |
{ | |
/// <summary> | |
/// Never show the touchpoints | |
/// </summary> | |
ShowNever, | |
/// <summary> | |
/// Only show the touchpoints when the screen is being mirrored. | |
/// </summary> | |
ShowWhenMirrored, | |
/// <summary> | |
/// Always show the touchpoints. | |
/// </summary> | |
ShowAlways | |
} | |
/// <summary> | |
/// A UIWindow subclass that can display the touchpoints on the screen. | |
/// </summary> | |
public class TouchWindow : UIWindow, IDisposable | |
{ | |
private const float AnimationDuration = 0.3f; | |
private List<NSObject> observers = new List<NSObject>(); | |
private Dictionary<UITouch, TouchView> touchViews = new Dictionary<UITouch, TouchView> (); | |
private UIImage touchImage; | |
private UIWindow overlayWindow; | |
private TouchWindowMode mode = TouchWindowMode.ShowWhenMirrored; | |
private bool active; | |
private bool removalScheduled; | |
public TouchWindow (IntPtr handle) : base (handle) | |
{ | |
} | |
public TouchWindow (RectangleF frame) : base (frame) | |
{ | |
observers.Add(NSNotificationCenter.DefaultCenter.AddObserver(UIScreen.DidConnectNotification, (obj) => { UpdateActive(); } )); | |
observers.Add(NSNotificationCenter.DefaultCenter.AddObserver(UIScreen.DidDisconnectNotification, (obj) => { UpdateActive(); })); | |
UpdateActive(); | |
} | |
public TouchWindowMode Mode | |
{ | |
get { return mode; } | |
set | |
{ | |
mode = value; | |
UpdateActive (); | |
} | |
} | |
public UIWindow Overlay | |
{ | |
get | |
{ | |
if (overlayWindow == null) | |
{ | |
overlayWindow = new TouchOverlayWindow(Frame); | |
overlayWindow.UserInteractionEnabled = false; | |
overlayWindow.WindowLevel = UIWindowLevel.StatusBar; | |
overlayWindow.BackgroundColor = UIColor.Clear; | |
overlayWindow.Hidden = false; | |
} | |
return overlayWindow; | |
} | |
set | |
{ | |
overlayWindow = value; | |
} | |
} | |
public UIImage TouchImage | |
{ | |
get | |
{ | |
if (touchImage == null) | |
{ | |
UIGraphics.BeginImageContextWithOptions(new SizeF(50,50), false, 0); | |
var context = UIGraphics.GetCurrentContext (); | |
context.SetLineWidth (2); | |
context.SetStrokeColor (new [] { 0f, 0f, 0f, 1f }); // Black | |
context.SetFillColor (new [] { 0.5f, 0.5f, 0.5f, .5f }); | |
context.AddEllipseInRect (new RectangleF (3, 3, 44, 44)); | |
context.FillPath (); | |
context.StrokePath (); | |
touchImage = UIGraphics.GetImageFromCurrentImageContext (); | |
UIGraphics.EndImageContext(); | |
} | |
return touchImage; | |
} | |
set | |
{ | |
touchImage = value; | |
} | |
} | |
protected override void Dispose (bool disposing) | |
{ | |
NSNotificationCenter.DefaultCenter.RemoveObservers(observers); | |
observers.Clear (); | |
} | |
public bool IsMirrored() | |
{ | |
foreach (UIScreen screen in UIScreen.Screens) | |
{ | |
if (screen.MirroredScreen != null) return true; | |
} | |
return false; | |
} | |
private void UpdateActive() | |
{ | |
bool wasActive = active; | |
switch (mode) | |
{ | |
case TouchWindowMode.ShowAlways: | |
active = true; | |
break; | |
case TouchWindowMode.ShowWhenMirrored: | |
active = IsMirrored (); | |
break; | |
default: | |
active = false; | |
} | |
if (wasActive && !active) | |
{ | |
RemoveAllTouches (); | |
} | |
} | |
public override void SendEvent (UIEvent evt) | |
{ | |
if (active) | |
{ | |
var allTouches = evt.AllTouches; | |
if (allTouches != null) | |
{ | |
foreach (var touchObject in allTouches) | |
{ | |
var touch = (UITouch)touchObject; | |
switch (touch.Phase) | |
{ | |
case UITouchPhase.Began: | |
case UITouchPhase.Moved: | |
case UITouchPhase.Stationary: | |
{ | |
TouchView touchView; | |
touchViews.TryGetValue (touch, out touchView); | |
if (touchView == null) | |
{ | |
touchView = new TouchView (TouchImage, touch); | |
Overlay.AddSubview (touchView); | |
touchViews.Add (touch, touchView); | |
} | |
if (!touchView.Removing) | |
{ | |
touchView.Center = touch.LocationInView (Overlay); | |
touchView.RemoveAfter = touch.Timestamp + .2; | |
touchView.RemoveAutomatically = ShouldRemoveAutomatically (touch); | |
} | |
break; | |
} | |
case UITouchPhase.Ended: | |
case UITouchPhase.Cancelled: | |
{ | |
RemoveTouch (touch, true); | |
break; | |
} | |
} | |
} | |
} | |
base.SendEvent (evt); | |
ScheduleRemoval (); | |
} | |
else | |
{ | |
base.SendEvent (evt); | |
} | |
} | |
private static Selector removeExpiredTouches = new Selector("RemoveExpiredTouches"); | |
public void ScheduleRemoval() | |
{ | |
if (removalScheduled) return; | |
removalScheduled = true; | |
PerformSelector (removeExpiredTouches, this, 0.1f); | |
} | |
[Export("RemoveExpiredTouches")] | |
public void RemoveExpiredTouches() | |
{ | |
var now = NSProcessInfo.ProcessInfo.SystemUptime; | |
foreach (var subView in Overlay.Subviews) | |
{ | |
var touchView = subView as TouchView; | |
if (touchView != null) | |
{ | |
if (touchView.RemoveAutomatically) | |
{ | |
if (now > touchView.RemoveAfter) | |
{ | |
var touch = touchView.Touch; | |
RemoveTouch (touch, true); | |
} | |
} | |
} | |
} | |
removalScheduled = false; | |
if (Overlay.Subviews.Length > 0) | |
{ | |
ScheduleRemoval (); | |
} | |
} | |
public void RemoveAllTouches() | |
{ | |
foreach (var subView in Overlay.Subviews) | |
{ | |
var touchView = subView as TouchView; | |
if (touchView != null) | |
{ | |
var touch = touchView.Touch; | |
RemoveTouch (touch, true); | |
} | |
} | |
removalScheduled = false; | |
} | |
private void RemoveTouch(UITouch touch, bool animated) | |
{ | |
TouchView touchView; | |
if (touchViews.TryGetValue (touch, out touchView)) | |
{ | |
touchViews.Remove (touch); | |
if (!touchView.Removing) | |
{ | |
touchView.Removing = true; | |
UIView.Animate (AnimationDuration, () => | |
{ | |
touchView.Frame = new RectangleF ( | |
touchView.Center.X - touchView.Frame.Size.Width, | |
touchView.Center.Y - touchView.Frame.Size.Height, | |
touchView.Frame.Size.Width * 2, | |
touchView.Frame.Size.Height * 2); | |
touchView.Alpha = 0.0f; | |
}, () => | |
{ | |
touchView.RemoveFromSuperview (); | |
}); | |
} | |
} | |
} | |
private bool ShouldRemoveAutomatically(UITouch touch) | |
{ | |
UIView view = touch.View; | |
if (view != null) | |
{ | |
var location = touch.LocationInView (view); | |
view = view.HitTest (location, null); | |
while (view != null) | |
{ | |
if (view is UITableViewCell) | |
{ | |
foreach (UIGestureRecognizer recognizer in touch.GestureRecognizers) | |
{ | |
if (recognizer is UISwipeGestureRecognizer) | |
{ | |
return true; | |
} | |
} | |
} | |
if (view is UITableView) | |
{ | |
if (touch.GestureRecognizers.Length == 0) | |
{ | |
return true; | |
} | |
} | |
view = view.Superview; | |
} | |
} | |
return false; | |
} | |
} | |
public class TouchView : UIImageView | |
{ | |
private readonly UITouch touch; | |
public TouchView (UIImage image, UITouch touch) : base (image) | |
{ | |
this.touch = touch; | |
} | |
public double RemoveAfter {get; set;} | |
public bool RemoveAutomatically {get; set;} | |
public bool Removing {get; set;} | |
public UITouch Touch | |
{ | |
get { return touch; } | |
} | |
} | |
public class TouchOverlayWindow : UIWindow | |
{ | |
public TouchOverlayWindow(RectangleF rect) : base (rect) | |
{ | |
} | |
public override UIViewController RootViewController | |
{ | |
get | |
{ | |
foreach (var window in UIApplication.SharedApplication.Windows) | |
{ | |
if (this == window) continue; | |
var realRootViewController = window.RootViewController; | |
if (realRootViewController != null) | |
{ | |
return realRootViewController; | |
} | |
} | |
return base.RootViewController; | |
} | |
set | |
{ | |
base.RootViewController = value; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment