Quellcode durchsuchen

Merge branch 'master' into feature/custom-cursors

Steven Kirk vor 4 Jahren
Ursprung
Commit
8f4d312ea3
50 geänderte Dateien mit 470 neuen und 182 gelöschten Zeilen
  1. 2 2
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 1 1
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 1 3
      .ncrunch/Avalonia.MicroCom.v3.ncrunchproject
  4. 3 0
      .ncrunch/Avalonia.Win32.v3.ncrunchproject
  5. 1 0
      .ncrunch/Direct3DInteropSample.v3.ncrunchproject
  6. 1 1
      build/ApiDiff.props
  7. 1 1
      build/SharedVersion.props
  8. 2 8
      native/Avalonia.Native/src/OSX/Screens.mm
  9. 1 0
      native/Avalonia.Native/src/OSX/common.h
  10. 7 3
      native/Avalonia.Native/src/OSX/main.mm
  11. 12 7
      native/Avalonia.Native/src/OSX/window.mm
  12. 0 2
      readme.md
  13. 8 6
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  14. 4 17
      samples/ControlCatalog.Android/MainActivity.cs
  15. 19 29
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  16. BIN
      samples/ControlCatalog.Android/Resources/drawable/Icon.png
  17. 13 0
      samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml
  18. 0 13
      samples/ControlCatalog.Android/Resources/layout/Main.axml
  19. 0 5
      samples/ControlCatalog.Android/Resources/values/Strings.xml
  20. 4 0
      samples/ControlCatalog.Android/Resources/values/colors.xml
  21. 17 0
      samples/ControlCatalog.Android/Resources/values/styles.xml
  22. 32 0
      samples/ControlCatalog.Android/SplashActivity.cs
  23. 7 9
      samples/ControlCatalog/Pages/ScreenPage.cs
  24. 16 3
      src/Android/Avalonia.Android/AndroidPlatform.cs
  25. 32 0
      src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
  26. 23 0
      src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
  27. 17 0
      src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
  28. 23 7
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  29. 7 18
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  30. 2 20
      src/Avalonia.Base/Data/IndexerBinding.cs
  31. 6 0
      src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs
  32. 0 3
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  33. 101 10
      src/Avalonia.Controls/Slider.cs
  34. 10 3
      src/Avalonia.Controls/ToolTipService.cs
  35. 1 1
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  36. 1 1
      src/Avalonia.Native/WindowImplBase.cs
  37. 3 1
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  38. 1 1
      src/Avalonia.OpenGL/GlInterface.cs
  39. 1 0
      src/Avalonia.Themes.Default/MenuItem.xaml
  40. 0 1
      src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
  41. 3 0
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  42. 1 0
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  43. 0 1
      src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml
  44. 1 1
      src/Avalonia.X11/X11Atoms.cs
  45. 2 2
      src/Avalonia.X11/X11Screens.cs
  46. 4 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
  47. 13 1
      src/Shared/PlatformSupport/DynLoader.cs
  48. 28 0
      tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
  49. 7 0
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs
  50. 31 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/IgnoredDirectivesTests.cs

+ 2 - 2
.github/ISSUE_TEMPLATE/bug_report.md

@@ -1,8 +1,8 @@
 ---
 name: Bug report
-about: Create a report to help us improve
+about: Create a report to help us improve Avalonia
 title: ''
-labels: ''
+labels: bug
 assignees: ''
 
 ---

+ 1 - 1
.github/ISSUE_TEMPLATE/feature_request.md

@@ -2,7 +2,7 @@
 name: Feature request
 about: Suggest an idea for this project
 title: ''
-labels: ''
+labels: enhancement
 assignees: ''
 
 ---

+ 1 - 3
.ncrunch/Avalonia.MicroCom.v3.ncrunchproject

@@ -1,5 +1,3 @@
 <ProjectConfiguration>
-  <Settings>
-    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
-  </Settings>
+  <Settings />
 </ProjectConfiguration>

+ 3 - 0
.ncrunch/Avalonia.Win32.v3.ncrunchproject

@@ -1,5 +1,8 @@
 <ProjectConfiguration>
   <Settings>
+    <AdditionalFilesToIncludeForProject>
+      <Value>..\..\tools\MicroComGenerator\bin\Debug\netcoreapp3.1\**.*</Value>
+    </AdditionalFilesToIncludeForProject>
     <HiddenComponentWarnings>
       <Value>MissingOrIgnoredProjectReference</Value>
     </HiddenComponentWarnings>

+ 1 - 0
.ncrunch/Direct3DInteropSample.v3.ncrunchproject

@@ -3,6 +3,7 @@
     <HiddenComponentWarnings>
       <Value>MissingOrIgnoredProjectReference</Value>
     </HiddenComponentWarnings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
     <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
   </Settings>
 </ProjectConfiguration>

+ 1 - 1
build/ApiDiff.props

@@ -1,6 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
-    <ApiContractPackageVersion>0.10.0-rc1</ApiContractPackageVersion>
+    <ApiContractPackageVersion>0.10.0</ApiContractPackageVersion>
     <NugetPackageName Condition="'$(PackageId)' != ''">$(PackageId)</NugetPackageName>
     <NugetPackageName Condition="'$(PackageId)' == ''">Avalonia</NugetPackageName>
   </PropertyGroup>

+ 1 - 1
build/SharedVersion.props

@@ -3,7 +3,7 @@
   <PropertyGroup>
     <Product>Avalonia</Product>
     <Version>0.10.999</Version>
-    <Copyright>Copyright 2020 &#169; The AvaloniaUI Project</Copyright>
+    <Copyright>Copyright 2021 &#169; The AvaloniaUI Project</Copyright>
     <PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
     <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>

+ 2 - 8
native/Avalonia.Native/src/OSX/Screens.mm

@@ -5,12 +5,6 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
     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];
             

+ 1 - 0
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__)

+ 7 - 3
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]);
+}

+ 12 - 7
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<AvnWindow>([self window]);
     
     if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
     {
-        auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
-        
-        if(window != nullptr)
+        if(trigerInputWhenDisabled)
         {
-            window->WindowEvents->GotInputWhenDisabled();
+            auto window = dynamic_cast<WindowImpl*>(_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;
     }

+ 0 - 2
readme.md

@@ -12,8 +12,6 @@ Avalonia is a cross-platform XAML-based UI framework providing a flexible stylin
 
 ([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
 
-> **Note:** The UI theme you see in the picture above is still work-in-progress and will be available in the upcoming Avalonia 0.10.0 release. However, you can connect to our nightly build feed and install latest pre-release versions of Avalonia NuGet packages, if you are willing to help out with the development and testing. See [Using nightly build feed](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) for more info.
-
 To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
 
 ## 🚀 Getting Started

+ 8 - 6
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@@ -71,21 +71,23 @@
     <Compile Include="MainActivity.cs" />
     <Compile Include="Resources\Resource.Designer.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="SplashActivity.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="Resources\AboutResources.txt" />
     <None Include="Assets\AboutAssets.txt" />
   </ItemGroup>
   <ItemGroup>
-    <AndroidResource Include="Resources\layout\Main.axml">
-      <SubType>Designer</SubType>
-    </AndroidResource>
+    <AndroidResource Include="Resources\drawable\splash_screen.xml" />
   </ItemGroup>
   <ItemGroup>
-    <AndroidResource Include="Resources\values\Strings.xml" />
+    <AndroidResource Include="Resources\values\colors.xml" />
+    <AndroidResource Include="Resources\values\styles.xml" />
   </ItemGroup>
   <ItemGroup>
-    <AndroidResource Include="Resources\drawable\Icon.png" />
+    <AndroidResource Include="..\..\build\Assets\Icon.png">
+      <Link>Resources\drawable\Icon.png</Link>
+    </AndroidResource>
   </ItemGroup>
   <ItemGroup>
     <None Include="Properties\AndroidManifest.xml" />
@@ -156,4 +158,4 @@
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
   <Import Project="..\..\build\AndroidWorkarounds.props" />
   <Import Project="..\..\build\LegacyProject.targets" />
-</Project>
+</Project>

+ 4 - 17
samples/ControlCatalog.Android/MainActivity.cs

@@ -1,31 +1,18 @@
-using System;
-using Android.App;
+using Android.App;
 using Android.OS;
 using Android.Content.PM;
 using Avalonia.Android;
-using Avalonia.Controls;
-using Avalonia.Controls.Templates;
-using Avalonia.Markup.Xaml;
-using Avalonia.Media;
-using Avalonia.Styling;
-using Avalonia.Themes.Default;
-using Avalonia;
 
 namespace ControlCatalog.Android
 {
-    [Activity(Label = "ControlCatalog.Android", MainLauncher = true, Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)]
+    [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)]
     public class MainActivity : AvaloniaActivity
     {
         protected override void OnCreate(Bundle savedInstanceState)
         {
-            if (Avalonia.Application.Current == null)           
-            {
-                AppBuilder.Configure<App>()
-                    .UseAndroid()
-                    .SetupWithoutStarting();
-                Content = new MainView();
-            }
             base.OnCreate(savedInstanceState);
+
+            Content = new MainView();
         }
     }
 }

+ 19 - 29
samples/ControlCatalog.Android/Resources/Resource.Designer.cs

@@ -40,69 +40,59 @@ namespace ControlCatalog.Android
 			}
 		}
 		
-		public partial class Drawable
+		public partial class Color
 		{
 			
 			// aapt resource value: 0x7F010000
-			public const int Icon = 2130771968;
+			public const int splash_background = 2130771968;
 			
-			static Drawable()
+			static Color()
 			{
 				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
 			}
 			
-			private Drawable()
+			private Color()
 			{
 			}
 		}
 		
-		public partial class Id
+		public partial class Drawable
 		{
 			
 			// aapt resource value: 0x7F020000
-			public const int MyButton = 2130837504;
+			public const int Icon = 2130837504;
+			
+			// aapt resource value: 0x7F020001
+			public const int splash_screen = 2130837505;
 			
-			static Id()
+			static Drawable()
 			{
 				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
 			}
 			
-			private Id()
+			private Drawable()
 			{
 			}
 		}
 		
-		public partial class Layout
+		public partial class Style
 		{
 			
 			// aapt resource value: 0x7F030000
-			public const int Main = 2130903040;
-			
-			static Layout()
-			{
-				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-			}
-			
-			private Layout()
-			{
-			}
-		}
-		
-		public partial class String
-		{
+			public const int MyTheme = 2130903040;
 			
-			// aapt resource value: 0x7F040000
-			public const int ApplicationName = 2130968576;
+			// aapt resource value: 0x7F030001
+			public const int MyTheme_NoActionBar = 2130903041;
 			
-			// aapt resource value: 0x7F040001
-			public const int Hello = 2130968577;
+			// aapt resource value: 0x7F030002
+			public const int MyTheme_Splash = 2130903042;
 			
-			static String()
+			static Style()
 			{
 				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
 			}
 			
-			private String()
+			private Style()
 			{
 			}
 		}

BIN
samples/ControlCatalog.Android/Resources/drawable/Icon.png


+ 13 - 0
samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+  <item>
+    <color android:color="@color/splash_background"/>
+  </item>
+
+  <item android:drawable="@drawable/icon"
+        android:width="120dp"
+        android:height="120dp"
+        android:gravity="center" />
+
+</layer-list>

+ 0 - 13
samples/ControlCatalog.Android/Resources/layout/Main.axml

@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    >
-<Button  
-    android:id="@+id/MyButton"
-    android:layout_width="match_parent" 
-    android:layout_height="wrap_content" 
-    android:text="@string/Hello"
-    />
-</LinearLayout>

+ 0 - 5
samples/ControlCatalog.Android/Resources/values/Strings.xml

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <string name="Hello">Hello World, Click Me!</string>
-    <string name="ApplicationName">ControlCatalog.Android</string>
-</resources>

+ 4 - 0
samples/ControlCatalog.Android/Resources/values/colors.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="splash_background">#FFFFFF</color>
+</resources>

+ 17 - 0
samples/ControlCatalog.Android/Resources/values/styles.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<resources>
+
+  <style name="MyTheme">
+  </style>
+
+  <style name="MyTheme.NoActionBar">
+    <item name="android:windowActionBar">false</item>
+    <item name="android:windowNoTitle">true</item>
+  </style>
+
+  <style name="MyTheme.Splash" parent ="MyTheme.NoActionBar">
+    <item name="android:windowBackground">@drawable/splash_screen</item>
+    <item name="android:windowContentOverlay">@null</item>
+  </style>
+
+</resources>

+ 32 - 0
samples/ControlCatalog.Android/SplashActivity.cs

@@ -0,0 +1,32 @@
+using Android.App;
+using Android.Content;
+using Android.OS;
+using Application = Android.App.Application;
+
+using Avalonia;
+
+namespace ControlCatalog.Android
+{
+    [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
+    public class SplashActivity : Activity
+    {
+        protected override void OnCreate(Bundle savedInstanceState)
+        {
+            base.OnCreate(savedInstanceState);
+        }
+
+        protected override void OnResume()
+        {
+            base.OnResume();
+
+            if (Avalonia.Application.Current == null)
+            {
+                AppBuilder.Configure<App>()
+                    .UseAndroid()
+                    .SetupWithoutStarting();
+            }
+
+            StartActivity(new Intent(Application.Context, typeof(MainActivity)));
+        }
+    }
+}

+ 7 - 9
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));
         }
     }
 }

+ 16 - 3
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -1,11 +1,13 @@
 using System;
+
+using Avalonia.Android;
 using Avalonia.Android.Platform;
 using Avalonia.Android.Platform.Input;
-using Avalonia.Android.Platform.SkiaPlatform;
 using Avalonia.Controls;
 using Avalonia.Controls.Platform;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
+using Avalonia.OpenGL.Egl;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Shared.PlatformSupport;
@@ -17,7 +19,8 @@ namespace Avalonia
     {
         public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
         {
-            builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.ApplicationType), "Android");
+            var options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
+            builder.UseWindowingSubsystem(() => AndroidPlatform.Initialize(builder.ApplicationType, options), "Android");
             builder.UseSkia();
             return builder;
         }
@@ -41,7 +44,7 @@ namespace Avalonia.Android
             _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;
         }
 
-        public static void Initialize(Type appType)
+        public static void Initialize(Type appType, AndroidPlatformOptions options)
         {
             AvaloniaLocator.CurrentMutable
                 .Bind<IClipboard>().ToTransient<ClipboardImpl>()
@@ -60,6 +63,11 @@ namespace Avalonia.Android
             SkiaPlatform.Initialize();
             ((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext)
                 .RegisterActivityLifecycleCallbacks(new ActivityTracker());
+
+            if (options.UseGpu)
+            {
+                EglPlatformOpenGlInterface.TryInitialize();
+            }
         }
 
         public IWindowImpl CreateWindow()
@@ -72,4 +80,9 @@ namespace Avalonia.Android
             throw new NotSupportedException();
         }
     }
