A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

557 lines
17 KiB

#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Automation.Peers;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Native.Interop;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia.Native;
internal class MacOSTopLevelHandle : IPlatformHandle, IMacOSTopLevelPlatformHandle
{
internal MacOSTopLevelHandle(IAvnTopLevel native)
{
Native = native;
HandleDescriptor = "NSView";
Handle = NSView;
}
internal MacOSTopLevelHandle(IAvnWindowBase native)
{
Native = native;
HandleDescriptor = "NSWindow";
Handle = NSWindow;
}
internal IAvnTopLevel Native { get; }
public IntPtr Handle { get; }
public string HandleDescriptor { get; }
public IntPtr NSView => Native.ObtainNSViewHandle();
public IntPtr GetNSViewRetained()
{
return Native.ObtainNSViewHandleRetained();
}
public IntPtr NSWindow => (Native as IAvnWindowBase)?.ObtainNSWindowHandle() ?? IntPtr.Zero;
public IntPtr GetNSWindowRetained()
{
return (Native as IAvnWindowBase)?.ObtainNSWindowHandleRetained() ?? IntPtr.Zero;
}
}
internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface
{
protected IInputRoot? _inputRoot;
private NativeControlHostImpl? _nativeControlHost;
private IStorageProvider? _storageProvider;
private PlatformBehaviorInhibition? _platformBehaviorInhibition;
private readonly MouseDevice? _mouse;
private readonly IKeyboardDevice? _keyboard;
private readonly ICursorFactory? _cursorFactory;
protected readonly IAvaloniaNativeFactory Factory;
private Size _savedLogicalSize;
private double _savedScaling;
private WindowTransparencyLevel _transparencyLevel = WindowTransparencyLevel.None;
protected MacOSTopLevelHandle? _handle;
private object _syncRoot = new object();
private IEnumerable<object>? _surfaces;
public TopLevelImpl(IAvaloniaNativeFactory factory)
{
Factory = factory;
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_mouse = new MouseDevice();
_cursorFactory = AvaloniaLocator.Current.GetService<ICursorFactory>();
}
internal virtual void Init(MacOSTopLevelHandle handle)
{
_handle = handle;
_savedLogicalSize = ClientSize;
_savedScaling = RenderScaling;
_nativeControlHost = new NativeControlHostImpl(Native!.CreateNativeControlHost());
_storageProvider = new SystemDialogs(this, Factory.CreateSystemDialogs());
_platformBehaviorInhibition = new PlatformBehaviorInhibition(Factory.CreatePlatformBehaviorInhibition());
_surfaces = new object[] { new GlPlatformSurface(Native), new MetalPlatformSurface(Native), this };
InputMethod = new AvaloniaNativeTextInputMethod(Native);
}
public double DesktopScaling => 1;
public IAvnTopLevel? Native => _handle?.Native;
public IPlatformHandle? Handle => _handle;
public AvaloniaNativeTextInputMethod? InputMethod { get; private set; }
public Size ClientSize
{
get
{
if (Native == null)
{
return default;
}
var s = Native.ClientSize;
return new Size(s.Width, s.Height);
}
}
public double RenderScaling => Native?.Scaling ?? 1;
public IEnumerable<object> Surfaces => _surfaces ?? Array.Empty<object>();
public Action<RawInputEventArgs>? Input { get; set; }
public Action<Rect>? Paint { get; set; }
public Action<Size, WindowResizeReason>? Resized { get; set; }
public Action<double>? ScalingChanged { get; set; }
public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
public Compositor Compositor => AvaloniaNativePlatform.Compositor;
public Action? Closed { get; set; }
public Action? LostFocus { get; set; }
public WindowTransparencyLevel TransparencyLevel
{
get => _transparencyLevel;
private set
{
if (_transparencyLevel != value)
{
_transparencyLevel = value;
TransparencyLevelChanged?.Invoke(value);
}
}
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0);
public virtual void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
//noop
}
public IMouseDevice? MouseDevice => _mouse;
public INativeControlHostImpl? NativeControlHost => _nativeControlHost;
public AutomationPeer? GetAutomationPeer()
{
return _inputRoot is Control c ? ControlAutomationPeer.CreatePeerForElement(c) : null;
}
public bool RawTextInputEvent(ulong timeStamp, string text)
{
if (_inputRoot is null)
return false;
if (_keyboard is null)
{
return false;
}
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
var args = new RawTextInputEventArgs(_keyboard, timeStamp, _inputRoot, text);
Input?.Invoke(args);
return args.Handled;
}
public bool RawKeyEvent(
AvnRawKeyEventType type,
ulong timeStamp,
AvnInputModifiers modifiers,
AvnKey key,
AvnPhysicalKey physicalKey,
string keySymbol)
{
if (_inputRoot is null)
return false;
if (_keyboard is null)
{
return false;
}
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
var args = new RawKeyEventArgs(
_keyboard,
timeStamp,
_inputRoot,
(RawKeyEventType)type,
(Key)key,
(RawInputModifiers)modifiers,
(PhysicalKey)physicalKey,
keySymbol);
Input?.Invoke(args);
return args.Handled;
}
public void RawMouseEvent(AvnRawMouseEventType type, ulong timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
{
if (_inputRoot is null)
return;
if (_mouse is null)
{
return;
}
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
switch (type)
{
case AvnRawMouseEventType.Wheel:
Input?.Invoke(new RawMouseWheelEventArgs(_mouse, timeStamp, _inputRoot,
point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (RawInputModifiers)modifiers));
break;
case AvnRawMouseEventType.Magnify:
Input?.Invoke(new RawPointerGestureEventArgs(_mouse, timeStamp, _inputRoot, RawPointerEventType.Magnify,
point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (RawInputModifiers)modifiers));
break;
case AvnRawMouseEventType.Rotate:
Input?.Invoke(new RawPointerGestureEventArgs(_mouse, timeStamp, _inputRoot, RawPointerEventType.Rotate,
point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (RawInputModifiers)modifiers));
break;
case AvnRawMouseEventType.Swipe:
Input?.Invoke(new RawPointerGestureEventArgs(_mouse, timeStamp, _inputRoot, RawPointerEventType.Swipe,
point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (RawInputModifiers)modifiers));
break;
default:
var e = new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type,
point.ToAvaloniaPoint(), (RawInputModifiers)modifiers);
if (!ChromeHitTest(e))
{
Input?.Invoke(e);
}
break;
}
}
public void Invalidate()
{
Native?.Invalidate();
}
public void SetInputRoot(IInputRoot inputRoot)
{
_inputRoot = inputRoot;
}
public Point PointToClient(PixelPoint point)
{
return Native?.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint() ?? default;
}
public PixelPoint PointToScreen(Point point)
{
return Native?.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint() ?? default;
}
public void SetCursor(ICursorImpl? cursor)
{
if (Native == null)
{
return;
}
var newCursor = cursor as AvaloniaNativeCursor;
newCursor ??= (_cursorFactory?.GetCursor(StandardCursorType.Arrow) as AvaloniaNativeCursor);
Native.SetCursor(newCursor?.Cursor);
}
public virtual IPopupImpl CreatePopup()
{
return new PopupImpl(Factory, this);
}
public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels)
{
foreach (var level in transparencyLevels)
{
AvnWindowTransparencyMode? mode = null;
if (level == WindowTransparencyLevel.None)
mode = AvnWindowTransparencyMode.Opaque;
if (level == WindowTransparencyLevel.Transparent)
mode = AvnWindowTransparencyMode.Transparent;
else if (level == WindowTransparencyLevel.AcrylicBlur)
mode = AvnWindowTransparencyMode.Blur;
if (mode.HasValue && level != TransparencyLevel)
{
Native?.SetTransparencyMode(mode.Value);
TransparencyLevel = level;
return;
}
}
// If we get here, we didn't find a supported level. Use the default of None.
if (TransparencyLevel != WindowTransparencyLevel.None)
{
Native?.SetTransparencyMode(AvnWindowTransparencyMode.Opaque);
TransparencyLevel = WindowTransparencyLevel.None;
}
}
public virtual object? TryGetFeature(Type featureType)
{
if (featureType == typeof(ITextInputMethodImpl))
{
return InputMethod;
}
if (featureType == typeof(INativeControlHostImpl))
{
return _nativeControlHost;
}
if (featureType == typeof(IStorageProvider))
{
return _storageProvider;
}
if (featureType == typeof(IPlatformBehaviorInhibition))
{
return _platformBehaviorInhibition;
}
if (featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
if (featureType == typeof(IScreenImpl))
{
return AvaloniaLocator.Current.GetRequiredService<IScreenImpl>();
}
if (featureType == typeof(ILauncher))
{
return new BclLauncher();
}
return null;
}
public virtual void Dispose()
{
Native?.Dispose();
_handle = null;
_nativeControlHost?.Dispose();
_nativeControlHost = null;
_mouse?.Dispose();
}
protected virtual bool ChromeHitTest(RawPointerEventArgs e)
{
return false;
}
IFramebufferRenderTarget IFramebufferPlatformSurface.CreateFramebufferRenderTarget()
{
if (!Dispatcher.UIThread.CheckAccess())
throw new RenderTargetNotReadyException();
var nativeRenderTarget = Native?.CreateSoftwareRenderTarget();
if (nativeRenderTarget is null)
{
throw new RenderTargetNotReadyException();
}
return new FramebufferRenderTarget(this, nativeRenderTarget);
}
protected internal unsafe class TopLevelEvents : NativeCallbackBase, IAvnTopLevelEvents
{
private readonly TopLevelImpl _parent;
public TopLevelEvents(TopLevelImpl parent)
{
_parent = parent;
}
void IAvnTopLevelEvents.Closed()
{
var n = _parent.Native;
try
{
_parent?.Closed?.Invoke();
}
finally
{
_parent?.Dispose();
n?.Dispose();
}
}
void IAvnTopLevelEvents.Paint()
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.UiThreadRender);
var s = _parent.ClientSize;
_parent.Paint?.Invoke(new Rect(0, 0, s.Width, s.Height));
}
void IAvnTopLevelEvents.Resized(AvnSize* size, AvnPlatformResizeReason reason)
{
if (_parent?.Native == null)
{
return;
}
var s = new Size(size->Width, size->Height);
_parent._savedLogicalSize = s;
_parent.Resized?.Invoke(s, (WindowResizeReason)reason);
}
void IAvnTopLevelEvents.RawMouseEvent(AvnRawMouseEventType type, ulong timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
{
_parent.RawMouseEvent(type, timeStamp, modifiers, point, delta);
}
int IAvnTopLevelEvents.RawKeyEvent(AvnRawKeyEventType type, ulong timeStamp, AvnInputModifiers modifiers, AvnKey key, AvnPhysicalKey physicalKey, string keySymbol)
{
return _parent.RawKeyEvent(type, timeStamp, modifiers, key, physicalKey, keySymbol).AsComBool();
}
int IAvnTopLevelEvents.RawTextInputEvent(ulong timeStamp, string text)
{
return _parent.RawTextInputEvent(timeStamp, text).AsComBool();
}
void IAvnTopLevelEvents.ScalingChanged(double scaling)
{
_parent._savedScaling = scaling;
_parent.ScalingChanged?.Invoke(scaling);
}
void IAvnTopLevelEvents.RunRenderPriorityJobs()
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.UiThreadRender);
}
void IAvnTopLevelEvents.LostFocus()
{
_parent.LostFocus?.Invoke();
}
AvnDragDropEffects IAvnTopLevelEvents.DragEvent(AvnDragEventType type, AvnPoint position,
AvnInputModifiers modifiers,
AvnDragDropEffects effects,
IAvnClipboard clipboard, IntPtr dataObjectHandle)
{
var device = AvaloniaLocator.Current.GetService<IDragDropDevice>();
if (device is null)
{
return AvnDragDropEffects.None;
}
if (_parent._inputRoot is null)
{
return AvnDragDropEffects.None;
}
IDataObject? dataObject = null;
if (dataObjectHandle != IntPtr.Zero)
dataObject = GCHandle.FromIntPtr(dataObjectHandle).Target as IDataObject;
using (var clipboardDataObject = new ClipboardDataObject(clipboard))
{
if (dataObject == null)
dataObject = clipboardDataObject;
var args = new RawDragEvent(device, (RawDragEventType)type,
_parent._inputRoot, position.ToAvaloniaPoint(), dataObject, (DragDropEffects)effects,
(RawInputModifiers)modifiers);
_parent.Input(args);
return (AvnDragDropEffects)args.Effects;
}
}
IAvnAutomationPeer? IAvnTopLevelEvents.AutomationPeer
{
get
{
var native = _parent.GetAutomationPeer();
return native is null ? null : AvnAutomationPeer.Wrap(native);
}
}
}
private class FramebufferRenderTarget : IFramebufferRenderTarget
{
private readonly TopLevelImpl _parent;
private IAvnSoftwareRenderTarget? _target;
public FramebufferRenderTarget(TopLevelImpl parent, IAvnSoftwareRenderTarget target)
{
_parent = parent;
_target = target;
}
public void Dispose()
{
lock (_parent._syncRoot)
{
_target?.Dispose();
_target = null;
}
}
public ILockedFramebuffer Lock()
{
var w = _parent._savedLogicalSize.Width * _parent._savedScaling;
var h = _parent._savedLogicalSize.Height * _parent._savedScaling;
var dpi = _parent._savedScaling * 96;
return new DeferredFramebuffer(_target, cb =>
{
lock (_parent._syncRoot)
{
if (_parent.Native != null && _target != null)
{
cb(_parent.Native);
}
}
}, (int)w, (int)h, new Vector(dpi, dpi));
}
}
}