diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index ed0bffdf10..e620a77e52 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -11,6 +11,7 @@ namespace ControlCatalog { this.InitializeComponent(); this.AttachDevTools(); + //Renderer.DrawFps = true; //Renderer.DrawDirtyRects = Renderer.DrawFps = true; } diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index 6327be12a5..2e5b2902f4 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -12,6 +12,7 @@ using Android.Runtime; using Android.Views; using Android.Widget; using Avalonia.Platform; +using Avalonia.Threading; namespace Avalonia.Android { @@ -78,13 +79,13 @@ namespace Avalonia.Android private void EnsureInvokeOnMainThread(Action action) => _handler.Post(action); - public void Signal() + public void Signal(DispatcherPriority prio) { - EnsureInvokeOnMainThread(() => Signaled?.Invoke()); + EnsureInvokeOnMainThread(() => Signaled?.Invoke(null)); } public bool CurrentThreadIsLoopThread => Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread()); - public event Action Signaled; + public event Action Signaled; } } \ No newline at end of file diff --git a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs index 8101e4b550..68f9e2c631 100644 --- a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs @@ -3,6 +3,7 @@ using System; using System.Threading; +using Avalonia.Threading; namespace Avalonia.Platform { @@ -21,10 +22,10 @@ namespace Avalonia.Platform /// An used to stop the timer. IDisposable StartTimer(TimeSpan interval, Action tick); - void Signal(); + void Signal(DispatcherPriority priority); bool CurrentThreadIsLoopThread { get; } - event Action Signaled; + event Action Signaled; } } diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs index c32aeed0b4..7a0249f876 100644 --- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs +++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs @@ -36,7 +36,7 @@ namespace Avalonia.Threading /// public override void Post(SendOrPostCallback d, object state) { - Dispatcher.UIThread.InvokeAsync(() => d(state)); + Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send); } /// @@ -45,7 +45,7 @@ namespace Avalonia.Threading if (Dispatcher.UIThread.CheckAccess()) d(state); else - Dispatcher.UIThread.InvokeTaskAsync(() => d(state)).Wait(); + Dispatcher.UIThread.InvokeTaskAsync(() => d(state), DispatcherPriority.Send).Wait(); } } } \ No newline at end of file diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index d46b7142f4..a60b663bed 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -60,7 +60,7 @@ namespace Avalonia.Threading public void MainLoop(CancellationToken cancellationToken) { var platform = AvaloniaLocator.Current.GetService(); - cancellationToken.Register(platform.Signal); + cancellationToken.Register(() => platform.Signal(DispatcherPriority.Send)); platform.RunLoop(cancellationToken); } @@ -69,7 +69,7 @@ namespace Avalonia.Threading /// public void RunJobs() { - _jobRunner?.RunJobs(); + _jobRunner?.RunJobs(null); } /// diff --git a/src/Avalonia.Base/Threading/DispatcherPriority.cs b/src/Avalonia.Base/Threading/DispatcherPriority.cs index 8d6be6eeec..1faa2da7f8 100644 --- a/src/Avalonia.Base/Threading/DispatcherPriority.cs +++ b/src/Avalonia.Base/Threading/DispatcherPriority.cs @@ -10,10 +10,10 @@ namespace Avalonia.Threading public enum DispatcherPriority { /// - /// The job will not be processed. + /// Minimum possible priority /// - Inactive = 0, - + MinValue = 1, + /// /// The job will be processed when the system is idle. /// @@ -48,20 +48,30 @@ namespace Avalonia.Threading /// The job will be processed with the same priority as render. /// Render = 7, - + + /// + /// The job will be processed with the same priority as render. + /// + Layout = 8, + /// /// The job will be processed with the same priority as data binding. /// - DataBind = 8, + DataBind = 9, /// /// The job will be processed with normal priority. /// - Normal = 9, + Normal = 10, /// /// The job will be processed before other asynchronous operations. /// - Send = 10, + Send = 11, + + /// + /// Maximum possible priority + /// + MaxValue = 11 } } diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index e212cd9a2e..c2040a0982 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Avalonia.Platform; @@ -13,29 +14,45 @@ namespace Avalonia.Threading /// internal class JobRunner { - private readonly Queue _queue = new Queue(); + + private IPlatformThreadingInterface _platform; + private Queue[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1) + .Select(_ => new Queue()).ToArray(); + public JobRunner(IPlatformThreadingInterface platform) { _platform = platform; } + Job GetNextJob(DispatcherPriority minimumPriority) + { + for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--) + { + var q = _queues[c]; + lock (q) + { + if (q.Count > 0) + return q.Dequeue(); + } + } + return null; + } + /// /// Runs continuations pushed on the loop. /// - public void RunJobs() + /// Priority to execute jobs for. Pass null if platform doesn't have internal priority system + public void RunJobs(DispatcherPriority? priority) { + var minimumPriority = priority ?? DispatcherPriority.MinValue; while (true) { - Job job; - - lock (_queue) - { - if (_queue.Count == 0) - return; - job = _queue.Dequeue(); - } + var job = GetNextJob(minimumPriority); + if (job == null) + return; + if (job.TaskCompletionSource == null) { @@ -77,7 +94,6 @@ namespace Avalonia.Threading /// The priority with which to invoke the method. internal void Post(Action action, DispatcherPriority priority) { - // TODO: Respect priority. AddJob(new Job(action, priority, true)); } @@ -92,13 +108,14 @@ namespace Avalonia.Threading private void AddJob(Job job) { var needWake = false; - lock (_queue) + var queue = _queues[(int) job.Priority]; + lock (queue) { - needWake = _queue.Count == 0; - _queue.Enqueue(job); + needWake = queue.Count == 0; + queue.Enqueue(job); } if (needWake) - _platform?.Signal(); + _platform?.Signal(job.Priority); } /// diff --git a/src/Avalonia.Base/Threading/SingleThreadDispatcher.cs b/src/Avalonia.Base/Threading/SingleThreadDispatcher.cs deleted file mode 100644 index 76d47b30b0..0000000000 --- a/src/Avalonia.Base/Threading/SingleThreadDispatcher.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Avalonia.Platform; - -namespace Avalonia.Threading -{ - public class SingleThreadDispatcher : Dispatcher - { - class ThreadingInterface : IPlatformThreadingInterface - { - private readonly AutoResetEvent _evnt = new AutoResetEvent(false); - private readonly JobRunner _timerJobRunner; - - public ThreadingInterface() - { - _timerJobRunner = new JobRunner(this); - } - - public void RunLoop(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - _evnt.WaitOne(); - if (cancellationToken.IsCancellationRequested) - return; - Signaled?.Invoke(); - _timerJobRunner.RunJobs(); - } - } - - public IDisposable StartTimer(TimeSpan interval, Action tick) - => AvaloniaLocator.Current.GetService().StartSystemTimer(interval, - () => _timerJobRunner.Post(tick, DispatcherPriority.Normal)); - - public void Signal() => _evnt.Set(); - //TODO: Actually perform a check - public bool CurrentThreadIsLoopThread => true; - - public event Action Signaled; - } - - public SingleThreadDispatcher() : base(new ThreadingInterface()) - { - } - - public static Dispatcher StartNew(CancellationToken token) - { - var dispatcher = new SingleThreadDispatcher(); - AvaloniaLocator.Current.GetService().PostThreadPoolItem(() => - { - dispatcher.MainLoop(token); - }); - return dispatcher; - } - } -} diff --git a/src/Avalonia.Base/Utilities/WeakTimer.cs b/src/Avalonia.Base/Utilities/WeakTimer.cs index a68ed9072f..5c44a6d122 100644 --- a/src/Avalonia.Base/Utilities/WeakTimer.cs +++ b/src/Avalonia.Base/Utilities/WeakTimer.cs @@ -23,7 +23,6 @@ namespace Avalonia.Utilities _timer = new DispatcherTimer(); _timer.Tick += delegate { OnTick(); }; - _timer.Start(); } private void OnTick() diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index a9b6afc172..663b7a22d5 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -165,7 +165,7 @@ namespace Avalonia.Controls.Primitives if (increaseButton != null) { - increaseButton.Arrange(new Rect(0, firstHeight + thumbHeight, finalSize.Width, remaining - firstHeight)); + increaseButton.Arrange(new Rect(0, firstHeight + thumbHeight, finalSize.Width, Math.Max(remaining - firstHeight, 0))); } } diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 3244f5e7dc..f8911dc036 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -203,7 +203,7 @@ namespace Avalonia.Layout { if (!_queued && !_running) { - Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Render); + Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Layout); _queued = true; } } diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs index a4e8e6f0c2..5dff3715b3 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs @@ -15,7 +15,7 @@ namespace Avalonia.Rendering /// public class DefaultRenderLoop : IRenderLoop { - private IPlatformThreadingInterface _threading; + private IRuntimePlatform _runtime; private int _subscriberCount; private EventHandler _tick; private IDisposable _subscription; @@ -78,12 +78,12 @@ namespace Avalonia.Rendering /// protected virtual IDisposable StartCore(Action tick) { - if (_threading == null) + if (_runtime == null) { - _threading = AvaloniaLocator.Current.GetService(); + _runtime = AvaloniaLocator.Current.GetService(); } - return _threading.StartTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), tick); + return _runtime.StartSystemTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), tick); } /// diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index bff41a65eb..f1b990f349 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -1,7 +1,6 @@  netstandard2.0 - False false @@ -25,34 +24,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs index 41e174bce4..00d1ec05f3 100644 --- a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs @@ -23,11 +23,11 @@ namespace Avalonia.Gtk3 public ILockedFramebuffer Lock() { - if(_window.CurrentCairoContext == IntPtr.Zero) - throw new InvalidOperationException("Window is not in drawing state"); - var width = (int) _window.ClientSize.Width; - var height = (int) _window.ClientSize.Height; - return new ImageSurfaceFramebuffer(_window.CurrentCairoContext, _window.GtkWidget, width, height); + // This method may be called from non-UI thread, don't touch anything that calls back to GTK/GDK + var s = _window.ClientSize; + var width = (int) s.Width; + var height = (int) s.Height; + return new ImageSurfaceFramebuffer(_window, width, height); } } } diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 4ea1a5af56..b36a1cda91 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -12,6 +12,7 @@ using Avalonia.Input.Platform; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Gtk3; +using Avalonia.Threading; namespace Avalonia.Gtk3 { @@ -21,6 +22,7 @@ namespace Avalonia.Gtk3 internal static readonly MouseDevice Mouse = new MouseDevice(); internal static readonly KeyboardDevice Keyboard = new KeyboardDevice(); internal static IntPtr App { get; set; } + public static bool UseDeferredRendering = true; public static void Initialize() { Resolver.Resolve(); @@ -65,37 +67,42 @@ namespace Avalonia.Gtk3 public IDisposable StartTimer(TimeSpan interval, Action tick) { - return GlibTimeout.StarTimer((uint) interval.TotalMilliseconds, tick); + var msec = interval.TotalMilliseconds; + if (msec <= 0) + throw new ArgumentException("Don't know how to create a timer with zero or negative interval"); + var imsec = (uint) msec; + if (imsec == 0) + imsec = 1; + return GlibTimeout.StarTimer(imsec, tick); } - private bool _signaled = false; + private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1]; object _lock = new object(); - - public void Signal() + public void Signal(DispatcherPriority prio) { + var idx = (int) prio; lock(_lock) - if (!_signaled) + if (!_signaled[idx]) { - _signaled = true; - GlibTimeout.Add(0, () => + _signaled[idx] = true; + GlibTimeout.Add(GlibPriority.FromDispatcherPriority(prio), 0, () => { lock (_lock) { - _signaled = false; + _signaled[idx] = false; } - Signaled?.Invoke(); + Signaled?.Invoke(prio); return false; }); } } - public event Action Signaled; + public event Action Signaled; [ThreadStatic] private static bool s_tlsMarker; public bool CurrentThreadIsLoopThread => s_tlsMarker; - } } @@ -103,10 +110,11 @@ namespace Avalonia { public static class Gtk3AppBuilderExtensions { - public static T UseGtk3(this AppBuilderBase builder, ICustomGtk3NativeLibraryResolver resolver = null) + public static T UseGtk3(this AppBuilderBase builder, bool deferredRendering = true, ICustomGtk3NativeLibraryResolver resolver = null) where T : AppBuilderBase, new() { Resolver.Custom = resolver; + Gtk3Platform.UseDeferredRendering = deferredRendering; return builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); } } diff --git a/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs b/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs new file mode 100644 index 0000000000..8c1456726c --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs @@ -0,0 +1,9 @@ +using System; + +namespace Avalonia.Gtk3 +{ + public interface IDeferredRenderOperation : IDisposable + { + void RenderNow(); + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index 61b1e69aa2..34a95df47e 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -1,26 +1,28 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Gtk3.Interop; using Avalonia.Platform; +using Avalonia.Threading; namespace Avalonia.Gtk3 { class ImageSurfaceFramebuffer : ILockedFramebuffer { - private IntPtr _context; + private readonly WindowBaseImpl _impl; private readonly GtkWidget _widget; private CairoSurface _surface; private int _factor; - - public ImageSurfaceFramebuffer(IntPtr context, GtkWidget widget, int width, int height) + private object _lock = new object(); + public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height) { - _context = context; - _widget = widget; + _impl = impl; + _widget = impl.GtkWidget; _factor = (int)(Native.GtkWidgetGetScaleFactor?.Invoke(_widget) ?? 1u); width *= _factor; height *= _factor; @@ -32,18 +34,97 @@ namespace Avalonia.Gtk3 RowBytes = Native.CairoImageSurfaceGetStride(_surface); Native.CairoSurfaceFlush(_surface); } + + static void Draw(IntPtr context, CairoSurface surface, double factor) + { + + Native.CairoSurfaceMarkDirty(surface); + Native.CairoScale(context, 1d / factor, 1d / factor); + Native.CairoSetSourceSurface(context, surface, 0, 0); + Native.CairoPaint(context); + + } + /* + static Stopwatch St =Stopwatch.StartNew(); + private static int _frames; + private static int _fps;*/ + static void DrawToWidget(GtkWidget widget, CairoSurface surface, int width, int height, double factor) + { + if(surface == null || widget.IsClosed) + return; + var window = Native.GtkWidgetGetWindow(widget); + if(window == IntPtr.Zero) + return; + var rc = new GdkRectangle {Width = width, Height = height}; + Native.GdkWindowBeginPaintRect(window, ref rc); + var context = Native.GdkCairoCreate(window); + Draw(context, surface, factor); + /* + _frames++; + var el = St.Elapsed; + if (el.TotalSeconds > 1) + { + _fps = (int) (_frames / el.TotalSeconds); + _frames = 0; + St = Stopwatch.StartNew(); + } + + Native.CairoSetSourceRgba(context, 1, 0, 0, 1); + Native.CairoMoveTo(context, 20, 20); + Native.CairoSetFontSize(context, 30); + using (var txt = new Utf8Buffer("FPS: " + _fps)) + Native.CairoShowText(context, txt); + */ + + Native.CairoDestroy(context); + Native.GdkWindowEndPaint(window); + } + + class RenderOp : IDeferredRenderOperation + { + private readonly GtkWidget _widget; + private CairoSurface _surface; + private readonly double _factor; + private readonly int _width; + private readonly int _height; + + public RenderOp(GtkWidget widget, CairoSurface _surface, double factor, int width, int height) + { + _widget = widget; + this._surface = _surface; + _factor = factor; + _width = width; + _height = height; + } + + public void Dispose() + { + _surface?.Dispose(); + _surface = null; + } + + public void RenderNow() + { + DrawToWidget(_widget, _surface, _width, _height, _factor); + } + } public void Dispose() { - if(_context == IntPtr.Zero || _surface == null) - return; - Native.CairoSurfaceMarkDirty(_surface); - Native.CairoScale(_context, 1d / _factor, 1d / _factor); - Native.CairoSetSourceSurface(_context, _surface, 0, 0); - Native.CairoPaint(_context); - _context = IntPtr.Zero; - _surface.Dispose(); - _surface = null; + lock (_lock) + { + if (Dispatcher.UIThread.CheckAccess()) + { + if (_impl.CurrentCairoContext != IntPtr.Zero) + Draw(_impl.CurrentCairoContext, _surface, _factor); + else + DrawToWidget(_widget, _surface, Width, Height, _factor); + _surface.Dispose(); + } + else + _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Width, Height)); + _surface = null; + } } public IntPtr Address { get; } diff --git a/src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs b/src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs index 7838be9305..87a70c4d35 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; namespace Avalonia.Gtk3.Interop diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs new file mode 100644 index 0000000000..add4ba3c02 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Threading; + +namespace Avalonia.Gtk3.Interop +{ + static class GlibPriority + { + public static int High = -100; + public static int Default = 0; + public static int HighIdle = 100; + public static int GtkResize = HighIdle + 10; + public static int GtkPaint = HighIdle + 20; + public static int DefaultIdle = 200; + public static int Low = 300; + public static int GdkEvents = Default; + public static int GdkRedraw = HighIdle + 20; + + public static int FromDispatcherPriority(DispatcherPriority prio) + { + if (prio == DispatcherPriority.Send) + return High; + if (prio == DispatcherPriority.Normal) + return Default; + if (prio == DispatcherPriority.DataBind) + return Default + 1; + if (prio == DispatcherPriority.Layout) + return Default + 2; + if (prio == DispatcherPriority.Render) + return Default + 3; + if (prio == DispatcherPriority.Loaded) + return GtkPaint + 20; + if (prio == DispatcherPriority.Input) + return GtkPaint + 21; + if (prio == DispatcherPriority.Background) + return DefaultIdle + 1; + if (prio == DispatcherPriority.ContextIdle) + return DefaultIdle + 2; + if (prio == DispatcherPriority.ApplicationIdle) + return DefaultIdle + 3; + if (prio == DispatcherPriority.SystemIdle) + return DefaultIdle + 4; + throw new ArgumentException("Unknown priority"); + + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index 9971d8881d..be886ea1c7 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Avalonia.Threading; namespace Avalonia.Gtk3.Interop { @@ -30,10 +31,11 @@ namespace Avalonia.Gtk3.Interop } - public static void Add(uint interval, Func callback) + public static void Add(int priority, uint interval, Func callback) { var handle = GCHandle.Alloc(callback); - Native.GTimeoutAdd(interval, PinnedHandler, GCHandle.ToIntPtr(handle)); + //Native.GTimeoutAdd(interval, PinnedHandler, GCHandle.ToIntPtr(handle)); + Native.GTimeoutAddFull(priority, interval, PinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero); } class Timer : IDisposable @@ -48,8 +50,10 @@ namespace Avalonia.Gtk3.Interop public static IDisposable StarTimer(uint interval, Action tick) { + if (interval == 0) + throw new ArgumentException("Don't know how to create a timer with zero or negative interval"); var timer = new Timer (); - GlibTimeout.Add(interval, + GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Background), interval, () => { if (timer.Stopped) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 35aef7b4e5..a6a08c3614 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -169,6 +169,9 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] public delegate void cairo_surface_mark_dirty(CairoSurface surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_surface_write_to_png(CairoSurface surface, Utf8Buffer path); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] public delegate void cairo_surface_flush(CairoSurface surface); @@ -178,15 +181,36 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_set_source_rgba(IntPtr cr, double r, double g, double b, double a); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] public delegate void cairo_scale(IntPtr context, double sx, double sy); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] public delegate void cairo_paint(IntPtr context); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_show_text(IntPtr context, Utf8Buffer text); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_set_font_size(IntPtr context, double size); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_select_font_face(IntPtr context, Utf8Buffer face, int slant, int weight); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_move_to(IntPtr context, double x, double y); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_destroy(IntPtr context); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_widget_queue_draw_area(GtkWidget widget, int x, int y, int width, int height); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_widget_add_tick_callback(GtkWidget widget, TickCallback callback, IntPtr userData, IntPtr destroy); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate GtkImContext gtk_im_multicontext_new(); @@ -236,6 +260,15 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] public delegate void gdk_window_begin_resize_drag(IntPtr window, WindowEdge edge, gint button, gint root_x, gint root_y, guint32 timestamp); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate void gdk_window_process_updates(IntPtr window, bool updateChildren); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate void gdk_window_begin_paint_rect(IntPtr window, ref GdkRectangle rect); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate void gdk_window_end_paint(IntPtr window); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] public delegate void gdk_event_request_motions(IntPtr ev); @@ -273,6 +306,9 @@ namespace Avalonia.Gtk3.Interop public delegate bool gdk_pixbuf_save_to_bufferv(Pixbuf pixbuf, out IntPtr buffer, out IntPtr buffer_size, Utf8Buffer type, IntPtr option_keys, IntPtr option_values, out IntPtr error); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate IntPtr gdk_cairo_create(IntPtr window); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate void g_object_unref(IntPtr instance); @@ -287,7 +323,10 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data); - + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] + public delegate ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, IntPtr destroy); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_free(IntPtr data); @@ -320,6 +359,11 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void GtkClipboardTextReceivedFunc(IntPtr clipboard, IntPtr utf8string, IntPtr userdata); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool TickCallback(IntPtr widget, IntPtr clock, IntPtr userdata); + + } public static D.gdk_display_get_n_screens GdkDisplayGetNScreens; @@ -366,6 +410,7 @@ namespace Avalonia.Gtk3.Interop public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_timeout_add GTimeoutAdd; + public static D.g_timeout_add_full GTimeoutAddFull; public static D.g_free GFree; public static D.g_slist_free GSlistFree; public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData; @@ -373,6 +418,7 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_widget_set_events GtkWidgetSetEvents; public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect; public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea; + public static D.gtk_widget_add_tick_callback GtkWidgetAddTickCallback; public static D.gtk_widget_activate GtkWidgetActivate; public static D.gtk_clipboard_get_for_display GtkClipboardGetForDisplay; public static D.gtk_clipboard_request_text GtkClipboardRequestText; @@ -398,6 +444,10 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag; public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag; public static D.gdk_event_request_motions GdkEventRequestMotions; + public static D.gdk_window_process_updates GdkWindowProcessUpdates; + public static D.gdk_window_begin_paint_rect GdkWindowBeginPaintRect; + public static D.gdk_window_end_paint GdkWindowEndPaint; + public static D.gdk_pixbuf_new_from_file GdkPixbufNewFromFile; public static D.gtk_icon_theme_get_default GtkIconThemeGetDefault; @@ -406,16 +456,24 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_window_set_cursor GdkWindowSetCursor; public static D.gdk_pixbuf_new_from_stream GdkPixbufNewFromStream; public static D.gdk_pixbuf_save_to_bufferv GdkPixbufSaveToBufferv; - + public static D.gdk_cairo_create GdkCairoCreate; + public static D.cairo_image_surface_create CairoImageSurfaceCreate; public static D.cairo_image_surface_get_data CairoImageSurfaceGetData; public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride; public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty; + public static D.cairo_surface_write_to_png CairoSurfaceWriteToPng; public static D.cairo_surface_flush CairoSurfaceFlush; public static D.cairo_surface_destroy CairoSurfaceDestroy; public static D.cairo_set_source_surface CairoSetSourceSurface; + public static D.cairo_set_source_rgba CairoSetSourceRgba; public static D.cairo_scale CairoScale; public static D.cairo_paint CairoPaint; + public static D.cairo_show_text CairoShowText; + public static D.cairo_select_font_face CairoSelectFontFace; + public static D.cairo_set_font_size CairoSetFontSize; + public static D.cairo_move_to CairoMoveTo; + public static D.cairo_destroy CairoDestroy; } public enum GtkWindowType diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs index 764cbfd6b6..31625e3f27 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -140,13 +140,11 @@ namespace Avalonia.Gtk3.Interop var nativeHandleNames = new[] { "gdk_win32_window_get_handle", "gdk_x11_window_get_xid", "gdk_quartz_window_get_nswindow" }; foreach (var name in nativeHandleNames) { - try - { - Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal - .GetDelegateForFunctionPointer(loader.GetProcAddress(dlls[GtkDll.Gdk], name, false), typeof(Native.D.gdk_get_native_handle)); - break; - } - catch { } + var ptr = loader.GetProcAddress(dlls[GtkDll.Gdk], name, true); + if (ptr == IntPtr.Zero) + continue; + Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle) Marshal + .GetDelegateForFunctionPointer(ptr, typeof(Native.D.gdk_get_native_handle)); } if (Native.GetNativeGdkWindowHandle == null) throw new Exception($"Unable to locate any of [{string.Join(", ", nativeHandleNames)}] in libgdk"); diff --git a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs index 0f4fbd54bd..786c9a2cf0 100644 --- a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs @@ -10,7 +10,7 @@ namespace Avalonia.Gtk3 { public int ScreenCount { - get => _allScreens.Length; + get => AllScreens.Length; } private Screen[] _allScreens; @@ -23,7 +23,7 @@ namespace Avalonia.Gtk3 IntPtr display = Native.GdkGetDefaultDisplay(); GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display); short primary = Native.GdkScreenGetPrimaryMonitor(screen); - Screen[] screens = new Screen[ScreenCount]; + Screen[] screens = new Screen[Native.GdkScreenGetNMonitors(screen)]; for (short i = 0; i < screens.Length; i++) { GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle(); diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 225aa2a795..a3b5d57fdc 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -10,6 +10,7 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Threading; namespace Avalonia.Gtk3 { @@ -25,6 +26,9 @@ namespace Avalonia.Gtk3 private double _lastScaling; private uint _lastKbdEvent; private uint _lastSmoothScrollEvent; + private GCHandle _gcHandle; + private object _lock = new object(); + private IDeferredRenderOperation _nextRenderOperation; public WindowBaseImpl(GtkWindow gtkWidget) { @@ -50,11 +54,25 @@ namespace Avalonia.Gtk3 Connect("destroy", OnDestroy); Native.GtkWidgetRealize(gtkWidget); _lastSize = ClientSize; + GlibTimeout.Add(0, 16, () => + { + Invalidate(default(Rect)); + return true; + }); + if (Gtk3Platform.UseDeferredRendering) + { + Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); + _gcHandle = GCHandle.Alloc(this); + Native.GtkWidgetAddTickCallback(GtkWidget, PinnedStaticCallback, GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero); + + } } private bool OnConfigured(IntPtr gtkwidget, IntPtr ev, IntPtr userdata) { - var size = ClientSize; + int w, h; + Native.GtkWindowGetSize(GtkWidget, out w, out h); + var size = ClientSize = new Size(w, h); if (_lastSize != size) { Resized?.Invoke(size); @@ -222,12 +240,52 @@ namespace Avalonia.Gtk3 private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) { - CurrentCairoContext = cairocontext; - Paint?.Invoke(new Rect(ClientSize)); - CurrentCairoContext = IntPtr.Zero; + if (!Gtk3Platform.UseDeferredRendering) + { + CurrentCairoContext = cairocontext; + Paint?.Invoke(new Rect(ClientSize)); + CurrentCairoContext = IntPtr.Zero; + } + return true; + } + + private static Native.D.TickCallback PinnedStaticCallback = StaticTickCallback; + + static bool StaticTickCallback(IntPtr widget, IntPtr clock, IntPtr userData) + { + var impl = (WindowBaseImpl) GCHandle.FromIntPtr(userData).Target; + impl.OnRenderTick(); return true; } + public void SetNextRenderOperation(IDeferredRenderOperation op) + { + lock (_lock) + { + _nextRenderOperation?.Dispose(); + _nextRenderOperation = op; + } + } + + private void OnRenderTick() + { + IDeferredRenderOperation op = null; + lock (_lock) + { + if (_nextRenderOperation != null) + { + op = _nextRenderOperation; + _nextRenderOperation = null; + } + } + if (op != null) + { + op?.RenderNow(); + op?.Dispose(); + } + } + + public void Dispose() { //We are calling it here, since signal handler will be detached @@ -236,6 +294,10 @@ namespace Avalonia.Gtk3 foreach(var d in Disposables.AsEnumerable().Reverse()) d.Dispose(); Disposables.Clear(); + if (_gcHandle.IsAllocated) + { + _gcHandle.Free(); + } } public Size MaxClientSize @@ -270,7 +332,8 @@ namespace Avalonia.Gtk3 { if(GtkWidget.IsClosed) return; - Native.GtkWidgetQueueDrawArea(GtkWidget, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height); + var s = ClientSize; + Native.GtkWidgetQueueDrawArea(GtkWidget, 0, 0, (int) s.Width, (int) s.Height); } public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; @@ -323,17 +386,7 @@ namespace Avalonia.Gtk3 } - public Size ClientSize - { - get - { - if (GtkWidget.IsClosed) - return new Size(); - int w, h; - Native.GtkWindowGetSize(GtkWidget, out w, out h); - return new Size(w, h); - } - } + public Size ClientSize { get; private set; } public void Resize(Size value) { @@ -363,7 +416,10 @@ namespace Avalonia.Gtk3 public IRenderer CreateRenderer(IRenderRoot root) { - return new ImmediateRenderer(root); + var loop = AvaloniaLocator.Current.GetService(); + return Gtk3Platform.UseDeferredRendering + ? (IRenderer) new DeferredRenderer(root, loop) + : new ImmediateRenderer(root); } } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs b/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs index 9231649754..3aef6944af 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Threading; namespace Avalonia.LinuxFramebuffer { @@ -30,7 +31,7 @@ namespace Avalonia.LinuxFramebuffer while (true) { if (0 == WaitHandle.WaitAny(handles)) - Signaled?.Invoke(); + Signaled?.Invoke(null); else { while (true) @@ -97,7 +98,7 @@ namespace Avalonia.LinuxFramebuffer } - public void Signal() + public void Signal(DispatcherPriority prio) { _signaled.Set(); } @@ -105,7 +106,7 @@ namespace Avalonia.LinuxFramebuffer [ThreadStatic] private static bool TlsCurrentThreadIsLoopThread; public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread; - public event Action Signaled; + public event Action Signaled; public event EventHandler Tick; } diff --git a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs index 5da38bf0de..80c854f5a5 100644 --- a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs +++ b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using Avalonia.Platform; +using Avalonia.Threading; using MonoMac.AppKit; using MonoMac.CoreGraphics; using MonoMac.Foundation; @@ -13,12 +14,12 @@ namespace Avalonia.MonoMac public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; - public event Action Signaled; + public event Action Signaled; public IDisposable StartTimer(TimeSpan interval, Action tick) => NSTimer.CreateRepeatingScheduledTimer(interval, () => tick()); - public void Signal() + public void Signal(DispatcherPriority prio) { lock (this) { @@ -34,7 +35,7 @@ namespace Avalonia.MonoMac return; _signaled = false; } - Signaled?.Invoke(); + Signaled?.Invoke(null); }); } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index d8e9256156..a260efd9b9 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -15,6 +15,7 @@ using Avalonia.Win32.Input; using Avalonia.Win32.Interop; using Avalonia.Controls; using Avalonia.Rendering; +using Avalonia.Threading; #if NETSTANDARD using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; #else @@ -137,7 +138,7 @@ namespace Avalonia.Win32 private static readonly int SignalW = unchecked((int) 0xdeadbeaf); private static readonly int SignalL = unchecked((int)0x12345678); - public void Signal() + public void Signal(DispatcherPriority prio) { UnmanagedMethods.PostMessage( _hwnd, @@ -148,14 +149,14 @@ namespace Avalonia.Win32 public bool CurrentThreadIsLoopThread => _uiThread == UnmanagedMethods.GetCurrentThreadId(); - public event Action Signaled; + public event Action Signaled; [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { if (msg == (int) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM && wParam.ToInt64() == SignalW && lParam.ToInt64() == SignalL) { - Signaled?.Invoke(); + Signaled?.Invoke(null); } return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4c28d44b93..348468e0e7 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -16,6 +16,7 @@ using Avalonia.Win32.Input; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; using Avalonia.Rendering; +using Avalonia.Threading; #if NETSTANDARD using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; #endif diff --git a/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs b/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs index e2e72f4c3c..6d6a5e22ca 100644 --- a/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs +++ b/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs @@ -9,6 +9,7 @@ using CoreAnimation; using Foundation; using Avalonia.Platform; using Avalonia.Shared.PlatformSupport; +using Avalonia.Threading; namespace Avalonia.iOS { @@ -18,7 +19,7 @@ namespace Avalonia.iOS public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; - public event Action Signaled; + public event Action Signaled; public void RunLoop(CancellationToken cancellationToken) { //Mobile platforms are using external main loop @@ -53,7 +54,7 @@ namespace Avalonia.iOS public IDisposable StartTimer(TimeSpan interval, Action tick) => NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick()); - public void Signal() + public void Signal(DispatcherPriority prio) { lock (this) { @@ -65,7 +66,7 @@ namespace Avalonia.iOS { lock (this) _signaled = false; - Signaled?.Invoke(); + Signaled?.Invoke(null); }); } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs index c4c5414e7f..229a34643d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs @@ -6,6 +6,7 @@ using System.Reactive.Subjects; using System.Threading; using System.Threading.Tasks; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.UnitTests; using Xunit; @@ -147,14 +148,14 @@ namespace Avalonia.Base.UnitTests public bool CurrentThreadIsLoopThread { get; set; } - public event Action Signaled; + public event Action Signaled; public void RunLoop(CancellationToken cancellationToken) { throw new NotImplementedException(); } - public void Signal() + public void Signal(DispatcherPriority prio) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index e733f59059..409870ed0f 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -13,7 +13,7 @@ using Avalonia.Platform; using System.Threading.Tasks; using System; using System.Threading; - +using Avalonia.Threading; #if AVALONIA_SKIA using Avalonia.Skia; #else @@ -149,14 +149,14 @@ namespace Avalonia.Direct2D1.RenderTests public Thread MainThread { get; set; } - public event Action Signaled; + public event Action Signaled; public void RunLoop(CancellationToken cancellationToken) { throw new NotImplementedException(); } - public void Signal() + public void Signal(DispatcherPriority prio) { throw new NotImplementedException(); }