+
+    public sealed class AndroidPlatformOptions
+    {
+        public bool UseGpu { get; set; } = true;
+    }
 }

+ 32 - 0
src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs

@@ -0,0 +1,32 @@
+using System.Linq;
+
+using Avalonia.OpenGL.Egl;
+using Avalonia.OpenGL.Surfaces;
+
+namespace Avalonia.Android.OpenGL
+{
+    internal sealed class GlPlatformSurface : EglGlPlatformSurfaceBase
+    {
+        private readonly EglPlatformOpenGlInterface _egl;
+        private readonly IEglWindowGlPlatformSurfaceInfo _info;
+
+        private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info)
+        {
+            _egl = egl;
+            _info = info;
+        }
+
+        public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() =>
+            new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle));
+
+        public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
+        {
+            if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl)
+            {
+                return new GlPlatformSurface(egl, info);
+            }
+
+            return null;
+        }
+    }
+}

+ 23 - 0
src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs

@@ -0,0 +1,23 @@
+using Avalonia.OpenGL.Egl;
+using Avalonia.OpenGL.Surfaces;
+
+namespace Avalonia.Android.OpenGL
+{
+    internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase
+    {
+        private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
+        private readonly EglSurface _surface;
+
+        public GlRenderTarget(
+            EglPlatformOpenGlInterface egl,
+            EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
+            EglSurface surface)
+            : base(egl)
+        {
+            _info = info;
+            _surface = surface;
+        }
+
+        public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info);
+    }
+}

+ 17 - 0
src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs

@@ -0,0 +1,17 @@
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+
+namespace Avalonia.Android.Platform.SkiaPlatform
+{
+    internal sealed class FramebufferManager : IFramebufferPlatformSurface
+    {
+        private readonly TopLevelImpl _topLevel;
+
+        public FramebufferManager(TopLevelImpl topLevel)
+        {
+            _topLevel = topLevel;
+        }
+
+        public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface);
+    }
+}

+ 23 - 7
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -2,7 +2,10 @@ using System;
 using System.Collections.Generic;
 using Android.Content;
 using Android.Graphics;
+using Android.Runtime;
 using Android.Views;
+
+using Avalonia.Android.OpenGL;
 using Avalonia.Android.Platform.Input;
 using Avalonia.Android.Platform.Specific;
 using Avalonia.Android.Platform.Specific.Helpers;
@@ -10,13 +13,18 @@ using Avalonia.Controls;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
+using Avalonia.OpenGL.Egl;
+using Avalonia.OpenGL.Surfaces;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
-    class TopLevelImpl : IAndroidView, ITopLevelImpl,  IFramebufferPlatformSurface
+    class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo
     {
+        private readonly IGlPlatformSurface _gl;
+        private readonly IFramebufferPlatformSurface _framebuffer;
+
         private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
         private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
 
@@ -29,7 +37,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
                 p => GetAvaloniaPointFromEvent(p));
 
-            Surfaces = new object[] { this };
+            _gl = GlPlatformSurface.TryCreate(this);
+            _framebuffer = new FramebufferManager(this);
 
             MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
                 _view.Resources.DisplayMetrics.HeightPixels);
@@ -48,7 +57,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
                 _keyboardHelper.HandleEvents = _handleEvents;
             }
         }
-        
+
         public virtual Point GetAvaloniaPointFromEvent(MotionEvent e) => new Point(e.GetX(), e.GetY());
 
         public IInputRoot InputRoot { get; private set; }
@@ -63,7 +72,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             }
             set
             {
-                
+
             }
         }
 
@@ -83,9 +92,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
         public View View => _view;
 
+        internal InvalidationAwareSurfaceView InternalView => _view;
+
         public IPlatformHandle Handle => _view;
 
