diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000..4e34d4b132
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,32 @@
+---
+name: Bug report
+about: Create a report to help us improve Avalonia
+title: ''
+labels: bug
+assignees: ''
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+
+- OS: [e.g. Windows, Mac, Linux (State distribution)]
+- Version [e.g. 0.10.0-rc1 or 0.9.12]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000..687355d825
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Questions, Discussions, Ideas
+ url: https://github.com/AvaloniaUI/Avalonia/discussions/new
+ about: Please ask and answer questions here.
+ - name: Avalonia Community Support on Gitter
+ url: https://gitter.im/AvaloniaUI/Avalonia
+ about: Please ask and answer questions here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000000..5f0a04cee3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,19 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject b/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
index 319cd523ce..95a483b433 100644
--- a/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
@@ -1,5 +1,3 @@
-
- True
-
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Win32.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.v3.ncrunchproject
index e12537d535..a764f36a24 100644
--- a/.ncrunch/Avalonia.Win32.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Win32.v3.ncrunchproject
@@ -1,5 +1,8 @@
+
+ ..\..\tools\MicroComGenerator\bin\Debug\netcoreapp3.1\**.*
+
MissingOrIgnoredProjectReference
diff --git a/.ncrunch/Direct3DInteropSample.v3.ncrunchproject b/.ncrunch/Direct3DInteropSample.v3.ncrunchproject
index e12537d535..21fcdee3a6 100644
--- a/.ncrunch/Direct3DInteropSample.v3.ncrunchproject
+++ b/.ncrunch/Direct3DInteropSample.v3.ncrunchproject
@@ -3,6 +3,7 @@
MissingOrIgnoredProjectReference
+ True
True
\ No newline at end of file
diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings
index 25d62b0494..2c0a6b9dc8 100644
--- a/Avalonia.sln.DotSettings
+++ b/Avalonia.sln.DotSettings
@@ -38,4 +38,5 @@
<Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" />
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
True
- True
\ No newline at end of file
+ True
+ True
\ No newline at end of file
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index ac1944b171..fbd8507193 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -27,7 +27,7 @@ jobs:
variables:
SolutionDir: '$(Build.SourcesDirectory)'
pool:
- vmImage: 'macOS-10.14'
+ vmImage: 'macOS-10.15'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.401'
@@ -51,10 +51,10 @@ jobs:
inputs:
actions: 'build'
scheme: ''
- sdk: 'macosx10.14'
+ sdk: 'macosx11.1'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
- xcodeVersion: '10' # Options: 8, 9, default, specifyPath
+ xcodeVersion: '12' # Options: 8, 9, default, specifyPath
args: '-derivedDataPath ./'
- task: CmdLine@2
diff --git a/build/AndroidWorkarounds.props b/build/AndroidWorkarounds.props
index 67947296b3..de86acc6de 100644
--- a/build/AndroidWorkarounds.props
+++ b/build/AndroidWorkarounds.props
@@ -2,7 +2,7 @@
-
+
diff --git a/build/ApiDiff.props b/build/ApiDiff.props
index fb65ef6e87..666417addf 100644
--- a/build/ApiDiff.props
+++ b/build/ApiDiff.props
@@ -1,6 +1,6 @@
- 0.10.0-preview6
+ 0.10.0
$(PackageId)
Avalonia
diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props
index e636461ad9..13419eb173 100644
--- a/build/HarfBuzzSharp.props
+++ b/build/HarfBuzzSharp.props
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index 43ec995ed9..75bada4bfc 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -3,7 +3,7 @@
Avalonia
0.10.999
- Copyright 2020 © The AvaloniaUI Project
+ Copyright 2021 © The AvaloniaUI Project
https://avaloniaui.net
https://github.com/AvaloniaUI/Avalonia/
true
@@ -21,6 +21,6 @@
-
+
diff --git a/build/SourceLink.props b/build/SourceLink.props
index e27727c9e8..1e007e01eb 100644
--- a/build/SourceLink.props
+++ b/build/SourceLink.props
@@ -1,5 +1,26 @@
+
+ true
+ false
+ true
+ embedded
+ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
+
+
+
+ true
+
+
+
+ true
+
+
-
+
+
+
+
+
+
diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm
index 455cfa2e41..10f698ff45 100644
--- a/native/Avalonia.Native/src/OSX/Screens.mm
+++ b/native/Avalonia.Native/src/OSX/Screens.mm
@@ -5,12 +5,6 @@ class Screens : public ComSingleObject
public:
FORWARD_IUNKNOWN()
- private:
- CGFloat PrimaryDisplayHeight()
- {
- return NSMaxY([[[NSScreen screens] firstObject] frame]);
- }
-
public:
virtual HRESULT GetScreenCount (int* ret) override
{
@@ -36,12 +30,12 @@ public:
ret->Bounds.Height = [screen frame].size.height;
ret->Bounds.Width = [screen frame].size.width;
ret->Bounds.X = [screen frame].origin.x;
- ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height;
+ ret->Bounds.Y = ConvertPointY(ToAvnPoint([screen frame].origin)).Y - ret->Bounds.Height;
ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->WorkingArea.X = [screen visibleFrame].origin.x;
- ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height;
+ ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
ret->PixelDensity = [screen backingScaleFactor];
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 871bca086d..ea48a19874 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -34,6 +34,7 @@ extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p);
+extern CGFloat PrimaryDisplayHeight();
extern NSSize ToNSSize (AvnSize s);
#ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__)
diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm
index b6f9ed5071..1732d6e71f 100644
--- a/native/Avalonia.Native/src/OSX/cursor.mm
+++ b/native/Avalonia.Native/src/OSX/cursor.mm
@@ -62,6 +62,28 @@ public:
return S_OK;
}
+
+ virtual HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut) override
+ {
+ if(bitmapData == nullptr || retOut == nullptr)
+ {
+ return E_POINTER;
+ }
+
+ NSData *imageData = [NSData dataWithBytes:bitmapData length:length];
+ NSImage *image = [[NSImage alloc] initWithData:imageData];
+
+
+ NSPoint hotSpot;
+ hotSpot.x = hotPixel.Width;
+ hotSpot.y = hotPixel.Height;
+
+ *retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]);
+
+ (*retOut)->AddRef();
+
+ return S_OK;
+ }
};
extern IAvnCursorFactory* CreateCursorFactory()
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index cd6ef73826..72f5aa0a7d 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -299,10 +299,14 @@ AvnPoint ToAvnPoint (NSPoint p)
AvnPoint ConvertPointY (AvnPoint p)
{
- auto sw = [NSScreen.screens objectAtIndex:0].frame;
+ auto primaryDisplayHeight = NSMaxY([[[NSScreen screens] firstObject] frame]);
- auto t = MAX(sw.origin.y, sw.origin.y + sw.size.height);
- p.Y = t - p.Y;
+ p.Y = primaryDisplayHeight - p.Y;
return p;
}
+
+CGFloat PrimaryDisplayHeight()
+{
+ return NSMaxY([[[NSScreen screens] firstObject] frame]);
+}
diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm
index 00b6dab219..b2d4341bb9 100644
--- a/native/Avalonia.Native/src/OSX/rendertarget.mm
+++ b/native/Avalonia.Native/src/OSX/rendertarget.mm
@@ -111,7 +111,11 @@
if(_renderbuffer != 0)
glDeleteRenderbuffers(1, &_renderbuffer);
}
- CFRelease(surface);
+
+ if(surface != nullptr)
+ {
+ CFRelease(surface);
+ }
}
@end
@@ -145,6 +149,12 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
}
- (void)resize:(AvnPixelSize)size withScale: (float) scale{
+
+ if(size.Height <= 0)
+ size.Height = 1;
+ if(size.Width <= 0)
+ size.Width = 1;
+
@synchronized (lock) {
if(surface == nil
|| surface->size.Width != size.Width
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 8419258fe9..9d49025398 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -1391,17 +1391,20 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[super viewDidChangeBackingProperties];
}
-- (bool) ignoreUserInput
+- (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
{
auto parentWindow = objc_cast([self window]);
if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
{
- auto window = dynamic_cast(_parent.getRaw());
-
- if(window != nullptr)
+ if(trigerInputWhenDisabled)
{
- window->WindowEvents->GotInputWhenDisabled();
+ auto window = dynamic_cast(_parent.getRaw());
+
+ if(window != nullptr)
+ {
+ window->WindowEvents->GotInputWhenDisabled();
+ }
}
return TRUE;
@@ -1412,7 +1415,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{
- if([self ignoreUserInput])
+ bool triggerInputWhenDisabled = type != Move;
+
+ if([self ignoreUserInput: triggerInputWhenDisabled])
{
return;
}
@@ -1578,7 +1583,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
{
- if([self ignoreUserInput])
+ if([self ignoreUserInput: false])
{
return;
}
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index 75ee4a05cb..44e2290a0d 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -6,7 +6,9 @@
-
+
+ all
+
@@ -29,21 +31,23 @@
+
true
- build\
+ build\;buildTransitive\
true
- build\
+ build\;buildTransitive\
true
- build\
+ build\;buildTransitive\
+
diff --git a/readme.md b/readme.md
index 726a634e1a..f73bdffaeb 100644
--- a/readme.md
+++ b/readme.md
@@ -10,7 +10,7 @@ Avalonia is a cross-platform XAML-based UI framework providing a flexible stylin
-> **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.
+([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
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!
@@ -31,15 +31,22 @@ Install-Package Avalonia.Desktop
Examples of UIs built with Avalonia

+([Synfonia](https://github.com/jmacato/Synfonia))
+

+([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))

+([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))

+([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
## JetBrains Rider
-If you need to develop Avalonia app with JetBrains Rider you can use latest Rider [preview builds](https://www.jetbrains.com/rider/nextversion/).
+[JetBrains Rider](https://www.jetbrains.com/rider/whatsnew/?mkt_tok=eyJpIjoiTURBNU1HSmhNV0kwTUdFMiIsInQiOiJtNnU2VEc1TlNLa1ZRVkROYmdZYVpYREJsaU1qdUhmS3dxSzRHczdYWHl0RVlTNDMwSFwvNUs3VENTNVM0bVcyNFdaRmVYZzVWTTF1N3VrQWNGTkJreEhlam1hMlB4UVVWcHBGM1dNOUxoXC95YnRQdGgyUXl1YmZCM3h3d3BVWWdBIn0%3D#avalonia-support) now has official support for Avalonia.
+
+Code completion, inspections and refactorings are supported out of the box, for XAML previewer add `https://plugins.jetbrains.com/plugins/dev/14839` to plugin repositories and install [AvaloniaRider](https://github.com/ForNeVeR/AvaloniaRider) plugin.
## Bleeding Edge Builds
diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
index 5b82e2caee..20ca0576d4 100644
--- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
+++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
@@ -16,7 +16,7 @@
Resources\Resource.Designer.cs
Off
False
- v9.0
+ v10.0
Properties\AndroidManifest.xml
@@ -71,21 +71,23 @@
+
-
- Designer
-
+
-
+
+
-
+
+ Resources\drawable\Icon.png
+
diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs
index 40d001a195..2ab03551b6 100644
--- a/samples/ControlCatalog.Android/MainActivity.cs
+++ b/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()
- .UseAndroid()
- .SetupWithoutStarting();
- Content = new MainView();
- }
base.OnCreate(savedInstanceState);
+
+ Content = new MainView();
}
}
}
diff --git a/samples/ControlCatalog.Android/Properties/AndroidManifest.xml b/samples/ControlCatalog.Android/Properties/AndroidManifest.xml
index e39ec39f1c..02e97f3065 100644
--- a/samples/ControlCatalog.Android/Properties/AndroidManifest.xml
+++ b/samples/ControlCatalog.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs
index 96f0e76fd8..b1ca548e2c 100644
--- a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs
+++ b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs
@@ -2,7 +2,6 @@
//------------------------------------------------------------------------------
//
// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -15,7 +14,7 @@ namespace ControlCatalog.Android
{
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
@@ -26,8 +25,6 @@ namespace ControlCatalog.Android
public static void UpdateIdValues()
{
- global::Avalonia.Android.Resource.String.ApplicationName = global::ControlCatalog.Android.Resource.String.ApplicationName;
- global::Avalonia.Android.Resource.String.Hello = global::ControlCatalog.Android.Resource.String.Hello;
}
public partial class Attribute
@@ -43,69 +40,59 @@ namespace ControlCatalog.Android
}
}
- public partial class Drawable
+ public partial class Color
{
- // aapt resource value: 0x7f020000
- public const int Icon = 2130837504;
+ // aapt resource value: 0x7F010000
+ 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: 0x7f050000
- public const int MyButton = 2131034112;
-
- static Id()
- {
- global::Android.Runtime.ResourceIdManager.UpdateIdValues();
- }
-
- private Id()
- {
- }
- }
-
- public partial class Layout
- {
+ // aapt resource value: 0x7F020000
+ public const int Icon = 2130837504;
- // aapt resource value: 0x7f030000
- public const int Main = 2130903040;
+ // aapt resource value: 0x7F020001
+ public const int splash_screen = 2130837505;
- static Layout()
+ static Drawable()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
- private Layout()
+ private Drawable()
{
}
}
- public partial class String
+ public partial class Style
{
- // aapt resource value: 0x7f040001
- public const int ApplicationName = 2130968577;
+ // aapt resource value: 0x7F030000
+ public const int MyTheme = 2130903040;
+
+ // aapt resource value: 0x7F030001
+ public const int MyTheme_NoActionBar = 2130903041;
- // aapt resource value: 0x7f040000
- public const int Hello = 2130968576;
+ // aapt resource value: 0x7F030002
+ public const int MyTheme_Splash = 2130903042;
- static String()
+ static Style()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
- private String()
+ private Style()
{
}
}
diff --git a/samples/ControlCatalog.Android/Resources/drawable/Icon.png b/samples/ControlCatalog.Android/Resources/drawable/Icon.png
deleted file mode 100644
index 8074c4c571..0000000000
Binary files a/samples/ControlCatalog.Android/Resources/drawable/Icon.png and /dev/null differ
diff --git a/samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml b/samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml
new file mode 100644
index 0000000000..2e920b4b3b
--- /dev/null
+++ b/samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml
@@ -0,0 +1,13 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/samples/ControlCatalog.Android/Resources/layout/Main.axml b/samples/ControlCatalog.Android/Resources/layout/Main.axml
deleted file mode 100644
index 570c96ad72..0000000000
--- a/samples/ControlCatalog.Android/Resources/layout/Main.axml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
diff --git a/samples/ControlCatalog.Android/Resources/values/Strings.xml b/samples/ControlCatalog.Android/Resources/values/Strings.xml
deleted file mode 100644
index 95221a08a9..0000000000
--- a/samples/ControlCatalog.Android/Resources/values/Strings.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- Hello World, Click Me!
- ControlCatalog.Android
-
diff --git a/samples/ControlCatalog.Android/Resources/values/colors.xml b/samples/ControlCatalog.Android/Resources/values/colors.xml
new file mode 100644
index 0000000000..59279d5d32
--- /dev/null
+++ b/samples/ControlCatalog.Android/Resources/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
diff --git a/samples/ControlCatalog.Android/Resources/values/styles.xml b/samples/ControlCatalog.Android/Resources/values/styles.xml
new file mode 100644
index 0000000000..e017b6facf
--- /dev/null
+++ b/samples/ControlCatalog.Android/Resources/values/styles.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog.Android/SplashActivity.cs b/samples/ControlCatalog.Android/SplashActivity.cs
new file mode 100644
index 0000000000..6d7c6bc116
--- /dev/null
+++ b/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()
+ .UseAndroid()
+ .SetupWithoutStarting();
+ }
+
+ StartActivity(new Intent(Application.Context, typeof(MainActivity)));
+ }
+ }
+}
diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs
index 1dc8c09c0e..0c8fd9465c 100644
--- a/samples/ControlCatalog.NetCore/Program.cs
+++ b/samples/ControlCatalog.NetCore/Program.cs
@@ -109,12 +109,12 @@ namespace ControlCatalog.NetCore
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
- UseDBusMenu = true
+ UseDBusMenu = true,
+ EnableIme = true,
})
.With(new Win32PlatformOptions
{
- EnableMultitouch = true,
- AllowEglInitialization = true
+ EnableMultitouch = true
})
.UseSkia()
.UseManagedSystemDialogs()
diff --git a/samples/ControlCatalog/Assets/Fonts/WenQuanYiMicroHei-01.ttf b/samples/ControlCatalog/Assets/Fonts/WenQuanYiMicroHei-01.ttf
new file mode 100644
index 0000000000..61e2583a6c
Binary files /dev/null and b/samples/ControlCatalog/Assets/Fonts/WenQuanYiMicroHei-01.ttf differ
diff --git a/samples/ControlCatalog/Assets/avalonia-32.png b/samples/ControlCatalog/Assets/avalonia-32.png
new file mode 100644
index 0000000000..7b443e7a25
Binary files /dev/null and b/samples/ControlCatalog/Assets/avalonia-32.png differ
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index f001425964..142c532d75 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -22,6 +22,10 @@
+
+
+
diff --git a/samples/ControlCatalog/Pages/CursorPage.xaml b/samples/ControlCatalog/Pages/CursorPage.xaml
new file mode 100644
index 0000000000..a28039ea3f
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CursorPage.xaml
@@ -0,0 +1,29 @@
+
+
+
+ Cursor
+ Defines a cursor (mouse pointer)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CursorPage.xaml.cs b/samples/ControlCatalog/Pages/CursorPage.xaml.cs
new file mode 100644
index 0000000000..9e9e9ba8b9
--- /dev/null
+++ b/samples/ControlCatalog/Pages/CursorPage.xaml.cs
@@ -0,0 +1,20 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using ControlCatalog.ViewModels;
+
+namespace ControlCatalog.Pages
+{
+ public class CursorPage : UserControl
+ {
+ public CursorPage()
+ {
+ this.InitializeComponent();
+ DataContext = new CursorPageViewModel();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml
index 6dc50087f8..323eaa3463 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml
@@ -39,8 +39,8 @@
-
-
+
+
diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs
index c39f414b44..4edb0f137a 100644
--- a/samples/ControlCatalog/Pages/ScreenPage.cs
+++ b/samples/ControlCatalog/Pages/ScreenPage.cs
@@ -29,7 +29,7 @@ namespace ControlCatalog.Pages
var screens = w.Screens.All;
var scaling = ((IRenderRoot)w).RenderScaling;
- var drawBrush = Brushes.Green;
+ var drawBrush = Brushes.Black;
Pen p = new Pen(drawBrush);
if (screens != null)
foreach (Screen screen in screens)
@@ -45,18 +45,16 @@ namespace ControlCatalog.Pages
screen.Bounds.Height / 10f);
Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
screen.WorkingArea.Height / 10f);
+
context.DrawRectangle(p, boundsRect);
context.DrawRectangle(p, workingAreaRect);
-
- FormattedText text = new FormattedText()
- {
- Typeface = Typeface.Default
- };
- text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}";
+ var text = new FormattedText() { Typeface = new Typeface("Arial"), FontSize = 18 };
+
+ text.Text = $"Bounds: {screen.Bounds.TopLeft} {screen.Bounds.Width}:{screen.Bounds.Height}";
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height), text);
- text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
+ text.Text = $"WorkArea: {screen.WorkingArea.TopLeft} {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
text.Text = $"Scaling: {screen.PixelDensity * 100}%";
@@ -69,7 +67,7 @@ namespace ControlCatalog.Pages
context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
}
- context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));
+ context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10));
}
}
}
diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml
index b3f32ed421..eeb198976b 100644
--- a/samples/ControlCatalog/Pages/SliderPage.xaml
+++ b/samples/ControlCatalog/Pages/SliderPage.xaml
@@ -22,6 +22,20 @@
IsSnapToTickEnabled="True"
Ticks="0,20,25,40,75,100"
Width="300" />
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml
index ec073d48a9..de23c7a169 100644
--- a/samples/ControlCatalog/Pages/ToolTipPage.xaml
+++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml
@@ -6,7 +6,7 @@
ToolTip
A control which pops up a hint when a control is hovered
-
@@ -38,6 +38,31 @@
ToolTip bottom placement
+
+
+
+
+ Moving offset
+
diff --git a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
new file mode 100644
index 0000000000..f1cc0637dc
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia;
+using Avalonia.Input;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using MiniMvvm;
+
+namespace ControlCatalog.ViewModels
+{
+ public class CursorPageViewModel : ViewModelBase
+ {
+ public CursorPageViewModel()
+ {
+ StandardCursors = Enum.GetValues(typeof(StandardCursorType))
+ .Cast()
+ .Select(x => new StandardCursorModel(x))
+ .ToList();
+
+ var loader = AvaloniaLocator.Current.GetService();
+ var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png"));
+ var bitmap = new Bitmap(s);
+ CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16));
+ }
+
+ public IEnumerable StandardCursors { get; }
+
+ public Cursor CustomCursor { get; }
+
+ public class StandardCursorModel
+ {
+ public StandardCursorModel(StandardCursorType type)
+ {
+ Type = type;
+ Cursor = new Cursor(type);
+ }
+
+ public StandardCursorType Type { get; }
+
+ public Cursor Cursor { get; }
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index 2e6f4a67c3..e0ceb0c8b7 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/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(this T builder) where T : AppBuilderBase, new()
{
- builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.ApplicationType), "Android");
+ var options = AvaloniaLocator.Current.GetService() ?? 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().ToTransient()
@@ -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;
+ }
}
diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
new file mode 100644
index 0000000000..4f4c03fe77
--- /dev/null
+++ b/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;
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs b/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
new file mode 100644
index 0000000000..75bbd15e3e
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
index 7802f336fb..d1a116345b 100644
--- a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
@@ -1,7 +1,11 @@
+using System;
using System.Threading.Tasks;
+
using Android.Content;
using Android.Runtime;
using Android.Views;
+
+using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
new file mode 100644
index 0000000000..18c4796fae
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 71dce93ce7..a8c7f7af9b 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -2,20 +2,29 @@ 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;
+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 _keyboardHelper;
private readonly AndroidTouchEventsHelper _touchHelper;
@@ -28,7 +37,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_touchHelper = new AndroidTouchEventsHelper(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);
@@ -47,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; }
@@ -62,7 +72,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
set
{
-
+
}
}
@@ -82,9 +92,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public View View => _view;
+ internal InvalidationAwareSurfaceView InternalView => _view;
+
public IPlatformHandle Handle => _view;
- public IEnumerable
@@ -150,4 +150,4 @@
-
+
\ No newline at end of file
diff --git a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
index ad2cec2ae3..121acb6351 100644
--- a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
+++ b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
@@ -4,7 +4,6 @@ using Android.Content.PM;
using Android.OS;
using Avalonia.Android;
using Avalonia.Controls;
-using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling;
@@ -38,8 +37,7 @@ namespace Avalonia.AndroidTestApplication
{
Styles.Add(new DefaultTheme());
- var loader = new AvaloniaXamlLoader();
- var baseLight = (IStyle)loader.Load(
+ var baseLight = (IStyle)AvaloniaXamlLoader.Load(
new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"));
Styles.Add(baseLight);
diff --git a/src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml b/src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
index 4792c8a1ec..e8e81da9de 100644
--- a/src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
+++ b/src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
index e171dd6162..83db67fcee 100644
--- a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
+++ b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
@@ -2,7 +2,6 @@
//------------------------------------------------------------------------------
//
// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -15,7 +14,7 @@ namespace Avalonia.AndroidTestApplication
{
- [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
@@ -26,8 +25,6 @@ namespace Avalonia.AndroidTestApplication
public static void UpdateIdValues()
{
- global::Avalonia.Android.Resource.String.ApplicationName = global::Avalonia.AndroidTestApplication.Resource.String.ApplicationName;
- global::Avalonia.Android.Resource.String.Hello = global::Avalonia.AndroidTestApplication.Resource.String.Hello;
}
public partial class Attribute
@@ -46,8 +43,8 @@ namespace Avalonia.AndroidTestApplication
public partial class Drawable
{
- // aapt resource value: 0x7f020000
- public const int Icon = 2130837504;
+ // aapt resource value: 0x7F010000
+ public const int Icon = 2130771968;
static Drawable()
{
@@ -62,11 +59,11 @@ namespace Avalonia.AndroidTestApplication
public partial class String
{
- // aapt resource value: 0x7f030001
- public const int ApplicationName = 2130903041;
+ // aapt resource value: 0x7F020000
+ public const int ApplicationName = 2130837504;
- // aapt resource value: 0x7f030000
- public const int Hello = 2130903040;
+ // aapt resource value: 0x7F020001
+ public const int Hello = 2130837505;
static String()
{
diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt
deleted file mode 100644
index d17f25ec49..0000000000
--- a/src/Avalonia.Base/ApiCompatBaseline.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Compat issues with assembly Avalonia.Base:
-MembersMustExist : Member 'public void Avalonia.Threading.AvaloniaSynchronizationContext..ctor(Avalonia.Threading.AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider)' does not exist in the implementation but it does exist in the contract.
-TypesMustExist : Type 'Avalonia.Threading.AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider' does not exist in the implementation but it does exist in the contract.
-Total Issues: 2
diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index 9bfd387ca9..0470dbfe89 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -5,9 +5,6 @@
Avalonia
True
-
-
-
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 3ae0445e9b..5117fdb170 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -17,9 +17,9 @@ namespace Avalonia
public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId;
- private readonly PropertyMetadata _defaultMetadata;
- private readonly Dictionary _metadata;
- private readonly Dictionary _metadataCache = new Dictionary();
+ private readonly AvaloniaPropertyMetadata _defaultMetadata;
+ private readonly Dictionary _metadata;
+ private readonly Dictionary _metadataCache = new Dictionary();
private bool _hasMetadataOverrides;
@@ -35,7 +35,7 @@ namespace Avalonia
string name,
Type valueType,
Type ownerType,
- PropertyMetadata metadata,
+ AvaloniaPropertyMetadata metadata,
Action notifying = null)
{
Contract.Requires(name != null);
@@ -48,7 +48,7 @@ namespace Avalonia
throw new ArgumentException("'name' may not contain periods.");
}
- _metadata = new Dictionary();
+ _metadata = new Dictionary();
Name = name;
PropertyType = valueType;
@@ -69,12 +69,12 @@ namespace Avalonia
protected AvaloniaProperty(
AvaloniaProperty source,
Type ownerType,
- PropertyMetadata metadata)
+ AvaloniaPropertyMetadata metadata)
{
Contract.Requires(source != null);
Contract.Requires(ownerType != null);
- _metadata = new Dictionary();
+ _metadata = new Dictionary();
Name = source.Name;
PropertyType = source.PropertyType;
@@ -419,7 +419,7 @@ namespace Avalonia
///
/// The property metadata.
///
- public PropertyMetadata GetMetadata() where T : IAvaloniaObject
+ public AvaloniaPropertyMetadata GetMetadata() where T : IAvaloniaObject
{
return GetMetadata(typeof(T));
}
@@ -432,7 +432,7 @@ namespace Avalonia
/// The property metadata.
///
///
- public PropertyMetadata GetMetadata(Type type)
+ public AvaloniaPropertyMetadata GetMetadata(Type type)
{
if (!_hasMetadataOverrides)
{
@@ -521,7 +521,7 @@ namespace Avalonia
///
/// The type.
/// The metadata.
- protected void OverrideMetadata(Type type, PropertyMetadata metadata)
+ protected void OverrideMetadata(Type type, AvaloniaPropertyMetadata metadata)
{
Contract.Requires(type != null);
Contract.Requires(metadata != null);
@@ -542,14 +542,14 @@ namespace Avalonia
protected abstract IObservable GetChanged();
- private PropertyMetadata GetMetadataWithOverrides(Type type)
+ private AvaloniaPropertyMetadata GetMetadataWithOverrides(Type type)
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
- if (_metadataCache.TryGetValue(type, out PropertyMetadata result))
+ if (_metadataCache.TryGetValue(type, out AvaloniaPropertyMetadata result))
{
return result;
}
diff --git a/src/Avalonia.Base/PropertyMetadata.cs b/src/Avalonia.Base/AvaloniaPropertyMetadata.cs
similarity index 85%
rename from src/Avalonia.Base/PropertyMetadata.cs
rename to src/Avalonia.Base/AvaloniaPropertyMetadata.cs
index 806051e1d1..2963567b14 100644
--- a/src/Avalonia.Base/PropertyMetadata.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyMetadata.cs
@@ -5,15 +5,15 @@ namespace Avalonia
///
/// Base class for avalonia property metadata.
///
- public class PropertyMetadata
+ public class AvaloniaPropertyMetadata
{
private BindingMode _defaultBindingMode;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The default binding mode.
- public PropertyMetadata(
+ public AvaloniaPropertyMetadata(
BindingMode defaultBindingMode = BindingMode.Default)
{
_defaultBindingMode = defaultBindingMode;
@@ -37,7 +37,7 @@ namespace Avalonia
/// The base metadata to merge.
/// The property to which the metadata is being applied.
public virtual void Merge(
- PropertyMetadata baseMetadata,
+ AvaloniaPropertyMetadata baseMetadata,
AvaloniaProperty property)
{
if (_defaultBindingMode == BindingMode.Default)
diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs
index d5549e979b..247ea0a25a 100644
--- a/src/Avalonia.Base/AvaloniaProperty`1.cs
+++ b/src/Avalonia.Base/AvaloniaProperty`1.cs
@@ -23,7 +23,7 @@ namespace Avalonia
protected AvaloniaProperty(
string name,
Type ownerType,
- PropertyMetadata metadata,
+ AvaloniaPropertyMetadata metadata,
Action notifying = null)
: base(name, typeof(TValue), ownerType, metadata, notifying)
{
@@ -40,7 +40,7 @@ namespace Avalonia
protected AvaloniaProperty(
AvaloniaProperty source,
Type ownerType,
- PropertyMetadata metadata)
+ AvaloniaPropertyMetadata metadata)
: this(source as AvaloniaProperty ?? throw new InvalidOperationException(), ownerType, metadata)
{
}
@@ -54,7 +54,7 @@ namespace Avalonia
protected AvaloniaProperty(
AvaloniaProperty source,
Type ownerType,
- PropertyMetadata metadata)
+ AvaloniaPropertyMetadata metadata)
: base(source, ownerType, metadata)
{
_changed = source._changed;
diff --git a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs b/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
index 7e7417d2f5..7ff0a8ceca 100644
--- a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
+++ b/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();
}
-
+
}
}
}
diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
index 3bf6842cd6..d600603d5c 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Data.Core.Plugins
{
private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup =
new Dictionary<(Type, string), PropertyInfo>();
-
+
///
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
@@ -51,7 +51,7 @@ namespace Avalonia.Data.Core.Plugins
private PropertyInfo GetFirstPropertyWithName(Type type, string propertyName)
{
var key = (type, propertyName);
-
+
if (!_propertyLookup.TryGetValue(key, out PropertyInfo propertyInfo))
{
propertyInfo = TryFindAndCacheProperty(type, propertyName);
@@ -59,7 +59,7 @@ namespace Avalonia.Data.Core.Plugins
return propertyInfo;
}
-
+
private PropertyInfo TryFindAndCacheProperty(Type type, string propertyName)
{
PropertyInfo found = null;
@@ -90,7 +90,7 @@ namespace Avalonia.Data.Core.Plugins
private readonly PropertyInfo _property;
private bool _eventRaised;
- public Accessor(WeakReference reference, PropertyInfo property)
+ public Accessor(WeakReference reference, PropertyInfo property)
{
Contract.Requires(reference != null);
Contract.Requires(property != null);
diff --git a/src/Avalonia.Base/Data/Core/SettableNode.cs b/src/Avalonia.Base/Data/Core/SettableNode.cs
index d0a918dc88..363d3da0ef 100644
--- a/src/Avalonia.Base/Data/Core/SettableNode.cs
+++ b/src/Avalonia.Base/Data/Core/SettableNode.cs
@@ -15,7 +15,8 @@ namespace Avalonia.Data.Core
private bool ShouldNotSet(object value)
{
- if (PropertyType == null)
+ var propertyType = PropertyType;
+ if (propertyType == null)
{
return false;
}
@@ -37,7 +38,7 @@ namespace Avalonia.Data.Core
return false;
}
- if (PropertyType.IsValueType)
+ if (propertyType.IsValueType)
{
return lastValue.Equals(value);
}
diff --git a/src/Avalonia.Base/Data/IndexerBinding.cs b/src/Avalonia.Base/Data/IndexerBinding.cs
index cc3baa4530..4d07b53dd2 100644
--- a/src/Avalonia.Base/Data/IndexerBinding.cs
+++ b/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);
}
}
}
diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs
index a2f113adb7..e6cc1edfdf 100644
--- a/src/Avalonia.Base/DirectPropertyBase.cs
+++ b/src/Avalonia.Base/DirectPropertyBase.cs
@@ -26,7 +26,7 @@ namespace Avalonia
protected DirectPropertyBase(
string name,
Type ownerType,
- PropertyMetadata metadata)
+ AvaloniaPropertyMetadata metadata)
: base(name, ownerType, metadata)
{
}
@@ -41,7 +41,7 @@ namespace Avalonia
protected DirectPropertyBase(
AvaloniaProperty source,
Type ownerType,
- PropertyMetadata metadata)
+ AvaloniaPropertyMetadata metadata)
: this(source as DirectPropertyBase ?? throw new InvalidOperationException(), ownerType, metadata)
{
}
@@ -55,7 +55,7 @@ namespace Avalonia
protected DirectPropertyBase(
DirectPropertyBase source,
Type ownerType,
- PropertyMetadata metadata)
+ AvaloniaPropertyMetadata metadata)
: base(source, ownerType, metadata)
{
}
diff --git a/src/Avalonia.Base/DirectPropertyMetadata`1.cs b/src/Avalonia.Base/DirectPropertyMetadata`1.cs
index 59a60507dc..205967984d 100644
--- a/src/Avalonia.Base/DirectPropertyMetadata`1.cs
+++ b/src/Avalonia.Base/DirectPropertyMetadata`1.cs
@@ -5,7 +5,7 @@ namespace Avalonia
///
/// Metadata for direct avalonia properties.
///
- public class DirectPropertyMetadata : PropertyMetadata, IDirectPropertyMetadata
+ public class DirectPropertyMetadata : AvaloniaPropertyMetadata, IDirectPropertyMetadata
{
///
/// Initializes a new instance of the class.
@@ -47,7 +47,7 @@ namespace Avalonia
object IDirectPropertyMetadata.UnsetValue => UnsetValue;
///
- public override void Merge(PropertyMetadata baseMetadata, AvaloniaProperty property)
+ public override void Merge(AvaloniaPropertyMetadata baseMetadata, AvaloniaProperty property)
{
base.Merge(baseMetadata, property);
diff --git a/src/Avalonia.Base/EnumExtensions.cs b/src/Avalonia.Base/EnumExtensions.cs
index 1e4864283f..bc1f8d36a9 100644
--- a/src/Avalonia.Base/EnumExtensions.cs
+++ b/src/Avalonia.Base/EnumExtensions.cs
@@ -11,10 +11,32 @@ namespace Avalonia
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasFlagCustom(this T value, T flag) where T : unmanaged, Enum
{
- var intValue = *(int*)&value;
- var intFlag = *(int*)&flag;
-
- return (intValue & intFlag) == intFlag;
+ if (sizeof(T) == 1)
+ {
+ var byteValue = Unsafe.As(ref value);
+ var byteFlag = Unsafe.As(ref flag);
+ return (byteValue & byteFlag) == byteFlag;
+ }
+ else if (sizeof(T) == 2)
+ {
+ var shortValue = Unsafe.As(ref value);
+ var shortFlag = Unsafe.As(ref flag);
+ return (shortValue & shortFlag) == shortFlag;
+ }
+ else if (sizeof(T) == 4)
+ {
+ var intValue = Unsafe.As(ref value);
+ var intFlag = Unsafe.As(ref flag);
+ return (intValue & intFlag) == intFlag;
+ }
+ else if (sizeof(T) == 8)
+ {
+ var longValue = Unsafe.As(ref value);
+ var longFlag = Unsafe.As(ref flag);
+ return (longValue & longFlag) == longFlag;
+ }
+ else
+ throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf() + " are not supported");
}
}
}
diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs
index 3c19b47a05..2ad220dddd 100644
--- a/src/Avalonia.Base/Logging/LogArea.cs
+++ b/src/Avalonia.Base/Logging/LogArea.cs
@@ -34,5 +34,10 @@ namespace Avalonia.Logging
/// The log event comes from the control system.
///
public const string Control = "Control";
+
+ ///
+ /// The log event comes from Win32Platform.
+ ///
+ public const string Win32Platform = nameof(Win32Platform);
}
}
diff --git a/src/Avalonia.Base/StyledPropertyMetadata`1.cs b/src/Avalonia.Base/StyledPropertyMetadata`1.cs
index cf0a0c34ec..ec7bff4dfb 100644
--- a/src/Avalonia.Base/StyledPropertyMetadata`1.cs
+++ b/src/Avalonia.Base/StyledPropertyMetadata`1.cs
@@ -6,7 +6,7 @@ namespace Avalonia
///
/// Metadata for styled avalonia properties.
///
- public class StyledPropertyMetadata : PropertyMetadata, IStyledPropertyMetadata
+ public class StyledPropertyMetadata : AvaloniaPropertyMetadata, IStyledPropertyMetadata
{
private Optional _defaultValue;
@@ -39,7 +39,7 @@ namespace Avalonia
object IStyledPropertyMetadata.DefaultValue => DefaultValue;
///
- public override void Merge(PropertyMetadata baseMetadata, AvaloniaProperty property)
+ public override void Merge(AvaloniaPropertyMetadata baseMetadata, AvaloniaProperty property)
{
base.Merge(baseMetadata, property);
diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs
index d0d88166a7..097731bc60 100644
--- a/src/Avalonia.Base/Utilities/TypeUtilities.cs
+++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs
@@ -372,8 +372,8 @@ namespace Avalonia.Utilities
const string implicitName = "op_Implicit";
const string explicitName = "op_Explicit";
- bool allowImplicit = (operatorType & OperatorType.Implicit) != 0;
- bool allowExplicit = (operatorType & OperatorType.Explicit) != 0;
+ bool allowImplicit = operatorType.HasFlagCustom(OperatorType.Implicit);
+ bool allowExplicit = operatorType.HasFlagCustom(OperatorType.Explicit);
foreach (MethodInfo method in fromType.GetMethods())
{
diff --git a/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt
deleted file mode 100644
index fcc74cf864..0000000000
--- a/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt
+++ /dev/null
@@ -1 +0,0 @@
-Total Issues: 0
diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
index 92734b128d..b97f2a2bcb 100644
--- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
+++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
@@ -2595,7 +2595,7 @@ namespace Avalonia.Collections
/// Whether the specified flag is set
private bool CheckFlag(CollectionViewFlags flags)
{
- return (_flags & flags) != 0;
+ return _flags.HasFlagCustom(flags);
}
///
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index 5bb2763566..8f9b9583cf 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -1,6 +1,6 @@
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
-// All other rights reserved.
+// All other rights reserved.
using Avalonia.Collections;
using Avalonia.Controls.Primitives;
@@ -92,7 +92,7 @@ namespace Avalonia.Controls
private ContentControl _topRightCornerHeader;
private Control _frozenColumnScrollBarSpacer;
- // the sum of the widths in pixels of the scrolling columns preceding
+ // the sum of the widths in pixels of the scrolling columns preceding
// the first displayed scrolling column
private double _horizontalOffset;
@@ -143,7 +143,7 @@ namespace Avalonia.Controls
private object _uneditedValue; // Represents the original current cell value at the time it enters editing mode.
private ICellEditBinding _currentCellEditBinding;
- // An approximation of the sum of the heights in pixels of the scrolling rows preceding
+ // An approximation of the sum of the heights in pixels of the scrolling rows preceding
// the first displayed scrolling row. Since the scrolled off rows are discarded, the grid
// does not know their actual height. The heights used for the approximation are the ones
// set as the rows were scrolled off.
@@ -162,7 +162,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register(nameof(CanUserReorderColumns));
///
- /// Gets or sets a value that indicates whether the user can change
+ /// Gets or sets a value that indicates whether the user can change
/// the column display order by dragging column headers with the mouse.
///
public bool CanUserReorderColumns
@@ -247,8 +247,8 @@ namespace Avalonia.Controls
/// Gets or sets the that is used to paint the background of odd-numbered rows.
///
///
- /// The brush that is used to paint the background of odd-numbered rows. The default is a
- /// with a
+ /// The brush that is used to paint the background of odd-numbered rows. The default is a
+ /// with a
/// value of white (ARGB value #00FFFFFF).
///
public IBrush AlternatingRowBackground
@@ -379,8 +379,8 @@ namespace Avalonia.Controls
public bool IsValid
{
get { return _isValid; }
- internal set
- {
+ internal set
+ {
SetAndRaise(IsValidProperty, ref _isValid, value);
PseudoClasses.Set(":invalid", !value);
}
@@ -398,7 +398,7 @@ namespace Avalonia.Controls
}
///
- /// Gets or sets the maximum width of columns in the .
+ /// Gets or sets the maximum width of columns in the .
///
public double MaxColumnWidth
{
@@ -418,7 +418,7 @@ namespace Avalonia.Controls
}
///
- /// Gets or sets the minimum width of columns in the .
+ /// Gets or sets the minimum width of columns in the .
///
public double MinColumnWidth
{
@@ -496,7 +496,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register(nameof(VerticalGridLinesBrush));
///
- /// Gets or sets the that is used to paint grid lines separating columns.
+ /// Gets or sets the that is used to paint grid lines separating columns.
///
public IBrush VerticalGridLinesBrush
{
@@ -542,7 +542,7 @@ namespace Avalonia.Controls
///
///
/// The index of the current selection, or -1 if the selection is empty.
- ///
+ ///
public int SelectedIndex
{
get { return _selectedIndex; }
@@ -582,7 +582,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register(nameof(AutoGenerateColumns));
///
- /// Gets or sets a value that indicates whether columns are created
+ /// Gets or sets a value that indicates whether columns are created
/// automatically when the property is set.
///
public bool AutoGenerateColumns
@@ -626,7 +626,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register(nameof(AreRowDetailsFrozen));
///
- /// Gets or sets a value that indicates whether the row details sections remain
+ /// Gets or sets a value that indicates whether the row details sections remain
/// fixed at the width of the display area or can scroll horizontally.
///
public bool AreRowDetailsFrozen
@@ -881,7 +881,7 @@ namespace Avalonia.Controls
{
int index = (int)e.NewValue;
- // GetDataItem returns null if index is >= Count, we do not check newValue
+ // GetDataItem returns null if index is >= Count, we do not check newValue
// against Count here to avoid enumerating through an Enumerable twice
// Setting SelectedItem coerces the finally value of the SelectedIndex
object newSelectedItem = (index < 0) ? null : DataConnection.GetDataItem(index);
@@ -1168,14 +1168,14 @@ namespace Avalonia.Controls
}
///
- /// Occurs one time for each public, non-static property in the bound data type when the
- /// property is changed and the
+ /// Occurs one time for each public, non-static property in the bound data type when the
+ /// property is changed and the
/// property is true.
///
public event EventHandler AutoGeneratingColumn;
///
- /// Occurs before a cell or row enters editing mode.
+ /// Occurs before a cell or row enters editing mode.
///
public event EventHandler BeginningEdit;
@@ -1195,7 +1195,7 @@ namespace Avalonia.Controls
public event EventHandler CellPointerPressed;
///
- /// Occurs when the
+ /// Occurs when the
/// property of a column changes.
///
public event EventHandler ColumnDisplayIndexChanged;
@@ -1218,14 +1218,14 @@ namespace Avalonia.Controls
public event EventHandler CurrentCellChanged;
///
- /// Occurs after a
+ /// Occurs after a
/// is instantiated, so that you can customize it before it is used.
///
public event EventHandler LoadingRow;
///
/// Occurs when a cell in a enters editing mode.
- ///
+ ///
///
public event EventHandler PreparingCellForEdit;
@@ -1243,7 +1243,7 @@ namespace Avalonia.Controls
RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble);
///
- /// Occurs when the or
+ /// Occurs when the or
/// property value changes.
///
public event EventHandler SelectionChanged
@@ -1258,19 +1258,19 @@ namespace Avalonia.Controls
public event EventHandler Sorting;
///
- /// Occurs when a
+ /// Occurs when a
/// object becomes available for reuse.
///
public event EventHandler UnloadingRow;
///
- /// Occurs when a new row details template is applied to a row, so that you can customize
+ /// Occurs when a new row details template is applied to a row, so that you can customize
/// the details section before it is used.
///
public event EventHandler LoadingRowDetails;
///
- /// Occurs when the
+ /// Occurs when the
/// property value changes.
///
public event EventHandler RowDetailsVisibilityChanged;
@@ -1282,7 +1282,7 @@ namespace Avalonia.Controls
///
/// Gets a collection that contains all the columns in the control.
- ///
+ ///
public ObservableCollection Columns
{
get
@@ -1456,7 +1456,7 @@ namespace Avalonia.Controls
}
// Height currently available for cells this value is smaller. This height is reduced by the existence of ColumnHeaders
- // or a horizontal scrollbar. Layout is asynchronous so changes to the ColumnHeaders or the horizontal scrollbar are
+ // or a horizontal scrollbar. Layout is asynchronous so changes to the ColumnHeaders or the horizontal scrollbar are
// not reflected immediately.
internal double CellsHeight
{
@@ -1555,7 +1555,7 @@ namespace Avalonia.Controls
internal static double HorizontalGridLinesThickness => DATAGRID_horizontalGridLinesThickness;
- // the sum of the widths in pixels of the scrolling columns preceding
+ // the sum of the widths in pixels of the scrolling columns preceding
// the first displayed scrolling column
internal double HorizontalOffset
{
@@ -2083,20 +2083,20 @@ namespace Avalonia.Controls
}
///
- /// Measures the children of a to prepare for
- /// arranging them during the
- /// pass.
+ /// Measures the children of a to prepare for
+ /// arranging them during the
+ /// pass.
///
///
/// The size that the determines it needs during layout, based on its calculations of child object allocated sizes.
///
///
- /// The available size that this element can give to child elements. Indicates an upper limit that
+ /// The available size that this element can give to child elements. Indicates an upper limit that
/// child elements should not exceed.
///
protected override Size MeasureOverride(Size availableSize)
{
- // Delay layout until after the initial measure to avoid invalid calculations when the
+ // Delay layout until after the initial measure to avoid invalid calculations when the
// DataGrid is not part of the visual tree
if (!_measured)
{
@@ -2285,6 +2285,17 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Comparator class so we can sort list by the display index
+ ///
+ public class DisplayIndexComparer : IComparer
+ {
+ int IComparer.Compare(DataGridColumn x, DataGridColumn y)
+ {
+ return (x.DisplayIndexWithFiller < y.DisplayIndexWithFiller) ? -1 : 1;
+ }
+ }
+
///
/// Builds the visual tree for the column header when a new template is applied.
///
@@ -2309,8 +2320,11 @@ namespace Avalonia.Controls
ColumnsInternal.FillerColumn.IsRepresented = false;
}
_columnHeadersPresenter.OwningGrid = this;
- // Columns were added before before our Template was applied, add the ColumnHeaders now
- foreach (DataGridColumn column in ColumnsItemsInternal)
+
+ // Columns were added before our Template was applied, add the ColumnHeaders now
+ List sortedInternal = new List(ColumnsItemsInternal);
+ sortedInternal.Sort(new DisplayIndexComparer());
+ foreach (DataGridColumn column in sortedInternal)
{
InsertDisplayedColumnHeader(column);
}
@@ -3006,7 +3020,7 @@ namespace Avalonia.Controls
/// If the editing element has focus, this method will set focus to the DataGrid itself
/// in order to force the element to lose focus. It will then wait for the editing element's
/// LostFocus event, at which point it will perform the specified action.
- ///
+ ///
/// NOTE: It is important to understand that the specified action will be performed when the editing
/// element loses focus only if this method returns true. If it returns false, then the action
/// will not be performed later on, and should instead be performed by the caller, if necessary.
@@ -3065,7 +3079,7 @@ namespace Avalonia.Controls
{
if (!_scrollingByHeight)
{
- // Update layout when RowDetails are expanded or collapsed, just updating the vertical scroll bar is not enough
+ // Update layout when RowDetails are expanded or collapsed, just updating the vertical scroll bar is not enough
// since rows could be added or removed
InvalidateMeasure();
}
@@ -3278,7 +3292,7 @@ namespace Avalonia.Controls
{
// Current cell was reset because the commit deleted row(s).
// Since the user wants to change the current cell, we don't
- // want to end up with no current cell. We pick the last row
+ // want to end up with no current cell. We pick the last row
// in the grid which may be the 'new row'.
int lastSlot = LastVisibleSlot;
if (forCurrentCellChange &&
@@ -3336,7 +3350,7 @@ namespace Avalonia.Controls
if (_ignoreNextScrollBarsLayout)
{
_ignoreNextScrollBarsLayout = false;
- //
+ //
}
@@ -3393,7 +3407,7 @@ namespace Avalonia.Controls
}
// Now cellsWidth is the width potentially available for displaying data cells.
- // Now cellsHeight is the height potentially available for displaying data cells.
+ // Now cellsHeight is the height potentially available for displaying data cells.
bool needHorizScrollbar = false;
bool needVertScrollbar = false;
@@ -3418,7 +3432,7 @@ namespace Avalonia.Controls
Debug.Assert(cellsHeight >= 0);
needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true;
- if (vertScrollBarWidth > 0 &&
+ if (vertScrollBarWidth > 0 &&
allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
{
@@ -3458,7 +3472,7 @@ namespace Avalonia.Controls
// we compute the number of visible columns only after we set up the vertical scroll bar.
ComputeDisplayedColumns();
- if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) &&
+ if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) &&
allowHorizScrollbar &&
needVertScrollbar && !needHorizScrollbar &&
MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
@@ -3963,7 +3977,8 @@ namespace Avalonia.Controls
{
var errorList =
binding.ValidationErrors
- .SelectMany(ex => ValidationUtil.UnpackException(ex))
+ .SelectMany(ValidationUtil.UnpackException)
+ .Select(ValidationUtil.UnpackDataValidationException)
.ToList();
DataValidationErrors.SetErrors(editingElement, errorList);
@@ -4124,7 +4139,7 @@ namespace Avalonia.Controls
}
///
- /// Exits editing mode without trying to commit or revert the editing, and
+ /// Exits editing mode without trying to commit or revert the editing, and
/// without repopulating the edited row's cell.
///
//TODO TabStop
@@ -5103,9 +5118,9 @@ namespace Avalonia.Controls
{
if (ctrl || _editingColumnIndex == -1 || IsReadOnly)
{
- //Go to the next/previous control on the page when
+ //Go to the next/previous control on the page when
// - Ctrl key is used
- // - Potential current cell is not edited, or the datagrid is read-only.
+ // - Potential current cell is not edited, or the datagrid is read-only.
return false;
}
@@ -5516,11 +5531,11 @@ namespace Avalonia.Controls
// v---v
//|<|_____|###|>|
// ^ ^
- // min max
+ // min max
// we want to make the relative size of the thumb reflect the relative size of the viewing area
// viewportSize / (max + viewportSize) = cellsWidth / max
- // -> viewportSize = max * cellsWidth / (max - cellsWidth)
+ // -> viewportSize = max * cellsWidth / (max - cellsWidth)
// always zero
_hScrollBar.Minimum = 0;
@@ -5572,7 +5587,7 @@ namespace Avalonia.Controls
_hScrollBar.Maximum = 0;
if (_hScrollBar.IsVisible)
{
- // This will trigger a call to this method via Cells_SizeChanged for
+ // This will trigger a call to this method via Cells_SizeChanged for
// which no processing is needed.
_hScrollBar.IsVisible = false;
_ignoreNextScrollBarsLayout = true;
@@ -5591,14 +5606,14 @@ namespace Avalonia.Controls
// v---v
//|<|_____|###|>|
// ^ ^
- // min max
+ // min max
// we want to make the relative size of the thumb reflect the relative size of the viewing area
// viewportSize / (max + viewportSize) = cellsWidth / max
// -> viewportSize = max * cellsHeight / (totalVisibleHeight - cellsHeight)
// -> = max * cellsHeight / (totalVisibleHeight - cellsHeight)
// -> = max * cellsHeight / max
- // -> = cellsHeight
+ // -> = cellsHeight
// always zero
_vScrollBar.Minimum = 0;
@@ -5621,7 +5636,7 @@ namespace Avalonia.Controls
if (!_vScrollBar.IsVisible)
{
- // This will trigger a call to this method via Cells_SizeChanged for
+ // This will trigger a call to this method via Cells_SizeChanged for
// which no processing is needed.
_vScrollBar.IsVisible = true;
if (_vScrollBar.DesiredSize.Width == 0)
@@ -5637,7 +5652,7 @@ namespace Avalonia.Controls
_vScrollBar.Maximum = 0;
if (_vScrollBar.IsVisible)
{
- // This will trigger a call to this method via Cells_SizeChanged for
+ // This will trigger a call to this method via Cells_SizeChanged for
// which no processing is needed.
_vScrollBar.IsVisible = false;
_ignoreNextScrollBarsLayout = true;
@@ -5660,8 +5675,8 @@ namespace Avalonia.Controls
Debug.Assert(slot >= 0);
// Before changing selection, check if the current cell needs to be committed, and
- // check if the current row needs to be committed. If any of those two operations are required and fail,
- // do not change selection, and do not change current cell.
+ // check if the current row needs to be committed. If any of those two operations are required and fail,
+ // do not change selection, and do not change current cell.
bool wasInEdit = EditingColumnIndex != -1;
diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs
index 6ad58bf250..0d19f4c479 100644
--- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs
+++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs
@@ -3,14 +3,15 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-using Avalonia.Media;
using System;
using System.Diagnostics;
+using Avalonia.Layout;
+using Avalonia.Media;
namespace Avalonia.Controls.Primitives
{
///
- /// Used within the template of a to specify the
+ /// Used within the template of a to specify the
/// location in the control's visual tree where the rows are to be added.
///
public sealed class DataGridRowsPresenter : Panel
@@ -22,9 +23,10 @@ namespace Avalonia.Controls.Primitives
}
private double _measureHeightOffset = 0;
+
private double CalculateEstimatedAvailableHeight(Size availableSize)
{
- if(!Double.IsPositiveInfinity(availableSize.Height))
+ if (!Double.IsPositiveInfinity(availableSize.Height))
{
return availableSize.Height + _measureHeightOffset;
}
@@ -50,10 +52,10 @@ namespace Avalonia.Controls.Primitives
return base.ArrangeOverride(finalSize);
}
- if(OwningGrid.RowsPresenterAvailableSize.HasValue)
+ if (OwningGrid.RowsPresenterAvailableSize.HasValue)
{
var availableHeight = OwningGrid.RowsPresenterAvailableSize.Value.Height;
- if(!Double.IsPositiveInfinity(availableHeight))
+ if (!Double.IsPositiveInfinity(availableHeight))
{
_measureHeightOffset = finalSize.Height - availableHeight;
OwningGrid.RowsPresenterEstimatedAvailableHeight = finalSize.Height;
@@ -108,6 +110,18 @@ namespace Avalonia.Controls.Primitives
///
protected override Size MeasureOverride(Size availableSize)
{
+ if (double.IsInfinity(availableSize.Height))
+ {
+ if (VisualRoot is TopLevel topLevel)
+ {
+ double maxHeight = topLevel.IsArrangeValid ?
+ topLevel.Bounds.Height :
+ LayoutHelper.ApplyLayoutConstraints(topLevel, availableSize).Height;
+
+ availableSize = availableSize.WithHeight(maxHeight);
+ }
+ }
+
if (availableSize.Height == 0 || OwningGrid == null)
{
return base.MeasureOverride(availableSize);
diff --git a/src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs
index 31c42db076..47d1ac1c0b 100644
--- a/src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs
+++ b/src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs
@@ -352,19 +352,37 @@ namespace Avalonia.Controls.Utils
return null;
}
- PropertyInfo indexer = null;
string stringIndex = propertyPath.Substring(1, propertyPath.Length - 2);
- indexer = FindIndexerInMembers(type.GetDefaultMembers(), stringIndex, out index);
+ var indexer = FindIndexerInMembers(type.GetDefaultMembers(), stringIndex, out index);
if (indexer != null)
{
// We found the indexer, so return it.
return indexer;
}
- if (typeof(IList).IsAssignableFrom(type))
+ var elementType = type.GetElementType();
+ if (elementType == null)
+ {
+ var genericArguments = type.GetGenericArguments();
+ if (genericArguments.Length == 1)
+ {
+ elementType = genericArguments[0];
+ }
+ }
+
+ if (elementType != null)
{
// If the object is of type IList, try to use its default indexer.
- indexer = FindIndexerInMembers(typeof(IList).GetDefaultMembers(), stringIndex, out index);
+ if (typeof(IList<>).MakeGenericType(elementType) is Type genericList
+ && genericList.IsAssignableFrom(type))
+ {
+ indexer = FindIndexerInMembers(genericList.GetDefaultMembers(), stringIndex, out index);
+ }
+ if (typeof(IReadOnlyList<>).MakeGenericType(elementType) is Type genericReadOnlyList
+ && genericReadOnlyList.IsAssignableFrom(type))
+ {
+ indexer = FindIndexerInMembers(genericReadOnlyList.GetDefaultMembers(), stringIndex, out index);
+ }
}
return indexer;
diff --git a/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs b/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs
index e1ecb2c562..c36d9708c1 100644
--- a/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs
+++ b/src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs
@@ -80,19 +80,24 @@ namespace Avalonia.Controls.Utils
{
if (exception != null)
{
- var aggregate = exception as AggregateException;
- var exceptions = aggregate == null ?
- (IEnumerable)new[] { exception } :
- aggregate.InnerExceptions;
- var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
+ var exceptions = exception is AggregateException aggregate ?
+ aggregate.InnerExceptions :
+ (IEnumerable)new[] { exception };
- if (filtered.Count > 0)
- {
- return filtered;
- }
+ return exceptions.Where(x => !(x is BindingChainException)).ToList();
}
- return null;
+ return Array.Empty();
+ }
+
+ public static object UnpackDataValidationException(Exception exception)
+ {
+ if (exception is DataValidationException dataValidationException)
+ {
+ return dataValidationException.ErrorData;
+ }
+
+ return exception;
}
///
diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt
index 7488ef8149..e5adc8c6ed 100644
--- a/src/Avalonia.Controls/ApiCompatBaseline.txt
+++ b/src/Avalonia.Controls/ApiCompatBaseline.txt
@@ -1,5 +1,6 @@
Compat issues with assembly Avalonia.Controls:
-InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show()' is present in the contract but not in the implementation.
-MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show()' does not exist in the implementation but it does exist in the contract.
-InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the implementation but not in the contract.
-Total Issues: 3
+MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
+MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
+Total Issues: 4
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index e94d00b2ff..c779e4b0cb 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Controls
/// A button control.
///
[PseudoClasses(":pressed")]
- public class Button : ContentControl
+ public class Button : ContentControl, ICommandSource
{
///
/// Defines the property.
@@ -80,6 +80,7 @@ namespace Avalonia.Controls
private ICommand _command;
private bool _commandCanExecute = true;
+ private KeyGesture _hotkey;
///
/// Initializes static members of the class.
@@ -207,6 +208,11 @@ namespace Avalonia.Controls
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
+ if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
+ {
+ HotKey = _hotkey;
+ }
+
base.OnAttachedToLogicalTree(e);
if (Command != null)
@@ -217,6 +223,13 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
+ // This will cause the hotkey manager to dispose the observer and the reference to this control
+ if (HotKey != null)
+ {
+ _hotkey = HotKey;
+ HotKey = null;
+ }
+
base.OnDetachedFromLogicalTree(e);
if (Command != null)
@@ -492,5 +505,7 @@ namespace Avalonia.Controls
{
PseudoClasses.Set(":pressed", isPressed);
}
+
+ void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e);
}
}
diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
index 7c259f0a09..af3fdbd662 100644
--- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
@@ -11,6 +11,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
+using Avalonia.Layout;
namespace Avalonia.Controls
{
@@ -209,6 +210,18 @@ namespace Avalonia.Controls
TextBox.UseFloatingWatermarkProperty.AddOwner();
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty HorizontalContentAlignmentProperty =
+ ContentControl.HorizontalContentAlignmentProperty.AddOwner();
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty VerticalContentAlignmentProperty =
+ ContentControl.VerticalContentAlignmentProperty.AddOwner();
+
///
/// Gets or sets the date to display.
///
@@ -364,6 +377,25 @@ namespace Avalonia.Controls
set { SetValue(UseFloatingWatermarkProperty, value); }
}
+
+ ///
+ /// Gets or sets the horizontal alignment of the content within the control.
+ ///
+ public HorizontalAlignment HorizontalContentAlignment
+ {
+ get => GetValue(HorizontalContentAlignmentProperty);
+ set => SetValue(HorizontalContentAlignmentProperty, value);
+ }
+
+ ///
+ /// Gets or sets the vertical alignment of the content within the control.
+ ///
+ public VerticalAlignment VerticalContentAlignment
+ {
+ get => GetValue(VerticalContentAlignmentProperty);
+ set => SetValue(VerticalContentAlignmentProperty, value);
+ }
+
///
/// Occurs when the drop-down
/// is closed.
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index 7f2acb58fe..20ca41bc57 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -188,7 +188,7 @@ namespace Avalonia.Controls
return;
if (e.Key == Key.F4 ||
- ((e.Key == Key.Down || e.Key == Key.Up) && ((e.KeyModifiers & KeyModifiers.Alt) != 0)))
+ ((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasFlagCustom(KeyModifiers.Alt)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index 5e17182f3e..fb8080f0d4 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -246,7 +246,7 @@ namespace Avalonia.Controls
///
/// Opens the menu.
///
- public override void Open() => throw new NotSupportedException();
+ public override void Open() => Open(null);
///
/// Opens a context menu on the specified control.
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index 522103c7bd..ca0e9d48b8 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
@@ -61,7 +61,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
public virtual PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1);
- public virtual void SetCursor(IPlatformHandle cursor)
+ public virtual void SetCursor(ICursorImpl cursor)
{
}
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index 6357ec98a8..66266c3b61 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/src/Avalonia.Controls/Grid.cs
@@ -637,7 +637,7 @@ namespace Avalonia.Controls
///
internal bool MeasureOverrideInProgress
{
- get { return (CheckFlagsAnd(Flags.MeasureOverrideInProgress)); }
+ get { return CheckFlags(Flags.MeasureOverrideInProgress); }
set { SetFlags(value, Flags.MeasureOverrideInProgress); }
}
@@ -646,7 +646,7 @@ namespace Avalonia.Controls
///
internal bool ArrangeOverrideInProgress
{
- get { return (CheckFlagsAnd(Flags.ArrangeOverrideInProgress)); }
+ get { return CheckFlags(Flags.ArrangeOverrideInProgress); }
set { SetFlags(value, Flags.ArrangeOverrideInProgress); }
}
@@ -2350,25 +2350,12 @@ namespace Avalonia.Controls
}
///
- /// CheckFlagsAnd returns true if all the flags in the
+ /// CheckFlags returns true if all the flags in the
/// given bitmask are set on the object.
///
- private bool CheckFlagsAnd(Flags flags)
+ private bool CheckFlags(Flags flags)
{
- return ((_flags & flags) == flags);
- }
-
- ///
- /// CheckFlagsOr returns true if at least one flag in the
- /// given bitmask is set.
- ///
- ///
- /// If no bits are set in the given bitmask, the method returns
- /// true.
- ///
- private bool CheckFlagsOr(Flags flags)
- {
- return (flags == 0 || (_flags & flags) != 0);
+ return _flags.HasFlagCustom(flags);
}
private static void OnShowGridLinesPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
@@ -2535,7 +2522,7 @@ namespace Avalonia.Controls
///
private bool CellsStructureDirty
{
- get { return (!CheckFlagsAnd(Flags.ValidCellsStructure)); }
+ get { return !CheckFlags(Flags.ValidCellsStructure); }
set { SetFlags(!value, Flags.ValidCellsStructure); }
}
@@ -2544,7 +2531,7 @@ namespace Avalonia.Controls
///
private bool ListenToNotifications
{
- get { return (CheckFlagsAnd(Flags.ListenToNotifications)); }
+ get { return CheckFlags(Flags.ListenToNotifications); }
set { SetFlags(value, Flags.ListenToNotifications); }
}
@@ -2553,7 +2540,7 @@ namespace Avalonia.Controls
///
private bool SizeToContentU
{
- get { return (CheckFlagsAnd(Flags.SizeToContentU)); }
+ get { return CheckFlags(Flags.SizeToContentU); }
set { SetFlags(value, Flags.SizeToContentU); }
}
@@ -2562,7 +2549,7 @@ namespace Avalonia.Controls
///
private bool SizeToContentV
{
- get { return (CheckFlagsAnd(Flags.SizeToContentV)); }
+ get { return CheckFlags(Flags.SizeToContentV); }
set { SetFlags(value, Flags.SizeToContentV); }
}
@@ -2571,7 +2558,7 @@ namespace Avalonia.Controls
///
private bool HasStarCellsU
{
- get { return (CheckFlagsAnd(Flags.HasStarCellsU)); }
+ get { return CheckFlags(Flags.HasStarCellsU); }
set { SetFlags(value, Flags.HasStarCellsU); }
}
@@ -2580,7 +2567,7 @@ namespace Avalonia.Controls
///
private bool HasStarCellsV
{
- get { return (CheckFlagsAnd(Flags.HasStarCellsV)); }
+ get { return CheckFlags(Flags.HasStarCellsV); }
set { SetFlags(value, Flags.HasStarCellsV); }
}
@@ -2589,7 +2576,7 @@ namespace Avalonia.Controls
///
private bool HasGroup3CellsInAutoRows
{
- get { return (CheckFlagsAnd(Flags.HasGroup3CellsInAutoRows)); }
+ get { return CheckFlags(Flags.HasGroup3CellsInAutoRows); }
set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); }
}
@@ -2803,10 +2790,10 @@ namespace Avalonia.Controls
internal LayoutTimeSizeType SizeTypeU;
internal LayoutTimeSizeType SizeTypeV;
internal int Next;
- internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } }
- internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } }
- internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } }
- internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } }
+ internal bool IsStarU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Star);
+ internal bool IsAutoU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Auto);
+ internal bool IsStarV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Star);
+ internal bool IsAutoV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Auto);
}
///
diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs
index 95752e7875..b76b7fbed5 100644
--- a/src/Avalonia.Controls/HotkeyManager.cs
+++ b/src/Avalonia.Controls/HotkeyManager.cs
@@ -12,18 +12,21 @@ namespace Avalonia.Controls
class HotkeyCommandWrapper : ICommand
{
- public HotkeyCommandWrapper(IControl control)
+ public HotkeyCommandWrapper(ICommandSource control)
{
- Control = control;
+ CommandSource = control;
}
- public readonly IControl Control;
+ public readonly ICommandSource CommandSource;
- private ICommand GetCommand() => Control.GetValue(Button.CommandProperty);
+ private ICommand GetCommand() => CommandSource.Command;
- public bool CanExecute(object parameter) => GetCommand()?.CanExecute(parameter) ?? false;
+ public bool CanExecute(object parameter) =>
+ CommandSource.Command?.CanExecute(CommandSource.CommandParameter) == true
+ && CommandSource.IsEffectivelyEnabled;
- public void Execute(object parameter) => GetCommand()?.Execute(parameter);
+ public void Execute(object parameter) =>
+ GetCommand()?.Execute(CommandSource.CommandParameter);
#pragma warning disable 67 // Event not used
public event EventHandler CanExecuteChanged;
@@ -44,7 +47,7 @@ namespace Avalonia.Controls
public Manager(IControl control)
{
_control = control;
- _wrapper = new HotkeyCommandWrapper(_control);
+ _wrapper = new HotkeyCommandWrapper(_control as ICommandSource);
}
public void Init()
@@ -84,7 +87,7 @@ namespace Avalonia.Controls
{
if (_root != null && _hotkey != null)
{
- _binding = new KeyBinding() {Gesture = _hotkey, Command = _wrapper};
+ _binding = new KeyBinding() { Gesture = _hotkey, Command = _wrapper };
_root.KeyBindings.Add(_binding);
}
}
@@ -102,8 +105,13 @@ namespace Avalonia.Controls
HotKeyProperty.Changed.Subscribe(args =>
{
var control = args.Sender as IControl;
- if (args.OldValue != null|| control == null)
+ if (args.OldValue != null || control == null || !(control is ICommandSource))
+ {
+ Logging.Logger.TryGet(Logging.LogEventLevel.Warning, Logging.LogArea.Control)?.
+ Log(control, $"The element {args.Sender.GetType().Name} does not support binding a HotKey ({args.NewValue}).");
return;
+ }
+
new Manager(control).Init();
});
}
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index d1b8038581..b6b3cc786c 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -135,8 +135,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
- (e.KeyModifiers & KeyModifiers.Shift) != 0,
- (e.KeyModifiers & KeyModifiers.Control) != 0);
+ e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
+ e.KeyModifiers.HasFlagCustom(KeyModifiers.Control));
}
}
@@ -154,8 +154,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
- (e.KeyModifiers & KeyModifiers.Shift) != 0,
- (e.KeyModifiers & KeyModifiers.Control) != 0,
+ e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
+ e.KeyModifiers.HasFlagCustom(KeyModifiers.Control),
point.Properties.IsRightButtonPressed);
}
}
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index 7d4fef009d..94099a970e 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Controls
/// A menu item control.
///
[PseudoClasses(":separator", ":icon", ":open", ":pressed", ":selected")]
- public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable
+ public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable, ICommandSource
{
///
/// Defines the property.
@@ -102,6 +102,8 @@ namespace Avalonia.Controls
private ICommand? _command;
private bool _commandCanExecute = true;
private Popup? _popup;
+ private KeyGesture _hotkey;
+ private bool _isEmbeddedInMenu;
///
/// Initializes static members of the class.
@@ -111,6 +113,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach