Browse Source

Merge branch 'master' into nullable

pull/5326/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
6fcce731fa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 2
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 4
      .ncrunch/Avalonia.MicroCom.v3.ncrunchproject
  4. 3
      .ncrunch/Avalonia.Win32.v3.ncrunchproject
  5. 1
      .ncrunch/Direct3DInteropSample.v3.ncrunchproject
  6. 2
      build/ApiDiff.props
  7. 2
      build/SharedVersion.props
  8. 10
      native/Avalonia.Native/src/OSX/Screens.mm
  9. 1
      native/Avalonia.Native/src/OSX/common.h
  10. 10
      native/Avalonia.Native/src/OSX/main.mm
  11. 19
      native/Avalonia.Native/src/OSX/window.mm
  12. 2
      readme.md
  13. 14
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  14. 21
      samples/ControlCatalog.Android/MainActivity.cs
  15. 48
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  16. BIN
      samples/ControlCatalog.Android/Resources/drawable/Icon.png
  17. 13
      samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml
  18. 13
      samples/ControlCatalog.Android/Resources/layout/Main.axml
  19. 5
      samples/ControlCatalog.Android/Resources/values/Strings.xml
  20. 4
      samples/ControlCatalog.Android/Resources/values/colors.xml
  21. 17
      samples/ControlCatalog.Android/Resources/values/styles.xml
  22. 32
      samples/ControlCatalog.Android/SplashActivity.cs
  23. 16
      samples/ControlCatalog/Pages/ScreenPage.cs
  24. 19
      src/Android/Avalonia.Android/AndroidPlatform.cs
  25. 32
      src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
  26. 23
      src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
  27. 17
      src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
  28. 30
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  29. 25
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  30. 22
      src/Avalonia.Base/Data/IndexerBinding.cs
  31. 13
      src/Avalonia.Controls/Button.cs
  32. 13
      src/Avalonia.Controls/MenuItem.cs
  33. 6
      src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs
  34. 3
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  35. 111
      src/Avalonia.Controls/Slider.cs
  36. 13
      src/Avalonia.Controls/ToolTipService.cs
  37. 2
      src/Avalonia.Controls/Utils/AncestorFinder.cs
  38. 2
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  39. 2
      src/Avalonia.Native/WindowImplBase.cs
  40. 4
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  41. 2
      src/Avalonia.OpenGL/GlInterface.cs
  42. 1
      src/Avalonia.Themes.Default/MenuItem.xaml
  43. 1
      src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
  44. 3
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  45. 1
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  46. 1
      src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml
  47. 2
      src/Avalonia.X11/X11Atoms.cs
  48. 4
      src/Avalonia.X11/X11Screens.cs
  49. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
  50. 14
      src/Shared/PlatformSupport/DynLoader.cs
  51. 28
      tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
  52. 115
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  53. 7
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs
  54. 31
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/IgnoredDirectivesTests.cs

4
.github/ISSUE_TEMPLATE/bug_report.md

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

2
.github/ISSUE_TEMPLATE/feature_request.md

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

4
.ncrunch/Avalonia.MicroCom.v3.ncrunchproject

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

3
.ncrunch/Avalonia.Win32.v3.ncrunchproject

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

1
.ncrunch/Direct3DInteropSample.v3.ncrunchproject

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

2
build/ApiDiff.props

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

2
build/SharedVersion.props

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Avalonia</Product> <Product>Avalonia</Product>
<Version>0.10.999</Version> <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> <PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl> <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>

10
native/Avalonia.Native/src/OSX/Screens.mm