-        public IEnumerable<object> Surfaces { get; }
+        public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer };
 
         public IRenderer CreateRenderer(IRenderRoot root)
         {
@@ -96,7 +107,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         {
             _view.Visibility = ViewStates.Invisible;
         }
-        
+
         public void Invalidate(Rect rect)
         {
             if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate();
@@ -203,7 +214,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
 
-        ILockedFramebuffer IFramebufferPlatformSurface.Lock() => new AndroidFramebuffer(_view.Holder.Surface);
+        IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle =>
+            AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, _view.Holder.Surface.Handle);
+
+        public PixelSize Size => new PixelSize(_view.Holder.SurfaceFrame.Width(), _view.Holder.SurfaceFrame.Height());
+
+        public double Scaling => RenderScaling;
 
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
         {

+ 7 - 18
src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs

@@ -63,7 +63,7 @@ namespace Avalonia.Data.Converters
             }
         }
 
-        void OnPropertyChanged(object sender,PropertyChangedEventArgs args)
+        void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
         {
             if (string.IsNullOrWhiteSpace(args.PropertyName)
                                || dependencyProperties?.Contains(args.PropertyName) == true)
@@ -88,12 +88,7 @@ namespace Avalonia.Data.Converters
 
             var parameter = Expression.Parameter(typeof(object), "parameter");
 
-            var instance = Expression.Convert
-            (
-                Expression.Constant(target),
-                method.DeclaringType
-            );
-
+            var instance = ConvertTarget(target, method);
 
             var call = Expression.Call
             (
@@ -114,11 +109,7 @@ namespace Avalonia.Data.Converters
 
             var parameter = Expression.Parameter(typeof(object), "parameter");
 
-            var instance = Expression.Convert
-            (
-                Expression.Constant(target),
-                method.DeclaringType
-            );
+            var instance = ConvertTarget(target, method);
 
             Expression body;
 
@@ -167,11 +158,7 @@ namespace Avalonia.Data.Converters
             , System.Reflection.MethodInfo method)
         {
             var parameter = Expression.Parameter(typeof(object), "parameter");
-            var instance = Expression.Convert
-            (
-                Expression.Constant(target),
-                method.DeclaringType
-            );
+            var instance = ConvertTarget(target, method);
             var call = Expression.Call
             (
                 instance,
@@ -183,6 +170,8 @@ namespace Avalonia.Data.Converters
                 .Compile();
         }
 
+        private static Expression? ConvertTarget(object? target, MethodInfo method) =>
+            target is null ? null : Expression.Convert(Expression.Constant(target), method.DeclaringType);
 
         internal class WeakPropertyChangedProxy
         {
@@ -224,7 +213,7 @@ namespace Avalonia.Data.Converters
                 else
                     Unsubscribe();
             }
-           
+
         }
     }
 }

+ 2 - 20
src/Avalonia.Base/Data/IndexerBinding.cs

@@ -1,6 +1,4 @@
-using System;
-
-namespace Avalonia.Data
+namespace Avalonia.Data
 {
     public class IndexerBinding : IBinding
     {
@@ -24,23 +22,7 @@ namespace Avalonia.Data
             object anchor = null,
             bool enableDataValidation = false)
         {
-            var mode = Mode == BindingMode.Default ?
-                targetProperty.GetMetadata(target.GetType()).DefaultBindingMode :
-                Mode;
-
-            switch (mode)
-            {
-                case BindingMode.OneTime:
-                    return InstancedBinding.OneTime(Source.GetObservable(Property));
-                case BindingMode.OneWay:
-                    return InstancedBinding.OneWay(Source.GetObservable(Property));
-                case BindingMode.OneWayToSource:
-                    return InstancedBinding.OneWayToSource(Source.GetSubject(Property));
-                case BindingMode.TwoWay:
-                    return InstancedBinding.TwoWay(Source.GetSubject(Property));
-                default:
-                    throw new NotSupportedException("Unsupported BindingMode.");
-            }
+            return new InstancedBinding(Source.GetSubject(Property), Mode, BindingPriority.LocalValue);
         }
     }
 }

+ 6 - 0
src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs

@@ -2,6 +2,7 @@ using System;
 using System.Linq;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
+using Avalonia.Media;
 using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
