diff --git a/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject b/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
index 319cd523ce..95a483b433 100644
--- a/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
@@ -1,5 +1,3 @@
-
- True
-
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Win32.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.v3.ncrunchproject
index e12537d535..a764f36a24 100644
--- a/.ncrunch/Avalonia.Win32.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Win32.v3.ncrunchproject
@@ -1,5 +1,8 @@
+
+ ..\..\tools\MicroComGenerator\bin\Debug\netcoreapp3.1\**.*
+
MissingOrIgnoredProjectReference
diff --git a/.ncrunch/Direct3DInteropSample.v3.ncrunchproject b/.ncrunch/Direct3DInteropSample.v3.ncrunchproject
index e12537d535..21fcdee3a6 100644
--- a/.ncrunch/Direct3DInteropSample.v3.ncrunchproject
+++ b/.ncrunch/Direct3DInteropSample.v3.ncrunchproject
@@ -3,6 +3,7 @@
MissingOrIgnoredProjectReference
+ True
True
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm
index 455cfa2e41..10f698ff45 100644
--- a/native/Avalonia.Native/src/OSX/Screens.mm
+++ b/native/Avalonia.Native/src/OSX/Screens.mm
@@ -5,12 +5,6 @@ class Screens : public ComSingleObject
public:
FORWARD_IUNKNOWN()
- private:
- CGFloat PrimaryDisplayHeight()
- {
- return NSMaxY([[[NSScreen screens] firstObject] frame]);
- }
-
public:
virtual HRESULT GetScreenCount (int* ret) override
{
@@ -36,12 +30,12 @@ public:
ret->Bounds.Height = [screen frame].size.height;
ret->Bounds.Width = [screen frame].size.width;
ret->Bounds.X = [screen frame].origin.x;
- ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height;
+ ret->Bounds.Y = ConvertPointY(ToAvnPoint([screen frame].origin)).Y - ret->Bounds.Height;
ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->WorkingArea.X = [screen visibleFrame].origin.x;
- ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height;
+ ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
ret->PixelDensity = [screen backingScaleFactor];
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 871bca086d..ea48a19874 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -34,6 +34,7 @@ extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p);
+extern CGFloat PrimaryDisplayHeight();
extern NSSize ToNSSize (AvnSize s);
#ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__)
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index cd6ef73826..72f5aa0a7d 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -299,10 +299,14 @@ AvnPoint ToAvnPoint (NSPoint p)
AvnPoint ConvertPointY (AvnPoint p)
{
- auto sw = [NSScreen.screens objectAtIndex:0].frame;
+ auto primaryDisplayHeight = NSMaxY([[[NSScreen screens] firstObject] frame]);
- auto t = MAX(sw.origin.y, sw.origin.y + sw.size.height);
- p.Y = t - p.Y;
+ p.Y = primaryDisplayHeight - p.Y;
return p;
}
+
+CGFloat PrimaryDisplayHeight()
+{
+ return NSMaxY([[[NSScreen screens] firstObject] frame]);
+}
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 8419258fe9..9d49025398 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -1391,17 +1391,20 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[super viewDidChangeBackingProperties];
}
-- (bool) ignoreUserInput
+- (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
{
auto parentWindow = objc_cast([self window]);
if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
{
- auto window = dynamic_cast(_parent.getRaw());
-
- if(window != nullptr)
+ if(trigerInputWhenDisabled)
{
- window->WindowEvents->GotInputWhenDisabled();
+ auto window = dynamic_cast(_parent.getRaw());
+
+ if(window != nullptr)
+ {
+ window->WindowEvents->GotInputWhenDisabled();
+ }
}
return TRUE;
@@ -1412,7 +1415,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{
- if([self ignoreUserInput])
+ bool triggerInputWhenDisabled = type != Move;
+
+ if([self ignoreUserInput: triggerInputWhenDisabled])
{
return;
}
@@ -1578,7 +1583,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
{
- if([self ignoreUserInput])
+ if([self ignoreUserInput: false])
{
return;
}
diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs
index c39f414b44..4edb0f137a 100644
--- a/samples/ControlCatalog/Pages/ScreenPage.cs
+++ b/samples/ControlCatalog/Pages/ScreenPage.cs
@@ -29,7 +29,7 @@ namespace ControlCatalog.Pages
var screens = w.Screens.All;
var scaling = ((IRenderRoot)w).RenderScaling;
- var drawBrush = Brushes.Green;
+ var drawBrush = Brushes.Black;
Pen p = new Pen(drawBrush);
if (screens != null)
foreach (Screen screen in screens)
@@ -45,18 +45,16 @@ namespace ControlCatalog.Pages
screen.Bounds.Height / 10f);
Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
screen.WorkingArea.Height / 10f);
+
context.DrawRectangle(p, boundsRect);
context.DrawRectangle(p, workingAreaRect);
-
- FormattedText text = new FormattedText()
- {
- Typeface = Typeface.Default
- };
- text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}";
+ var text = new FormattedText() { Typeface = new Typeface("Arial"), FontSize = 18 };
+
+ text.Text = $"Bounds: {screen.Bounds.TopLeft} {screen.Bounds.Width}:{screen.Bounds.Height}";
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height), text);
- text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
+ text.Text = $"WorkArea: {screen.WorkingArea.TopLeft} {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
text.Text = $"Scaling: {screen.PixelDensity * 100}%";
@@ -69,7 +67,7 @@ namespace ControlCatalog.Pages
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
}
- context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));
+ context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10));
}
}
}
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 91eef3947b..c779e4b0cb 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -80,6 +80,7 @@ namespace Avalonia.Controls
private ICommand _command;
private bool _commandCanExecute = true;
+ private KeyGesture _hotkey;
///
/// Initializes static members of the class.
@@ -207,6 +208,11 @@ namespace Avalonia.Controls
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
+ if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
+ {
+ HotKey = _hotkey;
+ }
+
base.OnAttachedToLogicalTree(e);
if (Command != null)
@@ -217,6 +223,13 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
+ // This will cause the hotkey manager to dispose the observer and the reference to this control
+ if (HotKey != null)
+ {
+ _hotkey = HotKey;
+ HotKey = null;
+ }
+
base.OnDetachedFromLogicalTree(e);
if (Command != null)
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index d034f54df2..71ac0fa523 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -102,6 +102,7 @@ namespace Avalonia.Controls
private ICommand? _command;
private bool _commandCanExecute = true;
private Popup? _popup;
+ private KeyGesture _hotkey;
///
/// Initializes static members of the class.
@@ -337,6 +338,11 @@ namespace Avalonia.Controls
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
+ if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
+ {
+ HotKey = _hotkey;
+ }
+
base.OnAttachedToLogicalTree(e);
if (Command != null)
@@ -347,6 +353,13 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
+ // This will cause the hotkey manager to dispose the observer and the reference to this control
+ if (HotKey != null)
+ {
+ _hotkey = HotKey;
+ HotKey = null;
+ }
+
base.OnDetachedFromLogicalTree(e);
if (Command != null)
diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs
index 529aec1393..42e0d85dcd 100644
--- a/src/Avalonia.Controls/Utils/AncestorFinder.cs
+++ b/src/Avalonia.Controls/Utils/AncestorFinder.cs
@@ -47,6 +47,8 @@ namespace Avalonia.Controls.Utils
public void Dispose()
{
+ _child?.Dispose();
+ _subject.Dispose();
_disposable.Dispose();
}
}
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 927b739b0c..11d5801532 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -383,7 +383,7 @@ namespace Avalonia.Native
_native.BeginMoveDrag();
}
- public Size MaxAutoSizeHint => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
+ public Size MaxAutoSizeHint => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(1))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
public void SetTopmost(bool value)
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
index 1f2508715e..ac78f32231 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
@@ -15,7 +15,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (d.Namespace == XamlNamespaces.Xaml2006)
{
- if (d.Name == "Precompile" || d.Name == "Class")
+ if (d.Name == "Precompile" ||
+ d.Name == "Class" ||
+ d.Name == "FieldModifier" ||
+ d.Name == "ClassModifier")
no.Children.Remove(d);
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
index 151c3070f4..2a3db9992c 100644
--- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
@@ -1,9 +1,14 @@
-using Moq;
+using System;
+using System.Collections.Generic;
+using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
+using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Styling;
+using Avalonia.UnitTests;
+using Moq;
using Xunit;
using System;
using Avalonia.Input.Raw;
@@ -53,7 +58,115 @@ namespace Avalonia.Controls.UnitTests.Utils
HotKeyManager.SetHotKey(button, null);
Assert.Empty(tl.KeyBindings);
+ }
+ }
+
+ [Fact]
+ public void HotKeyManager_Should_Release_Reference_When_Control_Detached()
+ {
+ using (AvaloniaLocator.EnterScope())
+ {
+ var styler = new Mock();
+
+ AvaloniaLocator.CurrentMutable
+ .Bind().ToConstant(new WindowingPlatformMock())
+ .Bind().ToConstant(styler.Object);
+
+ var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
+
+ WeakReference reference = null;
+
+ var tl = new Window();
+ new Action(() =>
+ {
+ var button = new Button();
+ reference = new WeakReference(button, true);
+ tl.Content = button;
+ tl.Template = CreateWindowTemplate();
+ tl.ApplyTemplate();
+ tl.Presenter.ApplyTemplate();
+ HotKeyManager.SetHotKey(button, gesture1);
+
+ // Detach the button from the logical tree, so there is no reference to it
+ tl.Content = null;
+ tl.ApplyTemplate();
+ })();
+
+
+ // The button should be collected since it's detached from the listbox
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ Assert.Null(reference?.Target);
+ }
+ }
+
+ [Fact]
+ public void HotKeyManager_Should_Release_Reference_When_Control_In_Item_Template_Detached()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var styler = new Mock();
+
+ AvaloniaLocator.CurrentMutable
+ .Bind().ToConstant(new WindowingPlatformMock())
+ .Bind().ToConstant(styler.Object);
+
+ var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
+
+ var weakReferences = new List();
+ var tl = new Window { SizeToContent = SizeToContent.WidthAndHeight, IsVisible = true };
+ var lm = tl.LayoutManager;
+
+ var keyGestures = new AvaloniaList { gesture1 };
+ var listBox = new ListBox
+ {
+ Width = 100,
+ Height = 100,
+ VirtualizationMode = ItemVirtualizationMode.None,
+ // Create a button with binding to the KeyGesture in the template and add it to references list
+ ItemTemplate = new FuncDataTemplate(typeof(KeyGesture), (o, scope) =>
+ {
+ var keyGesture = o as KeyGesture;
+ var button = new Button
+ {
+ DataContext = keyGesture, [!Button.HotKeyProperty] = new Binding("")
+ };
+ weakReferences.Add(new WeakReference(button, true));
+ return button;
+ })
+ };
+ // Add the listbox and render it
+ tl.Content = listBox;
+ lm.ExecuteInitialLayoutPass();
+ listBox.Items = keyGestures;
+ lm.ExecuteLayoutPass();
+
+ // Let the button detach when clearing the source items
+ keyGestures.Clear();
+ lm.ExecuteLayoutPass();
+
+ // Add it again to double check,and render
+ keyGestures.Add(gesture1);
+ lm.ExecuteLayoutPass();
+
+ keyGestures.Clear();
+ lm.ExecuteLayoutPass();
+
+ // The button should be collected since it's detached from the listbox
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ Assert.True(weakReferences.Count > 0);
+ foreach (var weakReference in weakReferences)
+ {
+ Assert.Null(weakReference.Target);
+ }
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs
index 3b729e9cd8..4dada1313e 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
using Avalonia.Controls.Shapes;
+using Avalonia.Data;
using Avalonia.Markup.Xaml.Converters;
using Xunit;
@@ -7,6 +9,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
{
public class PointsListTypeConverterTests
{
+ static PointsListTypeConverterTests()
+ {
+ RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle);
+ }
+
[Theory]
[InlineData("1,2 3,4")]
[InlineData("1 2 3 4")]
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/IgnoredDirectivesTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/IgnoredDirectivesTests.cs
new file mode 100644
index 0000000000..de0d9e8bf1
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/IgnoredDirectivesTests.cs
@@ -0,0 +1,31 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.Xaml
+{
+ public class IgnoredDirectivesTests : XamlTestBase
+ {
+ [Fact]
+ public void Ignored_Directives_Should_Compile()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ const string xaml = @"
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var target = window.FindControl("target");
+
+ window.ApplyTemplate();
+ target.ApplyTemplate();
+
+ Assert.Equal("Foo", target.Text);
+ }
+ }
+ }
+}