csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
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.
514 lines
18 KiB
514 lines
18 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
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.Raw;
|
|
using Avalonia.Native.Interop;
|
|
using Avalonia.OpenGL;
|
|
using Avalonia.Platform;
|
|
using Avalonia.Rendering;
|
|
using Avalonia.Threading;
|
|
|
|
namespace Avalonia.Native
|
|
{
|
|
internal class MacOSTopLevelWindowHandle : IPlatformHandle, IMacOSTopLevelPlatformHandle
|
|
{
|
|
IAvnWindowBase _native;
|
|
|
|
public MacOSTopLevelWindowHandle(IAvnWindowBase native)
|
|
{
|
|
_native = native;
|
|
}
|
|
|
|
public IntPtr Handle => NSWindow;
|
|
|
|
public string HandleDescriptor => "NSWindow";
|
|
|
|
public IntPtr NSView => _native?.ObtainNSViewHandle() ?? IntPtr.Zero;
|
|
|
|
public IntPtr NSWindow => _native?.ObtainNSWindowHandle() ?? IntPtr.Zero;
|
|
|
|
public IntPtr GetNSViewRetained()
|
|
{
|
|
return _native?.ObtainNSViewHandleRetained() ?? IntPtr.Zero;
|
|
}
|
|
|
|
public IntPtr GetNSWindowRetained()
|
|
{
|
|
return _native?.ObtainNSWindowHandleRetained() ?? IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
internal abstract class WindowBaseImpl : IWindowBaseImpl,
|
|
IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost
|
|
{
|
|
protected readonly IAvaloniaNativeFactory _factory;
|
|
protected IInputRoot _inputRoot;
|
|
IAvnWindowBase _native;
|
|
private object _syncRoot = new object();
|
|
private bool _deferredRendering = false;
|
|
private bool _gpu = false;
|
|
private readonly MouseDevice _mouse;
|
|
private readonly IKeyboardDevice _keyboard;
|
|
private readonly ICursorFactory _cursorFactory;
|
|
private Size _savedLogicalSize;
|
|
private Size _lastRenderedLogicalSize;
|
|
private double _savedScaling;
|
|
private GlPlatformSurface _glSurface;
|
|
private NativeControlHostImpl _nativeControlHost;
|
|
private IGlContext _glContext;
|
|
|
|
internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
|
|
AvaloniaNativePlatformOpenGlInterface glFeature)
|
|
{
|
|
_factory = factory;
|
|
_gpu = opts.UseGpu && glFeature != null;
|
|
_deferredRendering = opts.UseDeferredRendering;
|
|
|
|
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
|
|
_mouse = new MouseDevice();
|
|
_cursorFactory = AvaloniaLocator.Current.GetService<ICursorFactory>();
|
|
}
|
|
|
|
protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext)
|
|
{
|
|
_native = window;
|
|
_glContext = glContext;
|
|
|
|
Handle = new MacOSTopLevelWindowHandle(window);
|
|
if (_gpu)
|
|
_glSurface = new GlPlatformSurface(window, _glContext);
|
|
Screen = new ScreenImpl(screens);
|
|
_savedLogicalSize = ClientSize;
|
|
_savedScaling = RenderScaling;
|
|
_nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
|
|
|
|
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
|
|
.FirstOrDefault(m => m.Bounds.Contains(Position));
|
|
|
|
Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout);
|
|
}
|
|
|
|
public IAvnWindowBase Native => _native;
|
|
|
|
public Size ClientSize
|
|
{
|
|
get
|
|
{
|
|
if (_native != null)
|
|
{
|
|
var s = _native.ClientSize;
|
|
return new Size(s.Width, s.Height);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public Size? FrameSize
|
|
{
|
|
get
|
|
{
|
|
if (_native != null)
|
|
{
|
|
var s = _native.FrameSize;
|
|
return new Size(s.Width, s.Height);
|
|
}
|
|
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public IEnumerable<object> Surfaces => new[] {
|
|
(_gpu ? _glSurface : (object)null),
|
|
this
|
|
};
|
|
|
|
public INativeControlHostImpl NativeControlHost => _nativeControlHost;
|
|
|
|
public ILockedFramebuffer Lock()
|
|
{
|
|
var w = _savedLogicalSize.Width * _savedScaling;
|
|
var h = _savedLogicalSize.Height * _savedScaling;
|
|
var dpi = _savedScaling * 96;
|
|
return new DeferredFramebuffer(cb =>
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
if (_native == null)
|
|
return false;
|
|
cb(_native);
|
|
_lastRenderedLogicalSize = _savedLogicalSize;
|
|
return true;
|
|
}
|
|
}, (int)w, (int)h, new Vector(dpi, dpi));
|
|
}
|
|
|
|
public Action LostFocus { get; set; }
|
|
|
|
public Action<Rect> Paint { get; set; }
|
|
public Action<Size, PlatformResizeReason> Resized { get; set; }
|
|
public Action Closed { get; set; }
|
|
public IMouseDevice MouseDevice => _mouse;
|
|
public abstract IPopupImpl CreatePopup();
|
|
|
|
public AutomationPeer GetAutomationPeer()
|
|
{
|
|
return _inputRoot is Control c ? ControlAutomationPeer.CreatePeerForElement(c) : null;
|
|
}
|
|
|
|
protected unsafe class WindowBaseEvents : CallbackBase, IAvnWindowBaseEvents
|
|
{
|
|
private readonly WindowBaseImpl _parent;
|
|
|
|
public WindowBaseEvents(WindowBaseImpl parent)
|
|
{
|
|
_parent = parent;
|
|
}
|
|
|
|
void IAvnWindowBaseEvents.Closed()
|
|
{
|
|
var n = _parent._native;
|
|
try
|
|
{
|
|
_parent?.Closed?.Invoke();
|
|
}
|
|
finally
|
|
{
|
|
|
|
_parent?.Dispose();
|
|
n?.Dispose();
|
|
}
|
|
}
|
|
|
|
void IAvnWindowBaseEvents.Activated() => _parent.Activated?.Invoke();
|
|
|
|
void IAvnWindowBaseEvents.Deactivated() => _parent.Deactivated?.Invoke();
|
|
|
|
void IAvnWindowBaseEvents.Paint()
|
|
{
|
|
Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
|
|
var s = _parent.ClientSize;
|
|
_parent.Paint?.Invoke(new Rect(0, 0, s.Width, s.Height));
|
|
}
|
|
|
|
void IAvnWindowBaseEvents.Resized(AvnSize* size, AvnPlatformResizeReason reason)
|
|
{
|
|
if (_parent?._native != null)
|
|
{
|
|
var s = new Size(size->Width, size->Height);
|
|
_parent._savedLogicalSize = s;
|
|
_parent.Resized?.Invoke(s, (PlatformResizeReason)reason);
|
|
}
|
|
}
|
|
|
|
void IAvnWindowBaseEvents.PositionChanged(AvnPoint position)
|
|
{
|
|
_parent.PositionChanged?.Invoke(position.ToAvaloniaPixelPoint());
|
|
}
|
|
|
|
void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
|
|
{
|
|
_parent.RawMouseEvent(type, timeStamp, modifiers, point, delta);
|
|
}
|
|
|
|
int IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key)
|
|
{
|
|
return _parent.RawKeyEvent(type, timeStamp, modifiers, key).AsComBool();
|
|
}
|
|
|
|
int IAvnWindowBaseEvents.RawTextInputEvent(uint timeStamp, string text)
|
|
{
|
|
return _parent.RawTextInputEvent(timeStamp, text).AsComBool();
|
|
}
|
|
|
|
void IAvnWindowBaseEvents.ScalingChanged(double scaling)
|
|
{
|
|
_parent._savedScaling = scaling;
|
|
_parent.ScalingChanged?.Invoke(scaling);
|
|
}
|
|
|
|
void IAvnWindowBaseEvents.RunRenderPriorityJobs()
|
|
{
|
|
Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
|
|
}
|
|
|
|
void IAvnWindowBaseEvents.LostFocus()
|
|
{
|
|
_parent.LostFocus?.Invoke();
|
|
}
|
|
|
|
public AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position,
|
|
AvnInputModifiers modifiers,
|
|
AvnDragDropEffects effects,
|
|
IAvnClipboard clipboard, IntPtr dataObjectHandle)
|
|
{
|
|
var device = AvaloniaLocator.Current.GetService<IDragDropDevice>();
|
|
|
|
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 IAvnWindowBaseEvents.AutomationPeer
|
|
{
|
|
get => AvnAutomationPeer.Wrap(_parent.GetAutomationPeer());
|
|
}
|
|
}
|
|
|
|
public void Activate()
|
|
{
|
|
_native?.Activate();
|
|
}
|
|
|
|
public bool RawTextInputEvent(uint timeStamp, string text)
|
|
{
|
|
if (_inputRoot 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, uint timeStamp, AvnInputModifiers modifiers, uint key)
|
|
{
|
|
if (_inputRoot is null)
|
|
return false;
|
|
|
|
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
|
|
|
|
var args = new RawKeyEventArgs(_keyboard, timeStamp, _inputRoot, (RawKeyEventType)type, (Key)key, (RawInputModifiers)modifiers);
|
|
|
|
Input?.Invoke(args);
|
|
|
|
return args.Handled;
|
|
}
|
|
|
|
protected virtual bool ChromeHitTest(RawPointerEventArgs e)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public void RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
|
|
{
|
|
if (_inputRoot 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 Resize(Size clientSize, PlatformResizeReason reason)
|
|
{
|
|
_native?.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason);
|
|
}
|
|
|
|
public IRenderer CreateRenderer(IRenderRoot root)
|
|
{
|
|
if (_deferredRendering)
|
|
{
|
|
var loop = AvaloniaLocator.Current.GetService<IRenderLoop>();
|
|
var customRendererFactory = AvaloniaLocator.Current.GetService<IRendererFactory>();
|
|
|
|
if (customRendererFactory != null)
|
|
return customRendererFactory.Create(root, loop);
|
|
return new DeferredRenderer(root, loop);
|
|
}
|
|
|
|
return new ImmediateRenderer(root);
|
|
}
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
_native?.Close();
|
|
_native?.Dispose();
|
|
_native = null;
|
|
|
|
_nativeControlHost?.Dispose();
|
|
_nativeControlHost = null;
|
|
|
|
(Screen as ScreenImpl)?.Dispose();
|
|
_mouse.Dispose();
|
|
}
|
|
|
|
|
|
public void Invalidate(Rect rect)
|
|
{
|
|
_native?.Invalidate(new AvnRect { Height = rect.Height, Width = rect.Width, X = rect.X, Y = rect.Y });
|
|
}
|
|
|
|
public void SetInputRoot(IInputRoot inputRoot)
|
|
{
|
|
_inputRoot = inputRoot;
|
|
}
|
|
|
|
|
|
public virtual void Show(bool activate, bool isDialog)
|
|
{
|
|
_native?.Show(activate.AsComBool(), isDialog.AsComBool());
|
|
}
|
|
|
|
|
|
public PixelPoint Position
|
|
{
|
|
get => _native?.Position.ToAvaloniaPixelPoint() ?? default;
|
|
set => _native?.SetPosition(value.ToAvnPoint());
|
|
}
|
|
|
|
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 Hide()
|
|
{
|
|
_native?.Hide();
|
|
}
|
|
|
|
public void BeginMoveDrag(PointerPressedEventArgs e)
|
|
{
|
|
_native?.BeginMoveDrag();
|
|
}
|
|
|
|
public Size MaxAutoSizeHint => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(1))
|
|
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
|
|
|
|
public void SetTopmost(bool value)
|
|
{
|
|
_native?.SetTopMost(value.AsComBool());
|
|
}
|
|
|
|
public double RenderScaling => _native?.Scaling ?? 1;
|
|
|
|
public double DesktopScaling => 1;
|
|
|
|
public Action Deactivated { get; set; }
|
|
public Action Activated { get; set; }
|
|
|
|
public void SetCursor(ICursorImpl cursor)
|
|
{
|
|
if (_native == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var newCursor = cursor as AvaloniaNativeCursor;
|
|
newCursor = newCursor ?? (_cursorFactory.GetCursor(StandardCursorType.Arrow) as AvaloniaNativeCursor);
|
|
_native.SetCursor(newCursor.Cursor);
|
|
}
|
|
|
|
public Action<PixelPoint> PositionChanged { get; set; }
|
|
|
|
public Action<RawInputEventArgs> Input { get; set; }
|
|
|
|
public Action<double> ScalingChanged { get; set; }
|
|
|
|
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
|
|
|
|
public IScreenImpl Screen { get; private set; }
|
|
|
|
// TODO
|
|
|
|
public void SetMinMaxSize(Size minSize, Size maxSize)
|
|
{
|
|
_native?.SetMinMaxSize(minSize.ToAvnSize(), maxSize.ToAvnSize());
|
|
}
|
|
|
|
public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
internal void BeginDraggingSession(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard clipboard,
|
|
IAvnDndResultCallback callback, IntPtr sourceHandle)
|
|
{
|
|
_native?.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle);
|
|
}
|
|
|
|
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
|
|
{
|
|
if (TransparencyLevel != transparencyLevel)
|
|
{
|
|
if (transparencyLevel >= WindowTransparencyLevel.Blur)
|
|
{
|
|
transparencyLevel = WindowTransparencyLevel.AcrylicBlur;
|
|
}
|
|
|
|
if(transparencyLevel == WindowTransparencyLevel.None)
|
|
{
|
|
transparencyLevel = WindowTransparencyLevel.Transparent;
|
|
}
|
|
|
|
TransparencyLevel = transparencyLevel;
|
|
|
|
_native?.SetBlurEnabled((TransparencyLevel >= WindowTransparencyLevel.Blur).AsComBool());
|
|
TransparencyLevelChanged?.Invoke(TransparencyLevel);
|
|
}
|
|
}
|
|
|
|
public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent;
|
|
|
|
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0);
|
|
|
|
public IPlatformHandle Handle { get; private set; }
|
|
}
|
|
}
|
|
|