@@ -17,6 +18,11 @@ namespace Avalonia.Controls.Primitives
     {
         public IInputElement? InputPassThroughElement { get; set; }
 
+        static LightDismissOverlayLayer()
+        {
+            BackgroundProperty.OverrideDefaultValue<LightDismissOverlayLayer>(Brushes.Transparent);
+        }
+
         /// <summary>
         /// Returns the light dismiss overlay for a specified visual.
         /// </summary>

+ 0 - 3
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@@ -67,14 +67,11 @@ namespace Avalonia.Controls.Primitives
         {
             get
             {
-                if (IsPopup)
-                    return null;
                 var rv = FindLayer<LightDismissOverlayLayer>();
                 if (rv == null)
                 {
                     rv = new LightDismissOverlayLayer
                     {
-                        Background = Brushes.Transparent,
                         IsVisible = false
                     };
 

+ 101 - 10
src/Avalonia.Controls/Slider.cs

@@ -11,7 +11,6 @@ using Avalonia.Utilities;
 
 namespace Avalonia.Controls
 {
-
     /// <summary>
     /// Enum which describes how to position the ticks in a <see cref="Slider"/>.
     /// </summary>
@@ -84,6 +83,9 @@ namespace Avalonia.Controls
         private IDisposable _increaseButtonSubscription;
         private IDisposable _increaseButtonReleaseDispose;
         private IDisposable _pointerMovedDispose;
+        private IDisposable _trackOnKeyDownDispose;
+
+        private const double Tolerance = 0.0001;
 
         /// <summary>
         /// Initializes static members of the <see cref="Slider"/> class. 
@@ -95,7 +97,7 @@ namespace Avalonia.Controls
             Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
             Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e),
                 RoutingStrategies.Bubble);
-            
+
             ValueProperty.OverrideMetadata<Slider>(new DirectPropertyMetadata<double>(enableDataValidation: true));
         }
 
@@ -157,13 +159,14 @@ namespace Avalonia.Controls
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             base.OnApplyTemplate(e);
-            
+
             _decreaseButtonPressDispose?.Dispose();
             _decreaseButtonReleaseDispose?.Dispose();
             _increaseButtonSubscription?.Dispose();
             _increaseButtonReleaseDispose?.Dispose();
             _pointerMovedDispose?.Dispose();
-
+            _trackOnKeyDownDispose?.Dispose();
+            
             _decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
             _track = e.NameScope.Find<Track>("PART_Track");
             _increaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
@@ -171,6 +174,7 @@ namespace Avalonia.Controls
             if (_track != null)
             {
                 _track.IsThumbDragHandled = true;
+                _trackOnKeyDownDispose = _track.AddDisposableHandler(KeyDownEvent, TrackOnKeyDown);
             }
 
             if (_decreaseButton != null)
@@ -188,6 +192,94 @@ namespace Avalonia.Controls
             _pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel);
         }
 