@ -5,12 +5,6 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
public: public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
private:
CGFloat PrimaryDisplayHeight()
{
return NSMaxY([[[NSScreen screens] firstObject] frame]);
}
public: public:
virtual HRESULT GetScreenCount (int* ret) override virtual HRESULT GetScreenCount (int* ret) override
{ {
@ -36,12 +30,12 @@ public:
ret->Bounds.Height = [screen frame].size.height; ret->Bounds.Height = [screen frame].size.height;
ret->Bounds.Width = [screen frame].size.width; ret->Bounds.Width = [screen frame].size.width;
ret->Bounds.X = [screen frame].origin.x; 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.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width; ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->WorkingArea.X = [screen visibleFrame].origin.x; 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]; ret->PixelDensity = [screen backingScaleFactor];

1
native/Avalonia.Native/src/OSX/common.h

@ -34,6 +34,7 @@ extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p); extern NSPoint ToNSPoint (AvnPoint p);
extern AvnPoint ToAvnPoint (NSPoint p); extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p); extern AvnPoint ConvertPointY (AvnPoint p);
extern CGFloat PrimaryDisplayHeight();
extern NSSize ToNSSize (AvnSize s); extern NSSize ToNSSize (AvnSize s);
#ifdef DEBUG #ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__) #define NSDebugLog(...) NSLog(__VA_ARGS__)

10
native/Avalonia.Native/src/OSX/main.mm

@ -299,10 +299,14 @@ AvnPoint ToAvnPoint (NSPoint p)
AvnPoint ConvertPointY (AvnPoint 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 = primaryDisplayHeight - p.Y;
p.Y = t - p.Y;
return p; return p;
} }
CGFloat PrimaryDisplayHeight()
{
return NSMaxY([[[NSScreen screens] firstObject] frame]);
}

19
native/Avalonia.Native/src/OSX/window.mm

@ -1391,17 +1391,20 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[super viewDidChangeBackingProperties]; [super viewDidChangeBackingProperties];
} }
- (bool) ignoreUserInput - (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
{ {
auto parentWindow = objc_cast<AvnWindow>([self window]); auto parentWindow = objc_cast<AvnWindow>([self window]);
if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents]) if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
{ {
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw()); if(trigerInputWhenDisabled)
if(window != nullptr)
{ {
window->WindowEvents->GotInputWhenDisabled(); auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(window != nullptr)
{
window->WindowEvents->GotInputWhenDisabled();
}
} }
return TRUE; return TRUE;
@ -1412,7 +1415,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type - (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{ {
if([self ignoreUserInput]) bool triggerInputWhenDisabled = type != Move;
if([self ignoreUserInput: triggerInputWhenDisabled])
{ {
return; return;
} }
@ -1578,7 +1583,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type - (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
{ {
if([self ignoreUserInput]) if([self ignoreUserInput: false])
{ {
return; return;
} }

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)) ([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! 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 ## 🚀 Getting Started

14
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

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

21
samples/ControlCatalog.Android/MainActivity.cs

@ -1,31 +1,18 @@
using System; using Android.App;
using Android.App;
using Android.OS; using Android.OS;
using Android.Content.PM; using Android.Content.PM;
using Avalonia.Android; 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 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 public class MainActivity : AvaloniaActivity
{ {
protected override void OnCreate(Bundle savedInstanceState) protected override void OnCreate(Bundle savedInstanceState)
{ {
if (Avalonia.Application.Current == null)
{
AppBuilder.Configure<App>()
.UseAndroid()
.SetupWithoutStarting();
Content = new MainView();
}
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);
Content = new MainView();
} }
} }
} }

48
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 // aapt resource value: 0x7F010000
public const int Icon = 2130771968; public const int splash_background = 2130771968;
static Drawable() static Color()
{ {
global::Android.Runtime.ResourceIdManager.UpdateIdValues(); global::Android.Runtime.ResourceIdManager.UpdateIdValues();
} }
private Drawable() private Color()
{ {
} }
} }
public partial class Id public partial class Drawable
{ {
// aapt resource value: 0x7F020000 // 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(); global::Android.Runtime.ResourceIdManager.UpdateIdValues();
} }
private Id() private Drawable()
{ {
} }
} }
public partial class Layout public partial class Style
{ {
// aapt resource value: 0x7F030000 // aapt resource value: 0x7F030000
public const int Main = 2130903040; public const int MyTheme = 2130903040;
static Layout()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Layout()
{
}
}
public partial class String
{
// aapt resource value: 0x7F040000 // aapt resource value: 0x7F030001
public const int ApplicationName = 2130968576; public const int MyTheme_NoActionBar = 2130903041;
// aapt resource value: 0x7F040001 // aapt resource value: 0x7F030002
public const int Hello = 2130968577; public const int MyTheme_Splash = 2130903042;
static String() static Style()
{ {
global::Android.Runtime.ResourceIdManager.UpdateIdValues(); global::Android.Runtime.ResourceIdManager.UpdateIdValues();
} }
private String() private Style()
{ {
} }
} }

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

13
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>

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>

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

16
samples/ControlCatalog/Pages/ScreenPage.cs

@ -29,7 +29,7 @@ namespace ControlCatalog.Pages
var screens = w.Screens.All; var screens = w.Screens.All;
var scaling = ((IRenderRoot)w).RenderScaling; var scaling = ((IRenderRoot)w).RenderScaling;
var drawBrush = Brushes.Green; var drawBrush = Brushes.Black;
Pen p = new Pen(drawBrush); Pen p = new Pen(drawBrush);
if (screens != null) if (screens != null)
foreach (Screen screen in screens) foreach (Screen screen in screens)
@ -45,18 +45,16 @@ namespace ControlCatalog.Pages
screen.Bounds.Height / 10f); screen.Bounds.Height / 10f);
Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f, Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
screen.WorkingArea.Height / 10f); screen.WorkingArea.Height / 10f);
context.DrawRectangle(p, boundsRect); context.DrawRectangle(p, boundsRect);
context.DrawRectangle(p, workingAreaRect); 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); 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); context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
text.Text = $"Scaling: {screen.PixelDensity * 100}%"; 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.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));
} }
} }
} }

