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(); _mouse = new MouseDevice(); _cursorFactory = AvaloniaLocator.Current.GetService(); } 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 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 Paint { get; set; } public Action 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(); 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(); var customRendererFactory = AvaloniaLocator.Current.GetService(); 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 PositionChanged { get; set; } public Action Input { get; set; } public Action ScalingChanged { get; set; } public Action 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; } } }