+        private void TrackOnKeyDown(object sender, KeyEventArgs e)
+        {
+            if (e.KeyModifiers != KeyModifiers.None) return;
+
+            switch (e.Key)
+            {
+                case Key.Left:
+                    MoveToNextTick(-SmallChange);
+                    break;
+
+                case Key.Right:
+                    MoveToNextTick(SmallChange);
+                    break;
+
+                case Key.PageUp:
+                    MoveToNextTick(-LargeChange);
+                    break;
+
+                case Key.PageDown:
+                    MoveToNextTick(LargeChange);
+                    break;
+
+                case Key.Home:
+                    Value = Minimum;
+                    break;
+
+                case Key.End:
+                    Value = Maximum;
+                    break;
+            }
+        }
+            
+        private void MoveToNextTick(double direction)
+        {
+            if (direction == 0.0) return;
+
+            var value = Value;
+
+            // Find the next value by snapping
+            var next = SnapToTick(Math.Max(Minimum, Math.Min(Maximum, value + direction)));
+
+            var greaterThan = direction > 0; //search for the next tick greater than value?
+
+            // If the snapping brought us back to value, find the next tick point
+            if (Math.Abs(next - value) < Tolerance
+                && !(greaterThan && Math.Abs(value - Maximum) < Tolerance) // Stop if searching up if already at Max
+                && !(!greaterThan && Math.Abs(value - Minimum) < Tolerance)) // Stop if searching down if already at Min
+            {
+                var ticks = Ticks;
+
+                // If ticks collection is available, use it.
+                // Note that ticks may be unsorted.
+                if (ticks != null && ticks.Count > 0)
+                {
+                    foreach (var tick in ticks)
+                    {
+                        // Find the smallest tick greater than value or the largest tick less than value
+                        if (greaterThan && MathUtilities.GreaterThan(tick, value) &&
+                            (MathUtilities.LessThan(tick, next) || Math.Abs(next - value) < Tolerance)
+                            || !greaterThan && MathUtilities.LessThan(tick, value) &&
+                            (MathUtilities.GreaterThan(tick, next) || Math.Abs(next - value) < Tolerance))
+                        {
+                            next = tick;
+                        }
+                    }
+                }
+                else if (MathUtilities.GreaterThan(TickFrequency, 0.0))
+                {
+                    // Find the current tick we are at
+                    var tickNumber = Math.Round((value - Minimum) / TickFrequency);
+
+                    if (greaterThan)
+                        tickNumber += 1.0;
+                    else
+                        tickNumber -= 1.0;
+
+                    next = Minimum + tickNumber * TickFrequency;
+                }
+            }
+
+
+            // Update if we've found a better value
+            if (Math.Abs(next - value) > Tolerance)
+            {
+                Value = next;
+            }
+        }
+
         private void TrackMoved(object sender, PointerEventArgs e)
         {
             if (_isDragging)
@@ -272,19 +364,18 @@ namespace Avalonia.Controls
         {
             if (IsSnapToTickEnabled)
             {
-                double previous = Minimum;
-                double next = Maximum;
+                var previous = Minimum;
+                var next = Maximum;
 
                 // This property is rarely set so let's try to avoid the GetValue
                 var ticks = Ticks;
 
                 // If ticks collection is available, use it.
                 // Note that ticks may be unsorted.
-                if ((ticks != null) && (ticks.Count > 0))
+                if (ticks != null && ticks.Count > 0)
                 {
-                    for (int i = 0; i < ticks.Count; i++)
+                    foreach (var tick in ticks)
                     {
-                        double tick = ticks[i];
                         if (MathUtilities.AreClose(tick, value))
                         {
                             return value;
@@ -302,7 +393,7 @@ namespace Avalonia.Controls
                 }
                 else if (MathUtilities.GreaterThan(TickFrequency, 0.0))
                 {
-                    previous = Minimum + (Math.Round(((value - Minimum) / TickFrequency)) * TickFrequency);
+                    previous = Minimum + Math.Round((value - Minimum) / TickFrequency) * TickFrequency;
                     next = Math.Min(Maximum, previous + TickFrequency);
                 }
 

+ 10 - 3
src/Avalonia.Controls/ToolTipService.cs

@@ -38,9 +38,16 @@ namespace Avalonia.Controls
 
             if (ToolTip.GetIsOpen(control) && e.NewValue != e.OldValue && !(e.NewValue is ToolTip))
             {
-                var tip = control.GetValue(ToolTip.ToolTipProperty);
-
-                tip.Content = e.NewValue;
+                if (e.NewValue is null)
+                {
+                    Close(control);
+                }
+                else
+                {
+                    var tip = control.GetValue(ToolTip.ToolTipProperty);
+
+                    tip.Content = e.NewValue;
+                }
             }
         }
 

+ 1 - 1
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@@ -99,7 +99,7 @@
       </StackPanel>
      </StackPanel> 
     <StackPanel VerticalAlignment="Bottom" Margin="10">
-      <TextBlock Text="© 2020 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
+      <TextBlock Text="© 2021 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
     </StackPanel>
   </Grid>
 </Window>

+ 1 - 1
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)

+ 3 - 1
src/Avalonia.OpenGL/Egl/EglInterface.cs

@@ -30,8 +30,10 @@ namespace Avalonia.OpenGL.Egl
         static Func<string, IntPtr> Load()
         {
             var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem;
-            if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android)
+            if(os == OperatingSystemType.Linux)
                 return Load("libEGL.so.1");
+            if (os == OperatingSystemType.Android)
+                return Load("libEGL.so");
 
             throw new PlatformNotSupportedException();
         }

+ 1 - 1
src/Avalonia.OpenGL/GlInterface.cs

@@ -128,7 +128,7 @@ namespace Avalonia.OpenGL
             int dstY1,
             int mask,
             int filter);
-        [GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0)]
+        [GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0), GlOptionalEntryPoint]
         public GlBlitFramebuffer BlitFramebuffer { get; }
         
         public delegate void GlGenRenderbuffers(int count, int[] res);

+ 1 - 0
src/Avalonia.Themes.Default/MenuItem.xaml

@@ -60,6 +60,7 @@
             <Popup Name="PART_Popup"
                    PlacementMode="Right"
                    IsLightDismissEnabled="True"
+                   OverlayInputPassThroughElement="{Binding $parent[MenuItem]}"
                    IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
               <Border Background="{TemplateBinding Background}"
                       BorderBrush="{DynamicResource ThemeBorderMidBrush}"

+ 0 - 1
src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

@@ -11,7 +11,6 @@
     <Setter Property="HorizontalContentAlignment" Value="Left" />
     <Setter Property="VerticalContentAlignment" Value="Center" />    
     <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
-    <Setter Property="MinWidth" Value="120" />
     <Setter Property="MinHeight" Value="32" />
     <!--<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
     <Setter Property="FocusVisualMargin" Value="-7,-3,-7,-3" />-->

+ 3 - 0
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@@ -26,6 +26,9 @@
       </ControlTemplate>
     </Setter>
   </Style>
+  <Style Selector="Expander[IsExpanded=true] /template/ ToggleButton#PART_toggle /template/ ContentPresenter#PART_ContentPresenter">
+    <Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForeground}"/>
+  </Style>
   <Style Selector="Expander[ExpandDirection=Up]">
     <Setter Property="Template">
       <ControlTemplate>

+ 1 - 0
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@@ -112,6 +112,7 @@
           <Popup Name="PART_Popup"
                  WindowManagerAddShadowHint="False"
                  PlacementMode="Right"
+                 OverlayInputPassThroughElement="{Binding $parent[MenuItem]}"
                  HorizontalOffset="{DynamicResource MenuFlyoutSubItemPopupHorizontalOffset}"
                  IsLightDismissEnabled="True"
                  IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">

+ 0 - 1
src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml

@@ -19,7 +19,6 @@
     <Setter Property="HorizontalContentAlignment" Value="Left" />
     <Setter Property="VerticalContentAlignment" Value="Center" />    
     <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
-    <Setter Property="MinWidth" Value="120" />
     <Setter Property="Template">
       <ControlTemplate TargetType="RadioButton">
         <Border Name="RootBorder"

+ 1 - 1
src/Avalonia.X11/X11Atoms.cs

@@ -114,7 +114,7 @@ namespace Avalonia.X11
         public readonly IntPtr XA_WM_CLASS = (IntPtr)67;
         public readonly IntPtr XA_WM_TRANSIENT_FOR = (IntPtr)68;
 
-        public readonly IntPtr RR_PROPERTY_RANDR_EDID = (IntPtr)82;
+        public readonly IntPtr EDID;
 
         public readonly IntPtr WM_PROTOCOLS;
         public readonly IntPtr WM_DELETE_WINDOW;

+ 2 - 2
src/Avalonia.X11/X11Screens.cs

@@ -91,12 +91,12 @@ namespace Avalonia.X11
                 var hasEDID = false;
                 for(var pc = 0; pc < propertyCount; pc++)
                 {
-                    if(properties[pc] == _x11.Atoms.RR_PROPERTY_RANDR_EDID)
+                    if(properties[pc] == _x11.Atoms.EDID)
                         hasEDID = true;
                 }
                 if(!hasEDID)
                     return null;
-                XRRGetOutputProperty(_x11.Display, rrOutput, _x11.Atoms.RR_PROPERTY_RANDR_EDID, 0, EDIDStructureLength, false, false, _x11.Atoms.AnyPropertyType, out IntPtr actualType, out int actualFormat, out int bytesAfter, out _, out IntPtr prop);
+                XRRGetOutputProperty(_x11.Display, rrOutput, _x11.Atoms.EDID, 0, EDIDStructureLength, false, false, _x11.Atoms.AnyPropertyType, out IntPtr actualType, out int actualFormat, out int bytesAfter, out _, out IntPtr prop);
                 if(actualType != _x11.Atoms.XA_INTEGER)
                     return null;
                 if(actualFormat != 8) // Expecting an byte array

+ 4 - 1
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);
                     }
                 }

+ 13 - 1
src/Shared/PlatformSupport/DynLoader.cs

@@ -11,13 +11,25 @@ namespace Avalonia.Shared.PlatformSupport
         // ReSharper disable InconsistentNaming
         static class LinuxImports
         {
+#if __ANDROID__
+            [DllImport("libdl.so")]
+#else
             [DllImport("libdl.so.2")]
+#endif
             private static extern IntPtr dlopen(string path, int flags);
 
+#if __ANDROID__
+            [DllImport("libdl.so")]
+#else
             [DllImport("libdl.so.2")]
+#endif
             private static extern IntPtr dlsym(IntPtr handle, string symbol);
 
+#if __ANDROID__
+            [DllImport("libdl.so")]
+#else
             [DllImport("libdl.so.2")]
+#endif
             private static extern IntPtr dlerror();
 
             public static void Init()
@@ -27,7 +39,7 @@ namespace Avalonia.Shared.PlatformSupport
                 DlError = dlerror;
             }
         }
-        
+
         static class OsXImports
         {
             

+ 28 - 0
tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

@@ -270,6 +270,34 @@ namespace Avalonia.Controls.UnitTests
                 Assert.Empty(toolTip.Classes);
             }
         }
+
+        [Fact]
+        public void Should_Close_On_Null_Tip()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var window = new Window();
+
+                var target = new Decorator()
+                {
+                    [ToolTip.TipProperty] = "Tip",
+                    [ToolTip.ShowDelayProperty] = 0
+                };
+
+                window.Content = target;
+
+                window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
+
+                _mouseHelper.Enter(target);
+
+                Assert.True(ToolTip.GetIsOpen(target));
+
+                target[ToolTip.TipProperty] = null;
+
+                Assert.False(ToolTip.GetIsOpen(target));
+            }
+        }
     }
 
     internal class ToolTipViewModel

+ 7 - 0
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")]

+ 31 - 0
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 = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:sys='clr-namespace:System;assembly=netstandard'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
+    <TextBlock x:Name='target' x:FieldModifier='Public' Text='Foo'/>
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var target = window.FindControl<TextBlock>("target");
+
+                window.ApplyTemplate();
+                target.ApplyTemplate();
+
+                Assert.Equal("Foo", target.Text);
+            }
+        }
+    }
+}