diff --git a/src/Modern.WindowKit/Avalonia.Mac/WindowImplBase.cs b/src/Modern.WindowKit/Avalonia.Mac/WindowImplBase.cs index 91c5b38..efcf668 100644 --- a/src/Modern.WindowKit/Avalonia.Mac/WindowImplBase.cs +++ b/src/Modern.WindowKit/Avalonia.Mac/WindowImplBase.cs @@ -220,17 +220,17 @@ void IAvnWindowBaseEvents.PositionChanged(AvnPoint position) _parent.PositionChanged?.Invoke(position.ToAvaloniaPixelPoint()); } - void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta) + void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, ulong 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) + int IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, ulong timeStamp, AvnInputModifiers modifiers, uint key) { return _parent.RawKeyEvent(type, timeStamp, modifiers, key).AsComBool(); } - int IAvnWindowBaseEvents.RawTextInputEvent(uint timeStamp, string text) + int IAvnWindowBaseEvents.RawTextInputEvent(ulong timeStamp, string text) { return _parent.RawTextInputEvent(timeStamp, text).AsComBool(); } @@ -286,7 +286,7 @@ public void Activate() _native?.Activate(); } - public bool RawTextInputEvent(uint timeStamp, string text) + public bool RawTextInputEvent(ulong timeStamp, string text) { //if (_inputRoot is null) // return false; @@ -300,7 +300,7 @@ public bool RawTextInputEvent(uint timeStamp, string text) return args.Handled; } - public bool RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key) + public bool RawKeyEvent(AvnRawKeyEventType type, ulong timeStamp, AvnInputModifiers modifiers, uint key) { //if (_inputRoot is null) // return false; @@ -319,7 +319,7 @@ protected virtual bool ChromeHitTest(RawPointerEventArgs e) return false; } - public void RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta) + public void RawMouseEvent(AvnRawMouseEventType type, ulong timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta) { //if (_inputRoot is null) // return; diff --git a/src/Modern.WindowKit/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Modern.WindowKit/Avalonia.Skia/SkiaSharpExtensions.cs index 036078e..d8b22fe 100644 --- a/src/Modern.WindowKit/Avalonia.Skia/SkiaSharpExtensions.cs +++ b/src/Modern.WindowKit/Avalonia.Skia/SkiaSharpExtensions.cs @@ -19,7 +19,8 @@ public static SKFilterQuality ToSKFilterQuality(this BitmapInterpolationMode int return SKFilterQuality.Medium; case BitmapInterpolationMode.HighQuality: return SKFilterQuality.High; - case BitmapInterpolationMode.Default: + case BitmapInterpolationMode.None: + case BitmapInterpolationMode.Unspecified: return SKFilterQuality.None; default: throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); @@ -30,6 +31,7 @@ public static SKFilterQuality ToSKFilterQuality(this BitmapInterpolationMode int //{ // switch (blendingMode) // { + // case BitmapBlendingMode.Unspecified: // case BitmapBlendingMode.SourceOver: // return SKBlendMode.SrcOver; // case BitmapBlendingMode.Source: diff --git a/src/Modern.WindowKit/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Modern.WindowKit/Avalonia.Skia/WriteableBitmapImpl.cs index 58a84e1..1d1a34f 100644 --- a/src/Modern.WindowKit/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Modern.WindowKit/Avalonia.Skia/WriteableBitmapImpl.cs @@ -131,7 +131,7 @@ public WriteableBitmapImpl(PixelSize size, Vector dpi, PixelFormat format, Alpha //} /// - public void Dispose() + public virtual void Dispose() { _bitmap.Dispose(); } diff --git a/src/Modern.WindowKit/Avalonia.Win32/Win32StorageProvider.cs b/src/Modern.WindowKit/Avalonia.Win32/Win32StorageProvider.cs index b458999..97251e8 100644 --- a/src/Modern.WindowKit/Avalonia.Win32/Win32StorageProvider.cs +++ b/src/Modern.WindowKit/Avalonia.Win32/Win32StorageProvider.cs @@ -99,12 +99,14 @@ private unsafe Task> ShowFilePicker( } frm.SetOptions(options); - if (defaultExtension is not null) + if (defaultExtension is null) { - fixed (char* pExt = defaultExtension) - { - frm.SetDefaultExtension(pExt); - } + defaultExtension = String.Empty; + } + + fixed (char* pExt = defaultExtension) + { + frm.SetDefaultExtension(pExt); } suggestedFileName ??= ""; diff --git a/src/Modern.WindowKit/BitmapInterpolationMode.cs b/src/Modern.WindowKit/BitmapInterpolationMode.cs index edc723b..02351a1 100644 --- a/src/Modern.WindowKit/BitmapInterpolationMode.cs +++ b/src/Modern.WindowKit/BitmapInterpolationMode.cs @@ -3,12 +3,14 @@ /// /// Controls the performance and quality of bitmap scaling. /// - public enum BitmapInterpolationMode + public enum BitmapInterpolationMode : byte { + Unspecified, + /// - /// Uses the default behavior of the underling render backend. + /// Disable interpolation. /// - Default, + None, /// /// The best performance but worst image quality. @@ -18,7 +20,7 @@ public enum BitmapInterpolationMode /// /// Good performance and decent image quality. /// - MediumQuality, + MediumQuality, /// /// Highest quality but worst performance. diff --git a/src/Modern.WindowKit/DataFormats.cs b/src/Modern.WindowKit/DataFormats.cs index cb6eadb..b1c7fd7 100644 --- a/src/Modern.WindowKit/DataFormats.cs +++ b/src/Modern.WindowKit/DataFormats.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; namespace Modern.WindowKit.Input { @@ -17,7 +18,7 @@ public static class DataFormats /// /// Dataformat for one or more filenames /// - [Obsolete("Use DataFormats.Files, this format is supported only on desktop platforms.")] + [Obsolete("Use DataFormats.Files, this format is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)] public static readonly string FileNames = nameof(FileNames); } } diff --git a/src/Modern.WindowKit/DataObjectExtensions.cs b/src/Modern.WindowKit/DataObjectExtensions.cs index 0b4953b..1378c9b 100644 --- a/src/Modern.WindowKit/DataObjectExtensions.cs +++ b/src/Modern.WindowKit/DataObjectExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using Modern.WindowKit.Platform.Storage; @@ -25,7 +26,7 @@ public static class DataObjectExtensions /// /// Collection of file names. If format isn't available, returns null. /// - [System.Obsolete("Use GetFiles, this method is supported only on desktop platforms.")] + [System.Obsolete("Use GetFiles, this method is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable? GetFileNames(this IDataObject dataObject) { return (dataObject.Get(DataFormats.FileNames) as IEnumerable) diff --git a/src/Modern.WindowKit/Dispatcher.Invoke.cs b/src/Modern.WindowKit/Dispatcher.Invoke.cs index f9f40c9..031c1c5 100644 --- a/src/Modern.WindowKit/Dispatcher.Invoke.cs +++ b/src/Modern.WindowKit/Dispatcher.Invoke.cs @@ -248,11 +248,11 @@ public TResult Invoke(Func callback, DispatcherPriority priori /// An operation representing the queued delegate to be invoked. /// /// - /// Note that the default priority is DispatcherPriority.Normal. + /// Note that the default priority is DispatcherPriority.Default. /// public DispatcherOperation InvokeAsync(Action callback) { - return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None); + return InvokeAsync(callback, default, CancellationToken.None); } /// @@ -326,11 +326,11 @@ public DispatcherOperation InvokeAsync(Action callback, DispatcherPriority prior /// An operation representing the queued delegate to be invoked. /// /// - /// Note that the default priority is DispatcherPriority.Normal. + /// Note that the default priority is DispatcherPriority.Default. /// public DispatcherOperation InvokeAsync(Func callback) { - return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None); + return InvokeAsync(callback, DispatcherPriority.Default, CancellationToken.None); } /// @@ -541,6 +541,18 @@ public void Post(Action action, DispatcherPriority priority = default) InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None); } + /// + /// Executes the specified Func<Task> asynchronously on the + /// thread that the Dispatcher was created on + /// + /// + /// A Func<Task> delegate to invoke through the dispatcher. + /// + /// + /// An task that completes after the task returned from callback finishes. + /// + public Task InvokeAsync(Func callback) => InvokeAsync(callback, DispatcherPriority.Default); + /// /// Executes the specified Func<Task> asynchronously on the /// thread that the Dispatcher was created on @@ -556,11 +568,29 @@ public void Post(Action action, DispatcherPriority priority = default) /// /// An task that completes after the task returned from callback finishes /// - public Task InvokeAsync(Func callback, DispatcherPriority priority = default) + public Task InvokeAsync(Func callback, DispatcherPriority priority) { _ = callback ?? throw new ArgumentNullException(nameof(callback)); return InvokeAsync(callback, priority).GetTask().Unwrap(); } + + /// + /// Executes the specified Func<Task<TResult>> asynchronously on the + /// thread that the Dispatcher was created on + /// + /// + /// A Func<Task<TResult>> delegate to invoke through the dispatcher. + /// + /// + /// The priority that determines in what order the specified + /// callback is invoked relative to the other pending operations + /// in the Dispatcher. + /// + /// + /// An task that completes after the task returned from callback finishes + /// + public Task InvokeAsync(Func> action) => + InvokeAsync(action, DispatcherPriority.Default); /// /// Executes the specified Func<Task<TResult>> asynchronously on the @@ -577,7 +607,7 @@ public Task InvokeAsync(Func callback, DispatcherPriority priority = defau /// /// An task that completes after the task returned from callback finishes /// - public Task InvokeAsync(Func> action, DispatcherPriority priority = default) + public Task InvokeAsync(Func> action, DispatcherPriority priority) { _ = action ?? throw new ArgumentNullException(nameof(action)); return InvokeAsync>(action, priority).GetTask().Unwrap(); diff --git a/src/Modern.WindowKit/DispatcherFrame.cs b/src/Modern.WindowKit/DispatcherFrame.cs index 9edb8b3..471654e 100644 --- a/src/Modern.WindowKit/DispatcherFrame.cs +++ b/src/Modern.WindowKit/DispatcherFrame.cs @@ -91,31 +91,44 @@ public bool Continue internal void Run(IControlledDispatcherImpl impl) { - // Since the actual platform run loop is controlled by a Cancellation token, we are restarting - // it if frame still needs to run - while (Continue) - RunCore(impl); - } - - private void RunCore(IControlledDispatcherImpl impl) - { - if (_isRunning) - throw new InvalidOperationException("This frame is already running"); - _isRunning = true; - try + Dispatcher.VerifyAccess(); + + // Since the actual platform run loop is controlled by a Cancellation token, we have an + // outer loop that restarts the platform one in case Continue was set to true after being set to false + while (true) { - _cancellationTokenSource = new CancellationTokenSource(); - // Wake up the dispatcher in case it has pending jobs - Dispatcher.RequestProcessing(); - impl.RunLoop(_cancellationTokenSource.Token); + // Take the instance lock since `Continue` is changed from one too + lock (Dispatcher.InstanceLock) + { + if (!Continue) + return; + + if (_isRunning) + throw new InvalidOperationException("This frame is already running"); + + _cancellationTokenSource = new CancellationTokenSource(); + _isRunning = true; } - finally - { - _isRunning = false; - _cancellationTokenSource?.Cancel(); - _cancellationTokenSource = null; + + try + { + // Wake up the dispatcher in case it has pending jobs + Dispatcher.RequestProcessing(); + impl.RunLoop(_cancellationTokenSource.Token); + } + finally + { + lock (Dispatcher.InstanceLock) + { + _isRunning = false; + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } + } } } + internal void MaybeExitOnDispatcherRequest() { diff --git a/src/Modern.WindowKit/DispatcherOperation.cs b/src/Modern.WindowKit/DispatcherOperation.cs index 3feed69..478ed66 100644 --- a/src/Modern.WindowKit/DispatcherOperation.cs +++ b/src/Modern.WindowKit/DispatcherOperation.cs @@ -331,6 +331,8 @@ public DispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority, F private TaskCompletionSource TaskCompletionSource => (TaskCompletionSource)TaskSource!; + public new TaskAwaiter GetAwaiter() => GetTask().GetAwaiter(); + public new Task GetTask() => TaskCompletionSource!.Task; protected override Task GetTaskCore() => GetTask(); diff --git a/src/Modern.WindowKit/DispatcherPriority.cs b/src/Modern.WindowKit/DispatcherPriority.cs index 59f13dc..d78026b 100644 --- a/src/Modern.WindowKit/DispatcherPriority.cs +++ b/src/Modern.WindowKit/DispatcherPriority.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; namespace Modern.WindowKit.Threading { @@ -100,7 +101,7 @@ private DispatcherPriority(int value) /// /// The job will be processed with the same priority as data binding. /// - [Obsolete("WPF compatibility")] public static readonly DispatcherPriority DataBind = new(Layout); + [Obsolete("WPF compatibility"), EditorBrowsable(EditorBrowsableState.Never)] public static readonly DispatcherPriority DataBind = new(Layout); /// /// The job will be processed with normal priority. diff --git a/src/Modern.WindowKit/ManagedDispatcherImpl.cs b/src/Modern.WindowKit/ManagedDispatcherImpl.cs index f268485..5707ce9 100644 --- a/src/Modern.WindowKit/ManagedDispatcherImpl.cs +++ b/src/Modern.WindowKit/ManagedDispatcherImpl.cs @@ -58,6 +58,10 @@ public void UpdateTimer(long? dueTimeInMs) public void RunLoop(CancellationToken token) { + CancellationTokenRegistration registration = default; + if (token.CanBeCanceled) + registration = token.Register(() => _wakeup.Set()); + while (!token.IsCancellationRequested) { bool signaled; @@ -105,5 +109,7 @@ public void RunLoop(CancellationToken token) else _wakeup.WaitOne(); } + + registration.Dispose(); } } diff --git a/src/Modern.WindowKit/Modern.WindowKit.csproj b/src/Modern.WindowKit/Modern.WindowKit.csproj index 01f22cf..2f34360 100644 --- a/src/Modern.WindowKit/Modern.WindowKit.csproj +++ b/src/Modern.WindowKit/Modern.WindowKit.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Modern.WindowKit/Screen.cs b/src/Modern.WindowKit/Screen.cs index fcbc60e..e87e715 100644 --- a/src/Modern.WindowKit/Screen.cs +++ b/src/Modern.WindowKit/Screen.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; namespace Modern.WindowKit.Platform { @@ -17,7 +18,7 @@ public class Screen public double Scaling { get; } /// - [Obsolete("Use the Scaling property instead.")] + [Obsolete("Use the Scaling property instead."), EditorBrowsable(EditorBrowsableState.Never)] public double PixelDensity => Scaling; /// @@ -43,7 +44,7 @@ public class Screen public bool IsPrimary { get; } /// - [Obsolete("Use the IsPrimary property instead.")] + [Obsolete("Use the IsPrimary property instead."), EditorBrowsable(EditorBrowsableState.Never)] public bool Primary => IsPrimary; /// diff --git a/src/Modern.WindowKit/Screens.cs b/src/Modern.WindowKit/Screens.cs index 143dbf1..623e2ed 100644 --- a/src/Modern.WindowKit/Screens.cs +++ b/src/Modern.WindowKit/Screens.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using Modern.WindowKit.Platform; @@ -68,7 +69,7 @@ public Screens(IScreenImpl iScreenImpl) /// /// The window impl for which to retrieve the Screen. /// The . - [Obsolete("Use ScreenFromWindow(WindowBase) overload.")] + [Obsolete("Use ScreenFromWindow(WindowBase) overload."), EditorBrowsable(EditorBrowsableState.Never)] public Screen? ScreenFromWindow(IWindowBaseImpl window) { return _iScreenImpl.ScreenFromWindow(window); diff --git a/src/Modern.WindowKit/StorageProviderHelpers.cs b/src/Modern.WindowKit/StorageProviderHelpers.cs index 0dbf8f0..38d65b7 100644 --- a/src/Modern.WindowKit/StorageProviderHelpers.cs +++ b/src/Modern.WindowKit/StorageProviderHelpers.cs @@ -50,7 +50,8 @@ public static bool TryFilePathToUri(string path, [NotNullWhen(true)] out Uri? ur } } - public static string NameWithExtension(string path, string? defaultExtension, FilePickerFileType? filter) + [return: NotNullIfNotNull(nameof(path))] + public static string? NameWithExtension(string? path, string? defaultExtension, FilePickerFileType? filter) { var name = Path.GetFileName(path); if (name != null && !Path.HasExtension(name))