Browse Source
* android - remove keyboard size from safe area padding * add software keyboard listener * change keyboard height to keyboard rect * rename height members in software keyboard listener to bounds * fix docs * update api * update toplevel input pane propety name * remove redundant attribute from input pane interface * Add iOS implementation * Fix iOS event * Change iOS easing functions * Update demo * Make StartRect nullable as it's not available on every platform * Windows IInputPane implementation * Implement browser input pane * Minor fixes * Apply suggestions from code review Co-authored-by: workgroupengineering <workgroupengineering@users.noreply.github.com> * Update src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs Co-authored-by: workgroupengineering <workgroupengineering@users.noreply.github.com> * Dispose InputPane just in case * Fix build error, replace ref with in * Fix relative keyboard geometrychange on browser * Fix compile error because of the wrong style --------- Co-authored-by: Max Katz <maxkatz6@outlook.com> Co-authored-by: Jumar Macato <16554748+jmacato@users.noreply.github.com> Co-authored-by: workgroupengineering <workgroupengineering@users.noreply.github.com>pull/13775/head
committed by
GitHub
22 changed files with 511 additions and 94 deletions
@ -0,0 +1,89 @@ |
|||
using System; |
|||
using Avalonia.Animation.Easings; |
|||
using Avalonia.Metadata; |
|||
|
|||
namespace Avalonia.Controls.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Listener for the platform's input pane(eg, software keyboard). Provides access to the input pane height and state.
|
|||
/// </summary>
|
|||
[NotClientImplementable] |
|||
public interface IInputPane |
|||
{ |
|||
/// <summary>
|
|||
/// The current input pane state
|
|||
/// </summary>
|
|||
InputPaneState State { get; } |
|||
|
|||
/// <summary>
|
|||
/// The current input pane bounds.
|
|||
/// </summary>
|
|||
Rect OccludedRect { get; } |
|||
|
|||
/// <summary>
|
|||
/// Occurs when the input pane's state has changed.
|
|||
/// </summary>
|
|||
event EventHandler<InputPaneStateEventArgs>? StateChanged; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The input pane opened state.
|
|||
/// </summary>
|
|||
public enum InputPaneState |
|||
{ |
|||
/// <summary>
|
|||
/// The input pane is either closed, or doesn't form part of the platform insets, i.e. it's floating or is an overlay.
|
|||
/// </summary>
|
|||
Closed, |
|||
|
|||
/// <summary>
|
|||
/// The input pane is open.
|
|||
/// </summary>
|
|||
Open |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Provides state change information about the input pane.
|
|||
/// </summary>
|
|||
public sealed class InputPaneStateEventArgs : EventArgs |
|||
{ |
|||
/// <summary>
|
|||
/// The new state of the input pane
|
|||
/// </summary>
|
|||
public InputPaneState NewState { get; } |
|||
|
|||
/// <summary>
|
|||
/// The initial bounds of the input pane.
|
|||
/// </summary>
|
|||
public Rect? StartRect { get; } |
|||
|
|||
/// <summary>
|
|||
/// The final bounds of the input pane.
|
|||
/// </summary>
|
|||
public Rect EndRect { get; } |
|||
|
|||
/// <summary>
|
|||
/// The duration of the input pane's state change animation.
|
|||
/// </summary>
|
|||
public TimeSpan AnimationDuration { get; } |
|||
|
|||
/// <summary>
|
|||
/// The easing of the input pane's state changed animation.
|
|||
/// </summary>
|
|||
public IEasing? Easing { get; } |
|||
|
|||
public InputPaneStateEventArgs(InputPaneState newState, Rect? startRect, Rect endRect, TimeSpan animationDuration, IEasing? easing) |
|||
{ |
|||
NewState = newState; |
|||
StartRect = startRect; |
|||
EndRect = endRect; |
|||
AnimationDuration = animationDuration; |
|||
Easing = easing; |
|||
} |
|||
|
|||
public InputPaneStateEventArgs(InputPaneState newState, Rect? startRect, Rect endRect) |
|||
: this(newState, startRect, endRect, default, null) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
using Avalonia.Browser.Interop; |
|||
using Avalonia.Controls.Platform; |
|||
|
|||
namespace Avalonia.Browser; |
|||
|
|||
internal class BrowserInputPane : IInputPane |
|||
{ |
|||
public BrowserInputPane(JSObject container) |
|||
{ |
|||
InputHelper.SubscribeKeyboardGeometryChange(container, OnGeometryChange); |
|||
} |
|||
|
|||
public InputPaneState State { get; private set; } |
|||
public Rect OccludedRect { get; private set; } |
|||
public event EventHandler<InputPaneStateEventArgs>? StateChanged; |
|||
|
|||
private bool OnGeometryChange(JSObject args) |
|||
{ |
|||
var oldState = (OccludedRect, State); |
|||
|
|||
OccludedRect = new Rect( |
|||
args.GetPropertyAsDouble("x"), |
|||
args.GetPropertyAsDouble("y"), |
|||
args.GetPropertyAsDouble("width"), |
|||
args.GetPropertyAsDouble("height")); |
|||
State = OccludedRect.Width != 0 ? InputPaneState.Open : InputPaneState.Closed; |
|||
|
|||
if (oldState != (OccludedRect, State)) |
|||
{ |
|||
StateChanged?.Invoke(this, new InputPaneStateEventArgs(State, null, OccludedRect)); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
using System; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.MicroCom; |
|||
using Avalonia.Win32.Interop; |
|||
using Avalonia.Win32.Win32Com; |
|||
|
|||
namespace Avalonia.Win32.Input; |
|||
|
|||
internal unsafe class WindowsInputPane : IInputPane, IDisposable |
|||
{ |
|||
// GUID: D5120AA3-46BA-44C5-822D-CA8092C1FC72
|
|||
private static readonly Guid CLSID_FrameworkInputPane = new(0xD5120AA3, 0x46BA, 0x44C5, 0x82, 0x2D, 0xCA, 0x80, 0x92, 0xC1, 0xFC, 0x72); |
|||
// GUID: 5752238B-24F0-495A-82F1-2FD593056796
|
|||
private static readonly Guid SID_IFrameworkInputPane = new(0x5752238B, 0x24F0, 0x495A, 0x82, 0xF1, 0x2F, 0xD5, 0x93, 0x05, 0x67, 0x96); |
|||
|
|||
private readonly WindowImpl _windowImpl; |
|||
private readonly IFrameworkInputPane _inputPane; |
|||
private readonly uint _cookie; |
|||
|
|||
public WindowsInputPane(WindowImpl windowImpl) |
|||
{ |
|||
_windowImpl = windowImpl; |
|||
_inputPane = UnmanagedMethods.CreateInstance<IFrameworkInputPane>(in CLSID_FrameworkInputPane, in SID_IFrameworkInputPane); |
|||
|
|||
using (var handler = new Handler(this)) |
|||
{ |
|||
uint cookie = 0; |
|||
_inputPane.AdviseWithHWND(windowImpl.Handle.Handle, handler, &cookie); |
|||
_cookie = cookie; |
|||
} |
|||
} |
|||
public InputPaneState State { get; private set; } |
|||
|
|||
public Rect OccludedRect { get; private set; } |
|||
|
|||
public event EventHandler<InputPaneStateEventArgs>? StateChanged; |
|||
|
|||
private void OnStateChanged(bool showing, UnmanagedMethods.RECT? prcInputPaneScreenLocation) |
|||
{ |
|||
var oldState = (OccludedRect, State); |
|||
OccludedRect = prcInputPaneScreenLocation.HasValue |
|||
? ScreenRectToClient(prcInputPaneScreenLocation.Value) |
|||
: default; |
|||
State = showing ? InputPaneState.Open : InputPaneState.Closed; |
|||
|
|||
if (oldState != (OccludedRect, State)) |
|||
{ |
|||
StateChanged?.Invoke(this, new InputPaneStateEventArgs(State, null, OccludedRect)); |
|||
} |
|||
} |
|||
|
|||
private Rect ScreenRectToClient(UnmanagedMethods.RECT screenRect) |
|||
{ |
|||
var position = new PixelPoint(screenRect.left, screenRect.top); |
|||
var size = new PixelSize(screenRect.Width, screenRect.Height); |
|||
return new Rect(_windowImpl.PointToClient(position), size.ToSize(_windowImpl.DesktopScaling)); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_cookie != 0) |
|||
{ |
|||
_inputPane.Unadvise(_cookie); |
|||
} |
|||
|
|||
_inputPane.Dispose(); |
|||
} |
|||
|
|||
private class Handler : CallbackBase, IFrameworkInputPaneHandler |
|||
{ |
|||
private readonly WindowsInputPane _pane; |
|||
|
|||
public Handler(WindowsInputPane pane) => _pane = pane; |
|||
public void Showing(UnmanagedMethods.RECT* rect, int _) => _pane.OnStateChanged(true, *rect); |
|||
public void Hiding(int fEnsureFocusedElementInView) => _pane.OnStateChanged(false, null); |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using Avalonia.Animation.Easings; |
|||
using Avalonia.Controls.Platform; |
|||
using Foundation; |
|||
using UIKit; |
|||
|
|||
#nullable enable |
|||
namespace Avalonia.iOS; |
|||
|
|||
internal sealed class UIKitInputPane : IInputPane |
|||
{ |
|||
public static UIKitInputPane Instance { get; } = new(); |
|||
|
|||
public UIKitInputPane() |
|||
{ |
|||
NSNotificationCenter |
|||
.DefaultCenter |
|||
.AddObserver(UIKeyboard.WillShowNotification, KeyboardUpNotification); |
|||
NSNotificationCenter |
|||
.DefaultCenter |
|||
.AddObserver(UIKeyboard.WillHideNotification, KeyboardDownNotification); |
|||
} |
|||
|
|||
public InputPaneState State { get; private set; } |
|||
public Rect OccludedRect { get; private set; } |
|||
public event EventHandler<InputPaneStateEventArgs>? StateChanged; |
|||
|
|||
private void KeyboardDownNotification(NSNotification obj) => RaiseEventFromNotification(false, obj); |
|||
|
|||
private void KeyboardUpNotification(NSNotification obj) => RaiseEventFromNotification(true, obj); |
|||
|
|||
private void RaiseEventFromNotification(bool isUp, NSNotification notification) |
|||
{ |
|||
State = isUp ? InputPaneState.Open : InputPaneState.Closed; |
|||
|
|||
var startFrame = UIKeyboard.FrameBeginFromNotification(notification); |
|||
var endFrame = UIKeyboard.FrameEndFromNotification(notification); |
|||
var duration = UIKeyboard.AnimationDurationFromNotification(notification); |
|||
var curve = (UIViewAnimationOptions)UIKeyboard.AnimationCurveFromNotification(notification); |
|||
IEasing? easing = |
|||
curve.HasFlag(UIViewAnimationOptions.CurveLinear) ? new LinearEasing() |
|||
: curve.HasFlag(UIViewAnimationOptions.CurveEaseIn) ? new SineEaseIn() |
|||
: curve.HasFlag(UIViewAnimationOptions.CurveEaseOut) ? new SineEaseOut() |
|||
: curve.HasFlag(UIViewAnimationOptions.CurveEaseInOut) ? new SineEaseInOut() |
|||
: null; |
|||
|
|||
var startRect = new Rect(startFrame.X, startFrame.Y, startFrame.Width, startFrame.Height); |
|||
OccludedRect = new Rect(endFrame.X, endFrame.Y, endFrame.Width, endFrame.Height); |
|||
|
|||
StateChanged?.Invoke(this, new InputPaneStateEventArgs( |
|||
State, startRect, OccludedRect, TimeSpan.FromSeconds(duration), easing)); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue