From 5ac1f7c0c5dbfc511e822a61dd08e9791fb123d0 Mon Sep 17 00:00:00 2001 From: donandren Date: Sat, 5 Dec 2015 15:46:44 +0200 Subject: [PATCH 1/2] ios keyboard support --- src/iOS/Perspex.iOS/Perspex.iOS.csproj | 1 + src/iOS/Perspex.iOS/PerspexView.cs | 16 ++ .../Specific/KeyboardEventsHelper.cs | 147 ++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs diff --git a/src/iOS/Perspex.iOS/Perspex.iOS.csproj b/src/iOS/Perspex.iOS/Perspex.iOS.csproj index 1a5866908b..0d670ac5ed 100644 --- a/src/iOS/Perspex.iOS/Perspex.iOS.csproj +++ b/src/iOS/Perspex.iOS/Perspex.iOS.csproj @@ -42,6 +42,7 @@ + diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 1bea986c98..43d941fba3 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -14,26 +14,41 @@ using Perspex.Media; using Perspex.Platform; using Perspex.Skia.iOS; using UIKit; +using Perspex.iOS.Specific; +using ObjCRuntime; namespace Perspex.iOS { + [Adopts("UIKeyInput")] class PerspexView : SkiaView, IWindowImpl { private readonly UIWindow _window; private readonly UIViewController _controller; private IInputRoot _inputRoot; + private readonly KeyboardEventsHelper _keyboardHelper; public PerspexView(UIWindow window, UIViewController controller) : base(onFrame => PlatformThreadingInterface.Instance.Render = onFrame) { if (controller == null) throw new ArgumentNullException(nameof(controller)); _window = window; _controller = controller; + _keyboardHelper = new KeyboardEventsHelper(this); AutoresizingMask = UIViewAutoresizing.All; AutoFit(); UIApplication.Notifications.ObserveDidChangeStatusBarOrientation(delegate { AutoFit(); }); UIApplication.Notifications.ObserveDidChangeStatusBarFrame(delegate { AutoFit(); }); } + [Export("hasText")] + bool HasText => _keyboardHelper.HasText(); + + [Export("insertText:")] + void InsertText(string text) => _keyboardHelper.InsertText(text); + + [Export("deleteBackward")] + void DeleteBackward() => _keyboardHelper.DeleteBackward(); + + public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder(); void AutoFit() { @@ -89,6 +104,7 @@ namespace Perspex.iOS public void Show() { + _keyboardHelper.ActivateAutoShowKeybord(); } public Size MaxClientSize => Bounds.Size.ToPerspex(); diff --git a/src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs b/src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs new file mode 100644 index 0000000000..872f9cd37d --- /dev/null +++ b/src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs @@ -0,0 +1,147 @@ +using ObjCRuntime; +using Perspex.Controls; +using Perspex.Input; +using Perspex.Input.Raw; +using Perspex.Platform; +using System; +using System.ComponentModel; +using System.Linq; +using UIKit; + +namespace Perspex.iOS.Specific +{ + /// + /// In order to have properly handle of keyboard event in iOS View should already made some things in the View: + /// 1. Adopt the UIKeyInput protocol - add [Adopts("UIKeyInput")] to your view class + /// 2. Implement all the methods required by UIKeyInput: + /// 2.1 Implement HasText + /// example: + /// [Export("hasText")] + /// bool HasText => _keyboardHelper.HasText() + /// 2.2 Implement InsertText + /// example: + /// [Export("insertText:")] + /// void InsertText(string text) => _keyboardHelper.InsertText(text); + /// 2.3 Implement InsertText + /// example: + /// [Export("deleteBackward")] + /// void DeleteBackward() => _keyboardHelper.DeleteBackward(); + /// 3.Let iOS know that this can become a first responder: + /// public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder(); + /// or + /// public override bool CanBecomeFirstResponder { get { return true; } } + /// + /// 4. To show keyboard: + /// view.BecomeFirstResponder(); + /// 5. To hide keyboard + /// view.ResignFirstResponder(); + /// + /// View that needs keyboard events and show/hide keyboard + internal class KeyboardEventsHelper where TView : UIView, IWindowImpl + { + private TView _view; + private IInputElement _lastFocusedElement; + + public KeyboardEventsHelper(TView view) + { + _view = view; + + var uiKeyInputAttribute = view.GetType().GetCustomAttributes(typeof(AdoptsAttribute), true).OfType().Where(a => a.ProtocolType == "UIKeyInput").FirstOrDefault(); + + if (uiKeyInputAttribute == null) throw new NotSupportedException($"View class {typeof(TView).Name} should have class attribute - [Adopts(\"UIKeyInput\")] in order to access keyboard events!"); + + HandleEvents = true; + } + + /// + /// HandleEvents in order to suspend keyboard notifications or resume it + /// + public bool HandleEvents { get; set; } + + public bool HasText() => false; + + public bool CanBecomeFirstResponder() => true; + + public void DeleteBackward() + { + HandleKey(Key.Back, RawKeyEventType.KeyDown); + HandleKey(Key.Back, RawKeyEventType.KeyUp); + } + + public void InsertText(string text) + { + var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, text); + _view.Input(rawTextEvent); + } + + private void HandleKey(Key key, RawKeyEventType type) + { + var rawKeyEvent = new RawKeyEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, type, key, InputModifiers.None); + _view.Input(rawKeyEvent); + } + + //currently not found a way to get InputModifiers state + //private static InputModifiers GetModifierKeys(object e) + //{ + // var im = InputModifiers.None; + // //if (IsCtrlPressed) rv |= InputModifiers.Control; + // //if (IsShiftPressed) rv |= InputModifiers.Shift; + + // return im; + //} + + private bool NeedsKeyboard(IInputElement element) + { + //may be some other elements + return element is TextBox; + } + + private void TryShowHideKeyboard(IInputElement element, bool value) + { + if (value) + { + _view.BecomeFirstResponder(); + } + else + { + _view.ResignFirstResponder(); + } + } + + public void UpdateKeyboardState(IInputElement element) + { + var focusedElement = element; + bool oldValue = NeedsKeyboard(_lastFocusedElement); + bool newValue = NeedsKeyboard(focusedElement); + + if (newValue != oldValue || newValue) + { + TryShowHideKeyboard(focusedElement, newValue); + } + + _lastFocusedElement = element; + } + + public void ActivateAutoShowKeybord() + { + var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged); + + //just in case we've called more than once the method + kbDevice.PropertyChanged -= KeyboardDevice_PropertyChanged; + kbDevice.PropertyChanged += KeyboardDevice_PropertyChanged; + } + + private void KeyboardDevice_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(KeyboardDevice.FocusedElement)) + { + UpdateKeyboardState(KeyboardDevice.Instance.FocusedElement); + } + } + + public void Dispose() + { + HandleEvents = false; + } + } +} \ No newline at end of file From fcf79c45eba904a966c9abd11ebaaf37bf74ce4d Mon Sep 17 00:00:00 2001 From: donandren Date: Sat, 5 Dec 2015 17:36:17 +0200 Subject: [PATCH 2/2] enhanced emulation of mouse scroll event from touch in iOS --- src/iOS/Perspex.iOS/PerspexView.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 43d941fba3..4adfb98790 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -168,8 +168,14 @@ namespace Perspex.iOS RawMouseEventType.Move, location, InputModifiers.LeftMouseButton)); else { - Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, - _inputRoot, location, location - _touchLastPoint, InputModifiers.LeftMouseButton)); + double x = location.X - _touchLastPoint.X; + double y = location.Y - _touchLastPoint.Y; + double correction = 0.02; + var scale = PerspexLocator.Current.GetService().RenderScalingFactor; + scale = 1; + + Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint)touch.Timestamp, + _inputRoot, location, new Vector(x * correction / scale, y * correction / scale), InputModifiers.LeftMouseButton)); } _touchLastPoint = location; }