diff --git a/src/Avalonia.Controls/Platform/ITopLevelWithWin32JitterHacks.cs b/src/Avalonia.Controls/Platform/ITopLevelWithWin32JitterHacks.cs
new file mode 100644
index 0000000000..a11de89558
--- /dev/null
+++ b/src/Avalonia.Controls/Platform/ITopLevelWithWin32JitterHacks.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Avalonia.Controls.Platform
+{
+ ///
+ /// This file is needed to represent how awesome Windows is
+ /// See https://stackoverflow.com/questions/53000291/how-to-smooth-ugly-jitter-flicker-jumping-when-resizing-windows-especially-drag for more details
+ ///
+ public interface ITopLevelWithWin32JitterHacks
+ {
+ Action Win32JitterLastFrameRepaint { get; set; }
+ }
+}
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 21bd0e4e57..1dcce3dcd7 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls.Notifications;
+using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Raw;
@@ -114,6 +115,9 @@ namespace Avalonia.Controls
impl.Resized = HandleResized;
impl.ScalingChanged = HandleScalingChanged;
+ if (impl is ITopLevelWithWin32JitterHacks jitterHacks)
+ jitterHacks.Win32JitterLastFrameRepaint += HandleWin32JitterRepaint;
+
_keyboardNavigationHandler?.SetOwner(this);
_accessKeyHandler?.SetOwner(this);
styler?.ApplyStyles(this);
@@ -271,6 +275,11 @@ namespace Avalonia.Controls
Renderer?.Paint(rect);
}
+ protected virtual void HandleWin32JitterRepaint()
+ {
+ Renderer?.RepaintLastFrameIfExists();
+ }
+
///
/// Handles a closed notification from .
///
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index 0d077d2a3a..0ebbba66a6 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -31,6 +31,7 @@ namespace Avalonia.Rendering
private volatile IRef _scene;
private DirtyVisuals _dirty;
private IRef _overlay;
+ private IRef _lastRenderedScene;
private int _lastSceneId = -1;
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IRef _currentDraw;
@@ -129,6 +130,9 @@ namespace Avalonia.Rendering
var scene = _scene;
_scene = null;
scene?.Dispose();
+ scene = _lastRenderedScene;
+ _lastRenderedScene = null;
+ scene?.Dispose();
}
Stop();
@@ -176,6 +180,11 @@ namespace Avalonia.Rendering
Render(true);
}
+ public void RepaintLastFrameIfExists()
+ {
+ Render(true, true);
+ }
+
///
public void Resized(Size size)
{
@@ -229,7 +238,7 @@ namespace Avalonia.Rendering
internal void UnitTestRender() => Render(false);
- private void Render(bool forceComposite)
+ private void Render(bool forceComposite, bool doNotUpdateScene = false)
{
using (var l = _lock.TryLock())
{
@@ -256,12 +265,17 @@ namespace Avalonia.Rendering
}
- var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext);
+ var (scene, updated) =
+ doNotUpdateScene ?
+ (_lastRenderedScene?.Clone(), false) :
+ UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext);
using (scene)
{
if (scene?.Item != null)
{
+ _lastRenderedScene?.Dispose();
+ _lastRenderedScene = scene.Clone();
var overlay = DrawDirtyRects || DrawFps;
if (DrawDirtyRects)
_dirtyRectsDisplay.Tick();
diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs
index 36a1f7d220..bcdba81dd1 100644
--- a/src/Avalonia.Visuals/Rendering/IRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs
@@ -62,6 +62,8 @@ namespace Avalonia.Rendering
/// The dirty rectangle.
void Paint(Rect rect);
+ void RepaintLastFrameIfExists();
+
///
/// Starts the renderer.
///
diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
index 21129e38af..b7d79d66aa 100644
--- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
@@ -88,6 +88,11 @@ namespace Avalonia.Rendering
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
}
+ public void RepaintLastFrameIfExists()
+ {
+ // No-op, we don't have a saved frame
+ }
+
///
public void Resized(Size size)
{
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index bc7fc1c9fa..a770089694 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -1235,23 +1235,31 @@ namespace Avalonia.Win32.Interop
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
- public IntPtr hwnd;
- public IntPtr hwndInsertAfter;
- public int x;
- public int y;
- public int cx;
- public int cy;
+ public IntPtr hWndInsertAfter;
+ public IntPtr hWnd;
+ public int X;
+ public int Y;
+ public int CX;
+ public int CY;
public uint flags;
}
[StructLayout(LayoutKind.Sequential)]
- public struct NCCALCSIZE_PARAMS
+ public struct NCCALSIZEPARAMS_ARGS
{
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
- public RECT[] rgrc;
- public WINDOWPOS lppos;
+ public RECT NewRect;
+ public RECT OldRect;
+ public RECT OldClientRect;
+ public WINDOWPOS WindowPos;
}
-
+
+ public struct NCCALSIZEPARAMS_RETURN
+ {
+ public RECT NewClientRect;
+ public RECT DstRect;
+ public RECT SrcRect;
+ }
+
public struct TRACKMOUSEEVENT
{
public int cbSize;
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 475f7da6b7..98b6547fae 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using Avalonia.Controls;
+using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.OpenGL;
@@ -20,7 +21,8 @@ using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32
{
- public class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
+ public unsafe class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
+ ITopLevelWithWin32JitterHacks
{
private static readonly List s_instances = new List();
@@ -84,7 +86,10 @@ namespace Avalonia.Win32
public Action Input { get; set; }
+
public Action Paint { get; set; }
+
+ public Action Win32JitterLastFrameRepaint { get; set; }
public Action Resized { get; set; }
@@ -459,12 +464,26 @@ namespace Avalonia.Win32
return IntPtr.Zero;
+
case WindowsMessage.WM_NCCALCSIZE:
- if (ToInt32(wParam) == 1 && !_decorated)
+ if (ToInt32(wParam) == 1)
{
- return IntPtr.Zero;
+ var ncargs = *(NCCALSIZEPARAMS_ARGS*)lParam;
+ var rv = (NCCALSIZEPARAMS_RETURN*)lParam;
+
+ if (_decorated)
+ DefWindowProc(_hwnd, msg, wParam, lParam);
+ else
+ {
+ var x = rv->NewClientRect.left;
+ var y = rv->NewClientRect.top;
+ rv->DstRect = rv->SrcRect = new RECT {left = x, top = y, right = x + 1, bottom = y + 1};
+ }
+
+ return new IntPtr(0x0400);
}
break;
+
case UnmanagedMethods.WindowsMessage.WM_CLOSE:
bool? preventClosing = Closing?.Invoke();
@@ -661,6 +680,11 @@ namespace Avalonia.Win32
return IntPtr.Zero;
}
break;
+ case WindowsMessage.WM_ERASEBKGND:
+
+ if (_decorated)
+ return new IntPtr(1);
+ break;
case WindowsMessage.WM_NCACTIVATE:
if (!_decorated)
@@ -690,6 +714,11 @@ namespace Avalonia.Win32
{
// Do nothing here, just block until the pending frame render is completed on the render thread
}
+
+ // This is needed to fool Windows™®Ⓒ to think that we've reacted to resize in time,
+ // so it won't do atrocities to our window
+ Win32JitterLastFrameRepaint?.Invoke();
+
var size = (UnmanagedMethods.SizeCommand)wParam;
if (Resized != null &&
@@ -697,6 +726,7 @@ namespace Avalonia.Win32
size == UnmanagedMethods.SizeCommand.Maximized))
{
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
+
Resized(clientSize / Scaling);
}
@@ -782,7 +812,7 @@ namespace Avalonia.Win32
UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX
{
cbSize = Marshal.SizeOf(),
- style = (int)(ClassStyles.CS_OWNDC | ClassStyles.CS_HREDRAW | ClassStyles.CS_VREDRAW), // Unique DC helps with performance when using Gpu based rendering
+ style = (int)(ClassStyles.CS_OWNDC),// | ClassStyles.CS_HREDRAW | ClassStyles.CS_VREDRAW), // Unique DC helps with performance when using Gpu based rendering
lpfnWndProc = _wndProcDelegate,
hInstance = UnmanagedMethods.GetModuleHandle(null),
hCursor = DefaultCursor,