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 ![image](https://user-images.githubusercontent.com/4672627/84707589-5b69a880-af35-11ea-87a6-7ad57a31d314.png) +([Synfonia](https://github.com/jmacato/Synfonia)) + ![image](https://user-images.githubusercontent.com/4672627/85069644-d8419000-b18a-11ea-8732-be9055bb61fd.PNG) +([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery)) ![image](https://user-images.githubusercontent.com/4672627/85069659-dc6dad80-b18a-11ea-8375-39ef95315b5c.PNG) +([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery)) ![image](https://user-images.githubusercontent.com/4672627/84708947-c3b98980-af37-11ea-8c9d-503334615bbf.png) +([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/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 Surfaces { get; } + public IEnumerable Surfaces => new object[] { _gl, _framebuffer }; public IRenderer CreateRenderer(IRenderRoot root) { @@ -95,7 +107,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform { _view.Visibility = ViewStates.Invisible; } - + public void Invalidate(Rect rect) { if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate(); @@ -196,7 +208,22 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IPopupImpl CreatePopup() => null; public Action LostFocus { get; set; } + public Action TransparencyLevelChanged { get; set; } - ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(_view.Holder.Surface); + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; + + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); + + 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) + { + throw new NotImplementedException(); + } } } diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs index 1179ce9235..426b221738 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs @@ -5,14 +5,14 @@ using Android.Runtime; using Android.Views; using Android.Views.InputMethods; using Avalonia.Android.Platform.Input; +using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; -using Avalonia.Platform; namespace Avalonia.Android.Platform.Specific.Helpers { - public class AndroidKeyboardEventsHelper : IDisposable where TView :ITopLevelImpl, IAndroidView + internal class AndroidKeyboardEventsHelper : IDisposable where TView : TopLevelImpl, IAndroidView { private TView _view; private IInputElement _lastFocusedElement; @@ -46,9 +46,11 @@ namespace Avalonia.Android.Platform.Specific.Helpers var rawKeyEvent = new RawKeyEventArgs( AndroidKeyboardDevice.Instance, - Convert.ToUInt32(e.EventTime), + Convert.ToUInt64(e.EventTime), + _view.InputRoot, e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, - AndroidKeyboardDevice.ConvertKey(e.KeyCode), GetModifierKeys(e)); + AndroidKeyboardDevice.ConvertKey(e.KeyCode), GetModifierKeys(e)); + _view.Input(rawKeyEvent); if (e.Action == KeyEventActions.Down && e.UnicodeChar >= 32) @@ -56,6 +58,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers var rawTextEvent = new RawTextInputEventArgs( AndroidKeyboardDevice.Instance, Convert.ToUInt32(e.EventTime), + _view.InputRoot, Convert.ToChar(e.UnicodeChar).ToString() ); _view.Input(rawTextEvent); diff --git a/src/Android/Avalonia.Android/Resources/Resource.Designer.cs b/src/Android/Avalonia.Android/Resources/Resource.Designer.cs deleted file mode 100644 index 80cbbc51ec..0000000000 --- a/src/Android/Avalonia.Android/Resources/Resource.Designer.cs +++ /dev/null @@ -1,60 +0,0 @@ -#pragma warning disable 1591 -//------------------------------------------------------------------------------ -// -// 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. -// -//------------------------------------------------------------------------------ - -[assembly: global::Android.Runtime.ResourceDesignerAttribute("Avalonia.Android.Resource", IsApplication=false)] - -namespace Avalonia.Android -{ - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] - public partial class Resource - { - - static Resource() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - public partial class Attribute - { - - static Attribute() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Attribute() - { - } - } - - public partial class String - { - - // aapt resource value: 0x7f020001 - public static int ApplicationName = 2130837505; - - // aapt resource value: 0x7f020000 - public static int Hello = 2130837504; - - static String() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private String() - { - } - } - } -} -#pragma warning restore 1591 diff --git a/src/Android/Avalonia.Android/SystemDialogImpl.cs b/src/Android/Avalonia.Android/SystemDialogImpl.cs index a8d201d66e..1ed1f688b1 100644 --- a/src/Android/Avalonia.Android/SystemDialogImpl.cs +++ b/src/Android/Avalonia.Android/SystemDialogImpl.cs @@ -2,18 +2,17 @@ using System; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Platform; -using Avalonia.Platform; namespace Avalonia.Android { internal class SystemDialogImpl : ISystemDialogImpl { - public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + public Task ShowFileDialogAsync(FileDialog dialog, Window parent) { throw new NotImplementedException(); } - public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) { throw new NotImplementedException(); } diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index f880e48282..4f49f3a863 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -16,7 +16,7 @@ Resources\Resource.Designer.cs Off False - v9.0 + v10.0 Properties\AndroidManifest.xml @@ -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(IsSelectedProperty); PressedMixin.Attach(); CommandProperty.Changed.Subscribe(CommandChanged); + CommandParameterProperty.Changed.Subscribe(CommandParameterChanged); FocusableProperty.OverrideDefaultValue(true); HeaderProperty.Changed.AddClassHandler((x, e) => x.HeaderChanged(e)); IconProperty.Changed.AddClassHandler((x, e) => x.IconChanged(e)); @@ -145,7 +148,7 @@ namespace Avalonia.Controls { var parent = x as Control; return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ?? - Observable.Return(null); + Observable.Return(null); }); this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope); @@ -273,7 +276,7 @@ namespace Avalonia.Controls public bool IsTopLevel => Parent is Menu; /// - bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false; + bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false; /// IMenuElement? IMenuItem.Parent => Parent as IMenuElement; @@ -308,7 +311,7 @@ namespace Avalonia.Controls .Select(x => x.ContainerControl) .OfType(); } - } + } /// /// Opens the submenu. @@ -335,18 +338,51 @@ namespace Avalonia.Controls return new MenuItemContainerGenerator(this); } + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + + if (!_isEmbeddedInMenu) + { + //Normally the Menu's IMenuInteractionHandler is sending the click events for us + //However when the item is not embedded into a menu we need to send them ourselves. + RaiseEvent(new RoutedEventArgs(ClickEvent)); + } + } + 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) { Command.CanExecuteChanged += CanExecuteChanged; } + + var parent = Parent; + + while (parent is MenuItem) + { + parent = parent.Parent; + } + + _isEmbeddedInMenu = parent is IMenu; } 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) @@ -493,6 +529,18 @@ namespace Avalonia.Controls } } + /// + /// Called when the property changes. + /// + /// The event args. + private static void CommandParameterChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.Sender is MenuItem menuItem) + { + menuItem.CanExecuteChanged(menuItem, EventArgs.Empty); + } + } + /// /// Called when the event fires. /// @@ -609,6 +657,8 @@ namespace Avalonia.Controls SelectedItem = null; } + void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e); + /// /// A dependency resolver which returns a . /// diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 76197b62ad..2eaf24d2f2 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -150,7 +150,7 @@ namespace Avalonia.Controls void CanExecuteChanged() { - IsEnabled = _command?.CanExecute(null) ?? true; + IsEnabled = _command?.CanExecute(CommandParameter) ?? true; } public bool HasClickHandlers => Click != null; diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index aae041071d..b6b71644a3 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Layout; using Avalonia.Threading; using Avalonia.Utilities; @@ -105,6 +106,19 @@ namespace Avalonia.Controls public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register(nameof(Watermark)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty HorizontalContentAlignmentProperty = + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty VerticalContentAlignmentProperty = + ContentControl.VerticalContentAlignmentProperty.AddOwner(); + private IDisposable _textBoxTextChangedSubscription; private double _value; @@ -256,6 +270,25 @@ namespace Avalonia.Controls set { SetValue(WatermarkProperty, 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); + } + /// /// Initializes new instance of class. /// diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 5cbd3698b6..e3e9e84d7e 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -355,7 +355,13 @@ namespace Avalonia.Controls.Platform } else if (!item.IsPointerOverSubMenu) { - item.IsSubMenuOpen = false; + DelayRun(() => + { + if (!item.IsPointerOverSubMenu) + { + item.IsSubMenuOpen = false; + } + }, MenuShowDelay); } } } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 7514f214aa..09f38042a1 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -98,7 +98,7 @@ namespace Avalonia.Platform /// Sets the cursor associated with the toplevel. /// /// The cursor. Use null for default cursor - void SetCursor(IPlatformHandle cursor); + void SetCursor(ICursorImpl cursor); /// /// Gets or sets a method called when the underlying implementation is destroyed. diff --git a/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs b/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs new file mode 100644 index 0000000000..9c29415a6a --- /dev/null +++ b/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs @@ -0,0 +1,11 @@ +using Avalonia.Input; +using Avalonia.Input.TextInput; +using Avalonia.Platform; + +namespace Avalonia.Controls.Platform +{ + public interface ITopLevelImplWithTextInputMethod : ITopLevelImpl + { + public ITextInputMethodImpl TextInputMethod { get; } + } +} diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 9d644aaa00..7e1a7378c7 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -73,20 +73,20 @@ namespace Avalonia.Platform { if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None) return effect; // No need to check for the modifiers. - if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(RawInputModifiers.Alt)) + if (effect.HasFlagCustom(DragDropEffects.Link) && modifiers.HasFlagCustom(RawInputModifiers.Alt)) return DragDropEffects.Link; - if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(RawInputModifiers.Control)) + if (effect.HasFlagCustom(DragDropEffects.Copy) && modifiers.HasFlagCustom(RawInputModifiers.Control)) return DragDropEffects.Copy; return DragDropEffects.Move; } private StandardCursorType GetCursorForDropEffect(DragDropEffects effects) { - if (effects.HasFlag(DragDropEffects.Copy)) + if (effects.HasFlagCustom(DragDropEffects.Copy)) return StandardCursorType.DragCopy; - if (effects.HasFlag(DragDropEffects.Move)) + if (effects.HasFlagCustom(DragDropEffects.Move)) return StandardCursorType.DragMove; - if (effects.HasFlag(DragDropEffects.Link)) + if (effects.HasFlagCustom(DragDropEffects.Link)) return StandardCursorType.DragLink; return StandardCursorType.No; } @@ -161,7 +161,7 @@ namespace Avalonia.Platform void CheckDraggingAccepted(RawInputModifiers changedMouseButton) { - if (_initialInputModifiers.Value.HasFlag(changedMouseButton)) + if (_initialInputModifiers.Value.HasFlagCustom(changedMouseButton)) { var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers); UpdateCursor(null, DragDropEffects.None); diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 078d8050bf..6bbb1c13bf 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Linq; +using Avalonia.Input.TextInput; using Avalonia.Media; using Avalonia.Metadata; using Avalonia.Threading; @@ -378,19 +379,23 @@ namespace Avalonia.Controls.Presenters if (_caretBlink) { - var charPos = FormattedText.HitTestTextPosition(CaretIndex); - var x = Math.Floor(charPos.X) + 0.5; - var y = Math.Floor(charPos.Y) + 0.5; - var b = Math.Ceiling(charPos.Bottom) - 0.5; - + var (p1, p2) = GetCaretPoints(); context.DrawLine( new Pen(caretBrush, 1), - new Point(x, y), - new Point(x, b)); + p1, p2); } } } + (Point, Point) GetCaretPoints() + { + var charPos = FormattedText.HitTestTextPosition(CaretIndex); + var x = Math.Floor(charPos.X) + 0.5; + var y = Math.Floor(charPos.Y) + 0.5; + var b = Math.Ceiling(charPos.Bottom) - 0.5; + return (new Point(x, y), new Point(x, b)); + } + public void ShowCaret() { _caretBlink = true; @@ -538,5 +543,11 @@ namespace Avalonia.Controls.Presenters _caretBlink = !_caretBlink; InvalidateVisual(); } + + internal Rect GetCursorRectangle() + { + var (p1, p2) = GetCaretPoints(); + return new Rect(p1, p2); + } } } diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs index 752eedb68a..1d655bb691 100644 --- a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.Media; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.VisualTree; @@ -17,6 +18,11 @@ namespace Avalonia.Controls.Primitives { public IInputElement? InputPassThroughElement { get; set; } + static LightDismissOverlayLayer() + { + BackgroundProperty.OverrideDefaultValue(Brushes.Transparent); + } + /// /// Returns the light dismiss overlay for a specified visual. /// diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index aed7dff0fe..545034239f 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -253,9 +253,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning { public static void ValidateEdge(this PopupAnchor edge) { - if (((edge & PopupAnchor.Left) != 0 && (edge & PopupAnchor.Right) != 0) - || - ((edge & PopupAnchor.Top) != 0 && (edge & PopupAnchor.Bottom) != 0)) + if (edge.HasFlagCustom(PopupAnchor.Left) && edge.HasFlagCustom(PopupAnchor.Right) || + edge.HasFlagCustom(PopupAnchor.Top) && edge.HasFlagCustom(PopupAnchor.Bottom)) throw new ArgumentException("Opposite edges specified"); } @@ -266,25 +265,25 @@ namespace Avalonia.Controls.Primitives.PopupPositioning public static PopupAnchor Flip(this PopupAnchor edge) { - var hmask = PopupAnchor.Left | PopupAnchor.Right; - var vmask = PopupAnchor.Top | PopupAnchor.Bottom; - if ((edge & hmask) != 0) - edge ^= hmask; - if ((edge & vmask) != 0) - edge ^= vmask; + if (edge.HasFlagCustom(PopupAnchor.HorizontalMask)) + edge ^= PopupAnchor.HorizontalMask; + + if (edge.HasFlagCustom(PopupAnchor.VerticalMask)) + edge ^= PopupAnchor.VerticalMask; + return edge; } public static PopupAnchor FlipX(this PopupAnchor edge) { - if ((edge & PopupAnchor.HorizontalMask) != 0) + if (edge.HasFlagCustom(PopupAnchor.HorizontalMask)) edge ^= PopupAnchor.HorizontalMask; return edge; } public static PopupAnchor FlipY(this PopupAnchor edge) { - if ((edge & PopupAnchor.VerticalMask) != 0) + if (edge.HasFlagCustom(PopupAnchor.VerticalMask)) edge ^= PopupAnchor.VerticalMask; return edge; } diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index 7f1dbdf592..80b32b5ef8 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs @@ -42,16 +42,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning private static Point GetAnchorPoint(Rect anchorRect, PopupAnchor edge) { double x, y; - if ((edge & PopupAnchor.Left) != 0) + if (edge.HasFlagCustom(PopupAnchor.Left)) x = anchorRect.X; - else if ((edge & PopupAnchor.Right) != 0) + else if (edge.HasFlagCustom(PopupAnchor.Right)) x = anchorRect.Right; else x = anchorRect.X + anchorRect.Width / 2; - if ((edge & PopupAnchor.Top) != 0) + if (edge.HasFlagCustom(PopupAnchor.Top)) y = anchorRect.Y; - else if ((edge & PopupAnchor.Bottom) != 0) + else if (edge.HasFlagCustom(PopupAnchor.Bottom)) y = anchorRect.Bottom; else y = anchorRect.Y + anchorRect.Height / 2; @@ -61,16 +61,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning private static Point Gravitate(Point anchorPoint, Size size, PopupGravity gravity) { double x, y; - if ((gravity & PopupGravity.Left) != 0) + if (gravity.HasFlagCustom(PopupGravity.Left)) x = -size.Width; - else if ((gravity & PopupGravity.Right) != 0) + else if (gravity.HasFlagCustom(PopupGravity.Right)) x = 0; else x = -size.Width / 2; - if ((gravity & PopupGravity.Top) != 0) + if (gravity.HasFlagCustom(PopupGravity.Top)) y = -size.Height; - else if ((gravity & PopupGravity.Bottom) != 0) + else if (gravity.HasFlagCustom(PopupGravity.Bottom)) y = 0; else y = -size.Height / 2; @@ -125,21 +125,13 @@ namespace Avalonia.Controls.Primitives.PopupPositioning bool FitsInBounds(Rect rc, PopupAnchor edge = PopupAnchor.AllMask) { - if ((edge & PopupAnchor.Left) != 0 - && rc.X < bounds.X) - return false; - - if ((edge & PopupAnchor.Top) != 0 - && rc.Y < bounds.Y) - return false; - - if ((edge & PopupAnchor.Right) != 0 - && rc.Right > bounds.Right) - return false; - - if ((edge & PopupAnchor.Bottom) != 0 - && rc.Bottom > bounds.Bottom) + if (edge.HasFlagCustom(PopupAnchor.Left) && rc.X < bounds.X || + edge.HasFlagCustom(PopupAnchor.Top) && rc.Y < bounds.Y || + edge.HasFlagCustom(PopupAnchor.Right) && rc.Right > bounds.Right || + edge.HasFlagCustom(PopupAnchor.Bottom) && rc.Bottom > bounds.Bottom) + { return false; + } return true; } @@ -155,7 +147,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning // If flipping geometry and anchor is allowed and helps, use the flipped one, // otherwise leave it as is if (!FitsInBounds(geo, PopupAnchor.HorizontalMask) - && (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipX) != 0) + && constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipX)) { var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX()); if (FitsInBounds(flipped, PopupAnchor.HorizontalMask)) @@ -163,7 +155,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } // If sliding is allowed, try moving the rect into the bounds - if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideX) != 0) + if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideX)) { geo = geo.WithX(Math.Max(geo.X, bounds.X)); if (geo.Right > bounds.Right) @@ -171,7 +163,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } // Resize the rect horizontally if allowed. - if ((constraintAdjustment & PopupPositionerConstraintAdjustment.ResizeX) != 0) + if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeX)) { var unconstrainedRect = geo; @@ -194,7 +186,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning // If flipping geometry and anchor is allowed and helps, use the flipped one, // otherwise leave it as is if (!FitsInBounds(geo, PopupAnchor.VerticalMask) - && (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipY) != 0) + && constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipY)) { var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY()); if (FitsInBounds(flipped, PopupAnchor.VerticalMask)) @@ -202,7 +194,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } // If sliding is allowed, try moving the rect into the bounds - if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideY) != 0) + if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideY)) { geo = geo.WithY(Math.Max(geo.Y, bounds.Y)); if (geo.Bottom > bounds.Bottom) @@ -210,7 +202,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } // Resize the rect vertically if allowed. - if ((constraintAdjustment & PopupPositionerConstraintAdjustment.ResizeY) != 0) + if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeY)) { var unconstrainedRect = geo; diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 280f46be9f..2cd69793dc 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -321,7 +321,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets a value indicating whether is set. /// - protected bool AlwaysSelected => (SelectionMode & SelectionMode.AlwaysSelected) != 0; + protected bool AlwaysSelected => SelectionMode.HasFlagCustom(SelectionMode.AlwaysSelected); /// public override void BeginInit() @@ -487,7 +487,7 @@ namespace Avalonia.Controls.Primitives if (ItemCount > 0 && Match(keymap.SelectAll) && - SelectionMode.HasFlag(SelectionMode.Multiple)) + SelectionMode.HasFlagCustom(SelectionMode.Multiple)) { Selection.SelectAll(); e.Handled = true; @@ -577,8 +577,8 @@ namespace Avalonia.Controls.Primitives } var mode = SelectionMode; - var multi = (mode & SelectionMode.Multiple) != 0; - var toggle = (toggleModifier || (mode & SelectionMode.Toggle) != 0); + var multi = mode.HasFlagCustom(SelectionMode.Multiple); + var toggle = toggleModifier || mode.HasFlagCustom(SelectionMode.Toggle); var range = multi && rangeModifier; if (!select) diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index d8d3450c6f..03a84e26f2 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -67,14 +67,11 @@ namespace Avalonia.Controls.Primitives { get { - if (IsPopup) - return null; var rv = FindLayer(); if (rv == null) { rv = new LightDismissOverlayLayer { - Background = Brushes.Transparent, IsVisible = false }; diff --git a/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs b/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs index fd33eeaf39..4eadced423 100644 --- a/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs +++ b/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs @@ -53,8 +53,8 @@ namespace Avalonia.Controls { return _owner.GetElementImpl( index, - (options & ElementRealizationOptions.ForceCreate) != 0, - (options & ElementRealizationOptions.SuppressAutoRecycle) != 0); + options.HasFlagCustom(ElementRealizationOptions.ForceCreate), + options.HasFlagCustom(ElementRealizationOptions.SuppressAutoRecycle)); } protected override object GetItemAtCore(int index) => _owner.ItemsSourceView.GetAt(index); diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index fe4b24099f..74de33529a 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -11,7 +11,6 @@ using Avalonia.Utilities; namespace Avalonia.Controls { - /// /// Enum which describes how to position the ticks in a . /// @@ -84,6 +83,9 @@ namespace Avalonia.Controls private IDisposable _increaseButtonSubscription; private IDisposable _increaseButtonReleaseDispose; private IDisposable _pointerMovedDispose; + private IDisposable _trackOnKeyDownDispose; + + private const double Tolerance = 0.0001; /// /// Initializes static members of the class. @@ -95,7 +97,7 @@ namespace Avalonia.Controls Thumb.DragStartedEvent.AddClassHandler((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble); - + ValueProperty.OverrideMetadata(new DirectPropertyMetadata(enableDataValidation: true)); } @@ -157,13 +159,14 @@ namespace Avalonia.Controls protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - + _decreaseButtonPressDispose?.Dispose(); _decreaseButtonReleaseDispose?.Dispose(); _increaseButtonSubscription?.Dispose(); _increaseButtonReleaseDispose?.Dispose(); _pointerMovedDispose?.Dispose(); - + _trackOnKeyDownDispose?.Dispose(); + _decreaseButton = e.NameScope.Find