19
src/Android/Avalonia.Android/AndroidPlatform.cs

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

32
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
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
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);
}
}

30
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -2,7 +2,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Runtime;
using Android.Views; using Android.Views;
using Avalonia.Android.OpenGL;
using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Specific.Helpers;
@ -10,13 +13,18 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform 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 AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper; private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
@ -29,7 +37,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot, _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
p => GetAvaloniaPointFromEvent(p)); p => GetAvaloniaPointFromEvent(p));
Surfaces = new object[] { this }; _gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this);
MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels, MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels); _view.Resources.DisplayMetrics.HeightPixels);
@ -48,7 +57,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_keyboardHelper.HandleEvents = _handleEvents; _keyboardHelper.HandleEvents = _handleEvents;
} }
} }
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e) => new Point(e.GetX(), e.GetY()); public virtual Point GetAvaloniaPointFromEvent(MotionEvent e) => new Point(e.GetX(), e.GetY());
public IInputRoot InputRoot { get; private set; } public IInputRoot InputRoot { get; private set; }
@ -63,7 +72,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
} }
set set
{ {
} }
} }
@ -83,9 +92,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public View View => _view; public View View => _view;
internal InvalidationAwareSurfaceView InternalView => _view;
public IPlatformHandle Handle => _view; public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces { get; } public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer };
public IRenderer CreateRenderer(IRenderRoot root) public IRenderer CreateRenderer(IRenderRoot root)
{ {
@ -96,7 +107,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{ {
_view.Visibility = ViewStates.Invisible; _view.Visibility = ViewStates.Invisible;
} }
public void Invalidate(Rect rect) public void Invalidate(Rect rect)
{ {
if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate(); 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); 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) public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{ {

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

22
src/Avalonia.Base/Data/IndexerBinding.cs

@ -1,6 +1,4 @@
using System; namespace Avalonia.Data
namespace Avalonia.Data
{ {
public class IndexerBinding : IBinding public class IndexerBinding : IBinding
{ {
@ -24,23 +22,7 @@ namespace Avalonia.Data
object anchor = null, object anchor = null,
bool enableDataValidation = false) bool enableDataValidation = false)
{ {
var mode = Mode == BindingMode.Default ? return new InstancedBinding(Source.GetSubject(Property), Mode, BindingPriority.LocalValue);
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.");
}
} }
} }
} }

13
src/Avalonia.Controls/Button.cs

@ -80,6 +80,7 @@ namespace Avalonia.Controls
private ICommand _command; private ICommand _command;
private bool _commandCanExecute = true; private bool _commandCanExecute = true;
private KeyGesture _hotkey;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Button"/> class. /// Initializes static members of the <see cref="Button"/> class.
@ -207,6 +208,11 @@ namespace Avalonia.Controls
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) 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); base.OnAttachedToLogicalTree(e);
if (Command != null) if (Command != null)
@ -217,6 +223,13 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) 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); base.OnDetachedFromLogicalTree(e);
if (Command != null) if (Command != null)

13
src/Avalonia.Controls/MenuItem.cs

@ -102,6 +102,7 @@ namespace Avalonia.Controls
private ICommand? _command; private ICommand? _command;
private bool _commandCanExecute = true; private bool _commandCanExecute = true;
private Popup? _popup; private Popup? _popup;
private KeyGesture _hotkey;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="MenuItem"/> class. /// Initializes static members of the <see cref="MenuItem"/> class.
@ -337,6 +338,11 @@ namespace Avalonia.Controls
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) 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); base.OnAttachedToLogicalTree(e);
if (Command != null) if (Command != null)
@ -347,6 +353,13 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) 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); base.OnDetachedFromLogicalTree(e);
if (Command != null) if (Command != null)

6
src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs

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

3
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

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

111
src/Avalonia.Controls/Slider.cs

@ -11,7 +11,6 @@ using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// Enum which describes how to position the ticks in a <see cref="Slider"/>. /// Enum which describes how to position the ticks in a <see cref="Slider"/>.
/// </summary> /// </summary>
@ -84,6 +83,9 @@ namespace Avalonia.Controls
private IDisposable _increaseButtonSubscription; private IDisposable _increaseButtonSubscription;
private IDisposable _increaseButtonReleaseDispose; private IDisposable _increaseButtonReleaseDispose;
private IDisposable _pointerMovedDispose; private IDisposable _pointerMovedDispose;
private IDisposable _trackOnKeyDownDispose;
private const double Tolerance = 0.0001;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Slider"/> class. /// 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.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e),
RoutingStrategies.Bubble); RoutingStrategies.Bubble);
ValueProperty.OverrideMetadata<Slider>(new DirectPropertyMetadata<double>(enableDataValidation: true)); ValueProperty.OverrideMetadata<Slider>(new DirectPropertyMetadata<double>(enableDataValidation: true));
} }
@ -157,13 +159,14 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
_decreaseButtonPressDispose?.Dispose(); _decreaseButtonPressDispose?.Dispose();
_decreaseButtonReleaseDispose?.Dispose(); _decreaseButtonReleaseDispose?.Dispose();
_increaseButtonSubscription?.Dispose(); _increaseButtonSubscription?.Dispose();
_increaseButtonReleaseDispose?.Dispose(); _increaseButtonReleaseDispose?.Dispose();
_pointerMovedDispose?.Dispose(); _pointerMovedDispose?.Dispose();
_trackOnKeyDownDispose?.Dispose();
_decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton"); _decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
_track = e.NameScope.Find<Track>("PART_Track"); _track = e.NameScope.Find<Track>("PART_Track");
_increaseButton = e.NameScope.Find<Button>("PART_IncreaseButton"); _increaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
@ -171,6 +174,7 @@ namespace Avalonia.Controls
if (_track != null) if (_track != null)
{ {
_track.IsThumbDragHandled = true; _track.IsThumbDragHandled = true;
_trackOnKeyDownDispose = _track.AddDisposableHandler(KeyDownEvent, TrackOnKeyDown);
} }
if (_decreaseButton != null) if (_decreaseButton != null)
@ -188,6 +192,94 @@ namespace Avalonia.Controls
_pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel); _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) private void TrackMoved(object sender, PointerEventArgs e)
{ {
if (_isDragging) if (_isDragging)
@ -272,19 +364,18 @@ namespace Avalonia.Controls
{ {
if (IsSnapToTickEnabled) if (IsSnapToTickEnabled)
{ {
double previous = Minimum; var previous = Minimum;
double next = Maximum; var next = Maximum;
// This property is rarely set so let's try to avoid the GetValue // This property is rarely set so let's try to avoid the GetValue
var ticks = Ticks; var ticks = Ticks;
// If ticks collection is available, use it. // If ticks collection is available, use it.
// Note that ticks may be unsorted. // 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)) if (MathUtilities.AreClose(tick, value))
{ {
return value; return value;
@ -302,7 +393,7 @@ namespace Avalonia.Controls
} }
else if (MathUtilities.GreaterThan(TickFrequency, 0.0)) 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); next = Math.Min(Maximum, previous + TickFrequency);
} }

13
src/Avalonia.Controls/ToolTipService.cs

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

2
src/Avalonia.Controls/Utils/AncestorFinder.cs

@ -47,6 +47,8 @@ namespace Avalonia.Controls.Utils
public void Dispose() public void Dispose()
{ {
_child?.Dispose();
_subject.Dispose();
_disposable.Dispose(); _disposable.Dispose();
} }
} }

2
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@ -99,7 +99,7 @@
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<StackPanel VerticalAlignment="Bottom" Margin="10"> <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> </StackPanel>
</Grid> </Grid>
</Window> </Window>

2
src/Avalonia.Native/WindowImplBase.cs

@ -383,7 +383,7 @@ namespace Avalonia.Native
_native.BeginMoveDrag(); _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(); .OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
public void SetTopmost(bool value) public void SetTopmost(bool value)

4
src/Avalonia.OpenGL/Egl/EglInterface.cs

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

2
src/Avalonia.OpenGL/GlInterface.cs

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

1
src/Avalonia.Themes.Default/MenuItem.xaml

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

1
src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

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

3
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -26,6 +26,9 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </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]"> <Style Selector="Expander[ExpandDirection=Up]">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>

1
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

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

1
src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml

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

2
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_CLASS = (IntPtr)67;
public readonly IntPtr XA_WM_TRANSIENT_FOR = (IntPtr)68; 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_PROTOCOLS;
public readonly IntPtr WM_DELETE_WINDOW; public readonly IntPtr WM_DELETE_WINDOW;

4
src/Avalonia.X11/X11Screens.cs

@ -91,12 +91,12 @@ namespace Avalonia.X11
var hasEDID = false; var hasEDID = false;
for(var pc = 0; pc < propertyCount; pc++) for(var pc = 0; pc < propertyCount; pc++)
{ {
if(properties[pc] == _x11.Atoms.RR_PROPERTY_RANDR_EDID) if(properties[pc] == _x11.Atoms.EDID)
hasEDID = true; hasEDID = true;
} }
if(!hasEDID) if(!hasEDID)
return null; 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) if(actualType != _x11.Atoms.XA_INTEGER)
return null; return null;
if(actualFormat != 8) // Expecting an byte array if(actualFormat != 8) // Expecting an byte array

5
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.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); no.Children.Remove(d);
} }
} }

14
src/Shared/PlatformSupport/DynLoader.cs

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

28
tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

@ -270,6 +270,34 @@ namespace Avalonia.Controls.UnitTests
Assert.Empty(toolTip.Classes); 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 internal class ToolTipViewModel

115
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.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
using Xunit; using Xunit;
using System; using System;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
@ -53,7 +58,115 @@ namespace Avalonia.Controls.UnitTests.Utils
HotKeyManager.SetHotKey(button, null); HotKeyManager.SetHotKey(button, null);
Assert.Empty(tl.KeyBindings); Assert.Empty(tl.KeyBindings);
}
}
[Fact]
public void HotKeyManager_Should_Release_Reference_When_Control_Detached()
{
using (AvaloniaLocator.EnterScope())
{
var styler = new Mock<Styler>();
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
.Bind<IStyler>().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<Styler>();
AvaloniaLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
.Bind<IStyler>().ToConstant(styler.Object);
var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
var weakReferences = new List<WeakReference>();
var tl = new Window { SizeToContent = SizeToContent.WidthAndHeight, IsVisible = true };
var lm = tl.LayoutManager;
var keyGestures = new AvaloniaList<KeyGesture> { 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);
}
} }
} }

7
tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs

@ -1,5 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Converters; using Avalonia.Markup.Xaml.Converters;
using Xunit; using Xunit;
@ -7,6 +9,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
{ {
public class PointsListTypeConverterTests public class PointsListTypeConverterTests
{ {
static PointsListTypeConverterTests()
{
RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle);
}
[Theory] [Theory]
[InlineData("1,2 3,4")] [InlineData("1,2 3,4")]
[InlineData("1 2 3 4")] [InlineData("1 2 3 4")]

31
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);
}
}
}
}
Loading…
Cancel
Save