committed by
GitHub
91 changed files with 1876 additions and 1160 deletions
@ -0,0 +1,46 @@ |
|||
//
|
|||
// AvnTextInputMethod.h
|
|||
// Avalonia.Native.OSX
|
|||
//
|
|||
// Created by Benedikt Stebner on 22.11.22.
|
|||
// Copyright © 2022 Avalonia. All rights reserved.
|
|||
//
|
|||
|
|||
#ifndef AvnTextInputMethod_h |
|||
#define AvnTextInputMethod_h |
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
#include "com.h" |
|||
#include "comimpl.h" |
|||
#include "avalonia-native.h" |
|||
#import "AvnTextInputMethodDelegate.h" |
|||
|
|||
class AvnTextInputMethod: public virtual ComObject, public virtual IAvnTextInputMethod{ |
|||
private: |
|||
id<AvnTextInputMethodDelegate> _inputMethodDelegate; |
|||
public: |
|||
FORWARD_IUNKNOWN() |
|||
|
|||
BEGIN_INTERFACE_MAP() |
|||
INTERFACE_MAP_ENTRY(IAvnTextInputMethod, IID_IAvnTextInputMethod) |
|||
END_INTERFACE_MAP() |
|||
|
|||
virtual ~AvnTextInputMethod(); |
|||
|
|||
AvnTextInputMethod(id<AvnTextInputMethodDelegate> inputMethodDelegate); |
|||
|
|||
bool IsActive (); |
|||
|
|||
HRESULT SetClient (IAvnTextInputMethodClient* client) override; |
|||
|
|||
virtual void Reset () override; |
|||
|
|||
virtual void SetCursorRect (AvnRect rect) override; |
|||
|
|||
virtual void SetSurroundingText (char* text, int anchorOffset, int cursorOffset) override; |
|||
|
|||
public: |
|||
ComPtr<IAvnTextInputMethodClient> Client; |
|||
}; |
|||
#endif /* AvnTextInputMethod_h */ |
|||
@ -0,0 +1,41 @@ |
|||
// |
|||
// AvnTextInputMethod.mm |
|||
// Avalonia.Native.OSX |
|||
// |
|||
// Created by Benedikt Stebner on 23.11.22. |
|||
// Copyright © 2022 Avalonia. All rights reserved. |
|||
// |
|||
|
|||
#include "AvnTextInputMethod.h" |
|||
|
|||
AvnTextInputMethod::~AvnTextInputMethod() { |
|||
Client = nullptr; |
|||
} |
|||
|
|||
AvnTextInputMethod::AvnTextInputMethod(id<AvnTextInputMethodDelegate> inputMethodDelegate) { |
|||
_inputMethodDelegate = inputMethodDelegate; |
|||
} |
|||
|
|||
bool AvnTextInputMethod::IsActive() { |
|||
return Client != nullptr; |
|||
} |
|||
|
|||
HRESULT AvnTextInputMethod::SetClient(IAvnTextInputMethodClient *client) { |
|||
START_COM_CALL; |
|||
|
|||
Client = client; |
|||
|
|||
return S_OK; |
|||
} |
|||
|
|||
void AvnTextInputMethod::Reset() { |
|||
} |
|||
|
|||
void AvnTextInputMethod::SetSurroundingText(char* text, int anchorOffset, int cursorOffset) { |
|||
[_inputMethodDelegate setText:[NSString stringWithUTF8String:text]]; |
|||
[_inputMethodDelegate setSelection: anchorOffset : cursorOffset]; |
|||
} |
|||
|
|||
void AvnTextInputMethod::SetCursorRect(AvnRect rect) { |
|||
[_inputMethodDelegate setCursorRect: rect]; |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
//
|
|||
// AvnTextInputMethodHost.h
|
|||
// Avalonia.Native.OSX
|
|||
//
|
|||
// Created by Benedikt Stebner on 24.11.22.
|
|||
// Copyright © 2022 Avalonia. All rights reserved.
|
|||
//
|
|||
|
|||
#ifndef AvnTextInputMethodHost_h |
|||
#define AvnTextInputMethodHost_h |
|||
|
|||
@protocol AvnTextInputMethodDelegate |
|||
@required |
|||
-(void) setText:(NSString* _Nonnull) text; |
|||
-(void) setCursorRect:(AvnRect) cursorRect; |
|||
-(void) setSelection: (int) start : (int) end; |
|||
|
|||
@end |
|||
|
|||
#endif /* AvnTextInputMethodHost_h */ |
|||
@ -1,77 +0,0 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
public enum FlyoutPlacementMode |
|||
{ |
|||
/// <summary>
|
|||
/// Preferred location is above the target element
|
|||
/// </summary>
|
|||
Top = 0, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is below the target element
|
|||
/// </summary>
|
|||
Bottom = 1, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is to the left of the target element
|
|||
/// </summary>
|
|||
Left = 2, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is to the right of the target element
|
|||
/// </summary>
|
|||
Right = 3, |
|||
|
|||
//TODO
|
|||
// <summary>
|
|||
// Preferred location is centered on the screen
|
|||
// </summary>
|
|||
//Full = 4,
|
|||
|
|||
/// <summary>
|
|||
/// Preferred location is above the target element, with the left edge of the flyout
|
|||
/// aligned with the left edge of the target element
|
|||
/// </summary>
|
|||
TopEdgeAlignedLeft = 5, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is above the target element, with the right edge of flyout aligned with right edge of the target element.
|
|||
/// </summary>
|
|||
TopEdgeAlignedRight = 6, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is below the target element, with the left edge of flyout aligned with left edge of the target element.
|
|||
/// </summary>
|
|||
BottomEdgeAlignedLeft = 7, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is below the target element, with the right edge of flyout aligned with right edge of the target element.
|
|||
/// </summary>
|
|||
BottomEdgeAlignedRight = 8, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is to the left of the target element, with the top edge of flyout aligned with top edge of the target element.
|
|||
/// </summary>
|
|||
LeftEdgeAlignedTop = 9, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is to the left of the target element, with the bottom edge of flyout aligned with bottom edge of the target element.
|
|||
/// </summary>
|
|||
LeftEdgeAlignedBottom = 10, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is to the right of the target element, with the top edge of flyout aligned with top edge of the target element.
|
|||
/// </summary>
|
|||
RightEdgeAlignedTop = 11, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is to the right of the target element, with the bottom edge of flyout aligned with bottom edge of the target element.
|
|||
/// </summary>
|
|||
RightEdgeAlignedBottom = 12, |
|||
|
|||
/// <summary>
|
|||
/// Preferred location is determined automatically.
|
|||
/// </summary>
|
|||
Auto = 13 |
|||
} |
|||
} |
|||
@ -0,0 +1,512 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Diagnostics; |
|||
using Avalonia.Controls.Primitives.PopupPositioning; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Platform; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Logging; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider |
|||
{ |
|||
/// <inheritdoc cref="Popup.PlacementModeProperty"/>
|
|||
public static readonly StyledProperty<PlacementMode> PlacementProperty = |
|||
Popup.PlacementModeProperty.AddOwner<PopupFlyoutBase>(); |
|||
|
|||
/// <inheritdoc cref="Popup.HorizontalOffsetProperty"/>
|
|||
public static readonly StyledProperty<double> HorizontalOffsetProperty = |
|||
Popup.HorizontalOffsetProperty.AddOwner<PopupFlyoutBase>(); |
|||
|
|||
/// <inheritdoc cref="Popup.VerticalOffsetProperty"/>
|
|||
public static readonly StyledProperty<double> VerticalOffsetProperty = |
|||
Popup.VerticalOffsetProperty.AddOwner<PopupFlyoutBase>(); |
|||
|
|||
/// <inheritdoc cref="Popup.PlacementAnchorProperty"/>
|
|||
public static readonly StyledProperty<PopupAnchor> PlacementAnchorProperty = |
|||
Popup.PlacementAnchorProperty.AddOwner<PopupFlyoutBase>(); |
|||
|
|||
/// <inheritdoc cref="Popup.PlacementAnchorProperty"/>
|
|||
public static readonly StyledProperty<PopupGravity> PlacementGravityProperty = |
|||
Popup.PlacementGravityProperty.AddOwner<PopupFlyoutBase>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ShowMode"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<FlyoutShowMode> ShowModeProperty = |
|||
AvaloniaProperty.Register<PopupFlyoutBase, FlyoutShowMode>(nameof(ShowMode)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="OverlayInputPassThroughElement"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IInputElement?> OverlayInputPassThroughElementProperty = |
|||
Popup.OverlayInputPassThroughElementProperty.AddOwner<FlyoutBase>(); |
|||
|
|||
private readonly Lazy<Popup> _popupLazy; |
|||
private Rect? _enlargedPopupRect; |
|||
private PixelRect? _enlargePopupRectScreenPixelRect; |
|||
private IDisposable? _transientDisposable; |
|||
private Action<IPopupHost?>? _popupHostChangedHandler; |
|||
|
|||
static PopupFlyoutBase() |
|||
{ |
|||
Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged); |
|||
} |
|||
|
|||
public PopupFlyoutBase() |
|||
{ |
|||
_popupLazy = new Lazy<Popup>(() => CreatePopup()); |
|||
} |
|||
|
|||
protected Popup Popup => _popupLazy.Value; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the desired placement.
|
|||
/// </summary>
|
|||
public PlacementMode Placement |
|||
{ |
|||
get => GetValue(PlacementProperty); |
|||
set => SetValue(PlacementProperty, value); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Popup.PlacementGravity"/>
|
|||
public PopupGravity PlacementGravity |
|||
{ |
|||
get => GetValue(PlacementGravityProperty); |
|||
set => SetValue(PlacementGravityProperty, value); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Popup.PlacementAnchor"/>
|
|||
public PopupAnchor PlacementAnchor |
|||
{ |
|||
get => GetValue(PlacementAnchorProperty); |
|||
set => SetValue(PlacementAnchorProperty, value); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Popup.HorizontalOffset"/>
|
|||
public double HorizontalOffset |
|||
{ |
|||
get => GetValue(HorizontalOffsetProperty); |
|||
set => SetValue(HorizontalOffsetProperty, value); |
|||
} |
|||
|
|||
/// <inheritdoc cref="Popup.VerticalOffset"/>
|
|||
public double VerticalOffset |
|||
{ |
|||
get => GetValue(VerticalOffsetProperty); |
|||
set => SetValue(VerticalOffsetProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the desired ShowMode
|
|||
/// </summary>
|
|||
public FlyoutShowMode ShowMode |
|||
{ |
|||
get => GetValue(ShowModeProperty); |
|||
set => SetValue(ShowModeProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets an element that should receive pointer input events even when underneath
|
|||
/// the flyout's overlay.
|
|||
/// </summary>
|
|||
public IInputElement? OverlayInputPassThroughElement |
|||
{ |
|||
get => GetValue(OverlayInputPassThroughElementProperty); |
|||
set => SetValue(OverlayInputPassThroughElementProperty, value); |
|||
} |
|||
|
|||
IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host; |
|||
|
|||
event Action<IPopupHost?>? IPopupHostProvider.PopupHostChanged |
|||
{ |
|||
add => _popupHostChangedHandler += value; |
|||
remove => _popupHostChangedHandler -= value; |
|||
} |
|||
|
|||
public event EventHandler<CancelEventArgs>? Closing; |
|||
public event EventHandler? Opening; |
|||
|
|||
/// <summary>
|
|||
/// Shows the Flyout at the given Control
|
|||
/// </summary>
|
|||
/// <param name="placementTarget">The control to show the Flyout at</param>
|
|||
public sealed override void ShowAt(Control placementTarget) |
|||
{ |
|||
ShowAtCore(placementTarget); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Shows the Flyout for the given control at the current pointer location, as in a ContextFlyout
|
|||
/// </summary>
|
|||
/// <param name="placementTarget">The target control</param>
|
|||
/// <param name="showAtPointer">True to show at pointer</param>
|
|||
public void ShowAt(Control placementTarget, bool showAtPointer) |
|||
{ |
|||
ShowAtCore(placementTarget, showAtPointer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Hides the Flyout
|
|||
/// </summary>
|
|||
public sealed override void Hide() |
|||
{ |
|||
HideCore(); |
|||
} |
|||
|
|||
/// <returns>True, if action was handled</returns>
|
|||
protected virtual bool HideCore(bool canCancel = true) |
|||
{ |
|||
if (!IsOpen) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (canCancel) |
|||
{ |
|||
if (CancelClosing()) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
IsOpen = false; |
|||
Popup.IsOpen = false; |
|||
|
|||
((ISetLogicalParent)Popup).SetParent(null); |
|||
|
|||
// Ensure this isn't active
|
|||
_transientDisposable?.Dispose(); |
|||
_transientDisposable = null; |
|||
_enlargedPopupRect = null; |
|||
_enlargePopupRectScreenPixelRect = null; |
|||
|
|||
if (Target != null) |
|||
{ |
|||
Target.DetachedFromVisualTree -= PlacementTarget_DetachedFromVisualTree; |
|||
Target.KeyUp -= OnPlacementTargetOrPopupKeyUp; |
|||
} |
|||
|
|||
OnClosed(); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <returns>True, if action was handled</returns>
|
|||
protected virtual bool ShowAtCore(Control placementTarget, bool showAtPointer = false) |
|||
{ |
|||
if (placementTarget == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(placementTarget)); |
|||
} |
|||
|
|||
if (IsOpen) |
|||
{ |
|||
if (placementTarget == Target) |
|||
{ |
|||
return false; |
|||
} |
|||
else // Close before opening a new one
|
|||
{ |
|||
_ = HideCore(false); |
|||
} |
|||
} |
|||
|
|||
if (Popup.Parent != null && Popup.Parent != placementTarget) |
|||
{ |
|||
((ISetLogicalParent)Popup).SetParent(null); |
|||
} |
|||
|
|||
if (Popup.Parent == null || Popup.PlacementTarget != placementTarget) |
|||
{ |
|||
Popup.PlacementTarget = Target = placementTarget; |
|||
((ISetLogicalParent)Popup).SetParent(placementTarget); |
|||
Popup.TemplatedParent = placementTarget.TemplatedParent; |
|||
} |
|||
|
|||
if (Popup.Child == null) |
|||
{ |
|||
Popup.Child = CreatePresenter(); |
|||
} |
|||
|
|||
Popup.OverlayInputPassThroughElement = OverlayInputPassThroughElement; |
|||
|
|||
if (CancelOpening()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
PositionPopup(showAtPointer); |
|||
IsOpen = Popup.IsOpen = true; |
|||
OnOpened(); |
|||
|
|||
placementTarget.DetachedFromVisualTree += PlacementTarget_DetachedFromVisualTree; |
|||
placementTarget.KeyUp += OnPlacementTargetOrPopupKeyUp; |
|||
|
|||
if (ShowMode == FlyoutShowMode.Standard) |
|||
{ |
|||
// Try and focus content inside Flyout
|
|||
if (Popup.Child.Focusable) |
|||
{ |
|||
FocusManager.Instance?.Focus(Popup.Child); |
|||
} |
|||
else |
|||
{ |
|||
var nextFocus = KeyboardNavigationHandler.GetNext(Popup.Child, NavigationDirection.Next); |
|||
if (nextFocus != null) |
|||
{ |
|||
FocusManager.Instance?.Focus(nextFocus); |
|||
} |
|||
} |
|||
} |
|||
else if (ShowMode == FlyoutShowMode.TransientWithDismissOnPointerMoveAway) |
|||
{ |
|||
_transientDisposable = InputManager.Instance?.Process.Subscribe(HandleTransientDismiss); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private void PlacementTarget_DetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
_ = HideCore(false); |
|||
} |
|||
|
|||
private void HandleTransientDismiss(RawInputEventArgs args) |
|||
{ |
|||
if (args is RawPointerEventArgs pArgs && pArgs.Type == RawPointerEventType.Move) |
|||
{ |
|||
// In ShowMode = TransientWithDismissOnPointerMoveAway, the Flyout is kept
|
|||
// shown as long as the pointer is within a certain px distance from the
|
|||
// flyout itself. I'm not sure what WinUI uses, but I'm defaulting to
|
|||
// 100px, which seems about right
|
|||
// enlargedPopupRect is the Flyout bounds enlarged 100px
|
|||
// For windowed popups, enlargedPopupRect is in screen coordinates,
|
|||
// for overlay popups, its in OverlayLayer coordinates
|
|||
|
|||
if (_enlargedPopupRect == null && _enlargePopupRectScreenPixelRect == null) |
|||
{ |
|||
// Only do this once when the Flyout opens & cache the result
|
|||
if (Popup?.Host is PopupRoot root) |
|||
{ |
|||
// Get the popup root bounds and convert to screen coordinates
|
|||
|
|||
var tmp = root.Bounds.Inflate(100); |
|||
_enlargePopupRectScreenPixelRect = new PixelRect(root.PointToScreen(tmp.TopLeft), root.PointToScreen(tmp.BottomRight)); |
|||
} |
|||
else if (Popup?.Host is OverlayPopupHost host) |
|||
{ |
|||
// Overlay popups are in OverlayLayer coordinates, just use that
|
|||
_enlargedPopupRect = host.Bounds.Inflate(100); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Popup?.Host is PopupRoot && pArgs.Root is Visual eventRoot) |
|||
{ |
|||
// As long as the pointer stays within the enlargedPopupRect
|
|||
// the flyout stays open. If it leaves, close it
|
|||
// Despite working in screen coordinates, leaving the TopLevel
|
|||
// window will not close this (as pointer events stop), which
|
|||
// does match UWP
|
|||
var pt = eventRoot.PointToScreen(pArgs.Position); |
|||
if (!_enlargePopupRectScreenPixelRect?.Contains(pt) ?? false) |
|||
{ |
|||
HideCore(false); |
|||
} |
|||
} |
|||
else if (Popup?.Host is OverlayPopupHost) |
|||
{ |
|||
// Same as above here, but just different coordinate space
|
|||
// so we don't need to translate
|
|||
if (!_enlargedPopupRect?.Contains(pArgs.Position) ?? false) |
|||
{ |
|||
HideCore(false); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected virtual void OnOpening(CancelEventArgs args) |
|||
{ |
|||
Opening?.Invoke(this, args); |
|||
} |
|||
|
|||
protected virtual void OnClosing(CancelEventArgs args) |
|||
{ |
|||
Closing?.Invoke(this, args); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Used to create the content the Flyout displays
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
protected abstract Control CreatePresenter(); |
|||
|
|||
private Popup CreatePopup() |
|||
{ |
|||
var popup = new Popup |
|||
{ |
|||
WindowManagerAddShadowHint = false, |
|||
IsLightDismissEnabled = true, |
|||
//Note: This is required to prevent Button.Flyout from opening the flyout again after dismiss.
|
|||
OverlayDismissEventPassThrough = false |
|||
}; |
|||
|
|||
popup.Opened += OnPopupOpened; |
|||
popup.Closed += OnPopupClosed; |
|||
popup.Closing += OnPopupClosing; |
|||
popup.KeyUp += OnPlacementTargetOrPopupKeyUp; |
|||
return popup; |
|||
} |
|||
|
|||
private void OnPopupOpened(object? sender, EventArgs e) |
|||
{ |
|||
IsOpen = true; |
|||
|
|||
_popupHostChangedHandler?.Invoke(Popup.Host); |
|||
} |
|||
|
|||
private void OnPopupClosing(object? sender, CancelEventArgs e) |
|||
{ |
|||
if (IsOpen) |
|||
{ |
|||
e.Cancel = CancelClosing(); |
|||
} |
|||
} |
|||
|
|||
private void OnPopupClosed(object? sender, EventArgs e) |
|||
{ |
|||
HideCore(false); |
|||
|
|||
_popupHostChangedHandler?.Invoke(null); |
|||
} |
|||
|
|||
// This method is handling both popup logical tree and target logical tree.
|
|||
private void OnPlacementTargetOrPopupKeyUp(object? sender, KeyEventArgs e) |
|||
{ |
|||
if (!e.Handled |
|||
&& IsOpen |
|||
&& Target?.ContextFlyout == this) |
|||
{ |
|||
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>(); |
|||
|
|||
if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true) |
|||
{ |
|||
e.Handled = HideCore(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void PositionPopup(bool showAtPointer) |
|||
{ |
|||
Size sz; |
|||
// Popup.Child can't be null here, it was set in ShowAtCore.
|
|||
if (Popup.Child!.DesiredSize.IsDefault) |
|||
{ |
|||
// Popup may not have been shown yet. Measure content
|
|||
sz = LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness()); |
|||
} |
|||
else |
|||
{ |
|||
sz = Popup.Child.DesiredSize; |
|||
} |
|||
|
|||
Popup.VerticalOffset = VerticalOffset; |
|||
Popup.HorizontalOffset = HorizontalOffset; |
|||
Popup.PlacementAnchor = PlacementAnchor; |
|||
Popup.PlacementGravity = PlacementGravity; |
|||
if (showAtPointer) |
|||
{ |
|||
Popup.PlacementMode = PlacementMode.Pointer; |
|||
} |
|||
else |
|||
{ |
|||
Popup.PlacementMode = Placement; |
|||
Popup.PlacementConstraintAdjustment = |
|||
PopupPositioning.PopupPositionerConstraintAdjustment.SlideX | |
|||
PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; |
|||
} |
|||
} |
|||
|
|||
private static void OnContextFlyoutPropertyChanged(AvaloniaPropertyChangedEventArgs args) |
|||
{ |
|||
if (args.Sender is Control c) |
|||
{ |
|||
if (args.OldValue is FlyoutBase) |
|||
{ |
|||
c.ContextRequested -= OnControlContextRequested; |
|||
} |
|||
if (args.NewValue is FlyoutBase) |
|||
{ |
|||
c.ContextRequested += OnControlContextRequested; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void OnControlContextRequested(object? sender, ContextRequestedEventArgs e) |
|||
{ |
|||
if (!e.Handled |
|||
&& sender is Control control |
|||
&& control.ContextFlyout is { } flyout) |
|||
{ |
|||
if (control.ContextMenu != null) |
|||
{ |
|||
Logger.TryGet(LogEventLevel.Verbose, "FlyoutBase")?.Log(control, "ContextMenu and ContextFlyout are both set, defaulting to ContextMenu"); |
|||
return; |
|||
} |
|||
|
|||
if (flyout is PopupFlyoutBase popupFlyout) |
|||
{ |
|||
// We do not support absolute popup positioning yet, so we ignore "point" at this moment.
|
|||
var triggeredByPointerInput = e.TryGetPosition(null, out _); |
|||
e.Handled = popupFlyout.ShowAtCore(control, triggeredByPointerInput); |
|||
} |
|||
else |
|||
{ |
|||
flyout.ShowAt(control); |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private bool CancelClosing() |
|||
{ |
|||
var eventArgs = new CancelEventArgs(); |
|||
OnClosing(eventArgs); |
|||
return eventArgs.Cancel; |
|||
} |
|||
|
|||
private bool CancelOpening() |
|||
{ |
|||
var eventArgs = new CancelEventArgs(); |
|||
OnOpening(eventArgs); |
|||
return eventArgs.Cancel; |
|||
} |
|||
|
|||
internal static void SetPresenterClasses(Control? presenter, Classes classes) |
|||
{ |
|||
if(presenter is null) |
|||
{ |
|||
return; |
|||
} |
|||
//Remove any classes no longer in use, ignoring pseudo classes
|
|||
for (int i = presenter.Classes.Count - 1; i >= 0; i--) |
|||
{ |
|||
if (!classes.Contains(presenter.Classes[i]) && |
|||
!presenter.Classes[i].Contains(':')) |
|||
{ |
|||
presenter.Classes.RemoveAt(i); |
|||
} |
|||
} |
|||
|
|||
//Add new classes
|
|||
presenter.Classes.AddRange(classes); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
using System; |
|||
using Avalonia.Input.TextInput; |
|||
using Avalonia.Native.Interop; |
|||
|
|||
namespace Avalonia.Native |
|||
{ |
|||
internal class AvaloniaNativeTextInputMethod : ITextInputMethodImpl, IDisposable |
|||
{ |
|||
private ITextInputMethodClient _client; |
|||
private IAvnTextInputMethodClient _nativeClient; |
|||
private readonly IAvnTextInputMethod _inputMethod; |
|||
|
|||
public AvaloniaNativeTextInputMethod(IAvnWindowBase nativeWindow) |
|||
{ |
|||
_inputMethod = nativeWindow.InputMethod; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_inputMethod.Dispose(); |
|||
_nativeClient?.Dispose(); |
|||
} |
|||
|
|||
public void Reset() |
|||
{ |
|||
_inputMethod.Reset(); |
|||
} |
|||
|
|||
public void SetClient(ITextInputMethodClient client) |
|||
{ |
|||
if (_client is { SupportsSurroundingText: true }) |
|||
{ |
|||
_client.SurroundingTextChanged -= OnSurroundingTextChanged; |
|||
_client.CursorRectangleChanged -= OnCursorRectangleChanged; |
|||
|
|||
_nativeClient?.Dispose(); |
|||
} |
|||
|
|||
_nativeClient = null; |
|||
_client = client; |
|||
|
|||
if (client != null) |
|||
{ |
|||
_nativeClient = new AvnTextInputMethodClient(client); |
|||
|
|||
OnSurroundingTextChanged(this, EventArgs.Empty); |
|||
OnCursorRectangleChanged(this, EventArgs.Empty); |
|||
|
|||
_client.SurroundingTextChanged += OnSurroundingTextChanged; |
|||
_client.CursorRectangleChanged += OnCursorRectangleChanged; |
|||
} |
|||
|
|||
_inputMethod.SetClient(_nativeClient); |
|||
} |
|||
|
|||
private void OnCursorRectangleChanged(object sender, EventArgs e) |
|||
{ |
|||
if (_client == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_inputMethod.SetCursorRect(_client.CursorRectangle.ToAvnRect()); |
|||
} |
|||
|
|||
private void OnSurroundingTextChanged(object sender, EventArgs e) |
|||
{ |
|||
if (_client == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var surroundingText = _client.SurroundingText; |
|||
|
|||
_inputMethod.SetSurroundingText( |
|||
surroundingText.Text, |
|||
surroundingText.AnchorOffset, |
|||
surroundingText.CursorOffset |
|||
); |
|||
} |
|||
|
|||
public void SetCursorRect(Rect rect) |
|||
{ |
|||
_inputMethod.SetCursorRect(rect.ToAvnRect()); |
|||
} |
|||
|
|||
public void SetOptions(TextInputOptions options) |
|||
{ |
|||
|
|||
} |
|||
|
|||
private class AvnTextInputMethodClient : NativeCallbackBase, IAvnTextInputMethodClient |
|||
{ |
|||
private readonly ITextInputMethodClient _client; |
|||
|
|||
public AvnTextInputMethodClient(ITextInputMethodClient client) |
|||
{ |
|||
_client = client; |
|||
} |
|||
|
|||
public void SetPreeditText(string preeditText) |
|||
{ |
|||
if (_client.SupportsPreedit) |
|||
{ |
|||
_client.SetPreeditText(preeditText); |
|||
} |
|||
} |
|||
|
|||
public void SelectInSurroundingText(int start, int end) |
|||
{ |
|||
if (_client.SupportsSurroundingText) |
|||
{ |
|||
_client.SelectInSurroundingText(start, end); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
{ |
|||
"profiles": { |
|||
"Avalonia.Benchmarks": { |
|||
"commandName": "Project" |
|||
}, |
|||
"Avalonia.Benchmarks (in-process)": { |
|||
"commandName": "Project", |
|||
"commandLineArgs": "--inprocess" |
|||
}, |
|||
"Avalonia.Benchmarks (debug)": { |
|||
"commandName": "Project", |
|||
"commandLineArgs": "--debug" |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue