Browse Source

Merge branch 'master' into double-tapped-event-args

pull/5797/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
2e390d3ab5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/ReactiveUI.props
  2. 4
      native/Avalonia.Native/src/OSX/window.mm
  3. 17
      nukebuild/Build.cs
  4. 7
      nukebuild/BuildParameters.cs
  5. 2
      readme.md
  6. 6
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  7. 2
      samples/ControlCatalog/ControlCatalog.csproj
  8. 6
      samples/ControlCatalog/MainView.xaml
  9. 102
      samples/ControlCatalog/Pages/ContextFlyoutPage.axaml
  10. 45
      samples/ControlCatalog/Pages/ContextFlyoutPage.axaml.cs
  11. 264
      samples/ControlCatalog/Pages/FlyoutsPage.axaml
  12. 81
      samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
  13. 2
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  14. 6
      samples/ControlCatalog/Pages/TextBoxPage.xaml.cs
  15. 78
      samples/ControlCatalog/ViewModels/ContextFlyoutPageViewModel.cs
  16. 3
      samples/RenderDemo/MainWindow.xaml
  17. 89
      samples/RenderDemo/Pages/PathMeasurementPage.cs
  18. 96
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  19. 4
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  20. 12
      src/Android/Avalonia.Android/IInitEditorInfo.cs
  21. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  22. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  23. 46
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  24. 83
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  25. 42
      src/Android/Avalonia.Android/SoftKeyboardListner.cs
  26. 28
      src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
  27. 4
      src/Avalonia.Base/Data/BindingValue.cs
  28. 54
      src/Avalonia.Base/EnumExtensions.cs
  29. 15
      src/Avalonia.Base/Utilities/MathUtilities.cs
  30. 4
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  31. 12
      src/Avalonia.Build.Tasks/DeterministicIdGenerator.cs
  32. 4
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  33. 2
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  34. 5
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  35. 1
      src/Avalonia.Controls/ApiCompatBaseline.txt
  36. 52
      src/Avalonia.Controls/Button.cs
  37. 73
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  38. 2
      src/Avalonia.Controls/ComboBox.cs
  39. 20
      src/Avalonia.Controls/Control.cs
  40. 16
      src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs
  41. 50
      src/Avalonia.Controls/Flyouts/Flyout.cs
  42. 504
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  43. 77
      src/Avalonia.Controls/Flyouts/FlyoutPlacementMode.cs
  44. 33
      src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs
  45. 24
      src/Avalonia.Controls/Flyouts/FlyoutShowMode.cs
  46. 75
      src/Avalonia.Controls/Flyouts/MenuFlyout.cs
  47. 55
      src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs
  48. 10
      src/Avalonia.Controls/Grid.cs
  49. 22
      src/Avalonia.Controls/GridSplitter.cs
  50. 8
      src/Avalonia.Controls/ListBox.cs
  51. 24
      src/Avalonia.Controls/NativeControlHost.cs
  52. 2
      src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
  53. 12
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  54. 14
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  55. 12
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  56. 36
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  57. 12
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  58. 2
      src/Avalonia.Controls/ProgressBar.cs
  59. 5
      src/Avalonia.Controls/RelativePanel.cs
  60. 4
      src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs
  61. 2
      src/Avalonia.Controls/ScrollViewer.cs
  62. 84
      src/Avalonia.Controls/Selection/InternalSelectionModel.cs
  63. 4
      src/Avalonia.Controls/Slider.cs
  64. 7
      src/Avalonia.Controls/TextBox.cs
  65. 10
      src/Avalonia.Controls/TreeView.cs
  66. 8
      src/Avalonia.Controls/Window.cs
  67. 12
      src/Avalonia.Diagnostics/DevToolsExtensions.cs
  68. 24
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  69. 26
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  70. 16
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  71. 18
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  72. 8
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  73. 4
      src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
  74. 32
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  75. 2
      src/Avalonia.Input/AccessKeyHandler.cs
  76. 8
      src/Avalonia.Input/KeyGesture.cs
  77. 4
      src/Avalonia.Input/PointerEventArgs.cs
  78. 14
      src/Avalonia.Input/PointerPoint.cs
  79. 6
      src/Avalonia.Interactivity/EventRoute.cs
  80. 4
      src/Avalonia.Interactivity/Interactive.cs
  81. 25
      src/Avalonia.Layout/LayoutHelper.cs
  82. 16
      src/Avalonia.Layout/Layoutable.cs
  83. 2
      src/Avalonia.Native/avn.idl
  84. 18
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  85. 31
      src/Avalonia.Styling/ClassBindingManager.cs
  86. 21
      src/Avalonia.Styling/Controls/Classes.cs
  87. 11
      src/Avalonia.Styling/StyledElementExtensions.cs
  88. 2
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  89. 32
      src/Avalonia.Themes.Default/FlyoutPresenter.xaml
  90. 29
      src/Avalonia.Themes.Default/MenuFlyoutPresenter.xaml
  91. 9
      src/Avalonia.Themes.Default/TextBox.xaml
  92. 5
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  93. 5
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  94. 2
      src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml
  95. 2
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
  96. 43
      src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml
  97. 35
      src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml
  98. 9
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  99. 9
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  100. 13
      src/Avalonia.Visuals/Media/DrawingContext.cs

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="ReactiveUI" Version="12.1.1" /> <PackageReference Include="ReactiveUI" Version="13.2.10" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@ -1936,6 +1936,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[NSApp setMenu:_menu]; [NSApp setMenu:_menu];
} }
else
{
[self showAppMenuOnly];
}
} }
-(void) showAppMenuOnly -(void) showAppMenuOnly

17
nukebuild/Build.cs

@ -301,14 +301,19 @@ partial class Build : NukeBuild
.Executes(() => .Executes(() =>
{ {
var data = Parameters; var data = Parameters;
var pathToProjectSource = RootDirectory / "samples" / "ControlCatalog.NetCore";
var pathToPublish = pathToProjectSource / "bin" / data.Configuration / "publish";
DotNetPublish(c => c
.SetProject(pathToProjectSource / "ControlCatalog.NetCore.csproj")
.EnableNoBuild()
.SetConfiguration(data.Configuration)
.AddProperty("PackageVersion", data.Version)
.AddProperty("PublishDir", pathToPublish));
Zip(data.ZipCoreArtifacts, data.BinRoot); Zip(data.ZipCoreArtifacts, data.BinRoot);
Zip(data.ZipNuGetArtifacts, data.NugetRoot); Zip(data.ZipNuGetArtifacts, data.NugetRoot);
Zip(data.ZipTargetControlCatalogDesktopDir, Zip(data.ZipTargetControlCatalogNetCoreDir, pathToPublish);
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dll").Concat(
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.config")).Concat(
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.so")).Concat(
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dylib")).Concat(
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.exe")));
}); });
Target CreateIntermediateNugetPackages => _ => _ Target CreateIntermediateNugetPackages => _ => _

7
nukebuild/BuildParameters.cs

@ -58,8 +58,7 @@ public partial class Build
public string FileZipSuffix { get; } public string FileZipSuffix { get; }
public AbsolutePath ZipCoreArtifacts { get; } public AbsolutePath ZipCoreArtifacts { get; }
public AbsolutePath ZipNuGetArtifacts { get; } public AbsolutePath ZipNuGetArtifacts { get; }
public AbsolutePath ZipSourceControlCatalogDesktopDir { get; } public AbsolutePath ZipTargetControlCatalogNetCoreDir { get; }
public AbsolutePath ZipTargetControlCatalogDesktopDir { get; }
public BuildParameters(Build b) public BuildParameters(Build b)
@ -129,9 +128,7 @@ public partial class Build
FileZipSuffix = Version + ".zip"; FileZipSuffix = Version + ".zip";
ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix); ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix);
ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix); ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix);
ZipSourceControlCatalogDesktopDir = ZipTargetControlCatalogNetCoreDir = ZipRoot / ("ControlCatalog.NetCore-" + FileZipSuffix);
RootDirectory / ("samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461");
ZipTargetControlCatalogDesktopDir = ZipRoot / ("ControlCatalog.Desktop-" + FileZipSuffix);
} }
string GetVersion() string GetVersion()

2
readme.md

@ -2,8 +2,6 @@
<br /> <br />
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg)
<img alt="Avalonia" src="https://user-images.githubusercontent.com/6759207/84897744-cab6d800-b0ae-11ea-8214-e5174d71f5c8.png" width="400"/>
## 📖 About AvaloniaUI ## 📖 About AvaloniaUI
Avalonia is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows via .NET Framework and .NET Core, Linux via Xorg, macOS. Avalonia is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development. Avalonia is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows via .NET Framework and .NET Core, Linux via Xorg, macOS. Avalonia is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development.

6
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup> </PropertyGroup>
@ -15,6 +15,10 @@
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<!-- For Microsoft.CodeAnalysis -->
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\ReferenceCoreLibraries.props" /> <Import Project="..\..\build\ReferenceCoreLibraries.props" />

2
samples/ControlCatalog/ControlCatalog.csproj

@ -27,6 +27,6 @@
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />
</Project> </Project>

6
samples/ControlCatalog/MainView.xaml

@ -21,6 +21,9 @@
<TabItem Header="Carousel"><pages:CarouselPage/></TabItem> <TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem> <TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem> <TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
<TabItem Header="ContextFlyout">
<pages:ContextFlyoutPage/>
</TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem> <TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="Cursor" <TabItem Header="Cursor"
ScrollViewer.VerticalScrollBarVisibility="Disabled"> ScrollViewer.VerticalScrollBarVisibility="Disabled">
@ -38,6 +41,9 @@
<pages:CalendarDatePickerPage/></TabItem> <pages:CalendarDatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem> <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem> <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Flyouts">
<pages:FlyoutsPage />
</TabItem>
<TabItem Header="Image" <TabItem Header="Image"
ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"> ScrollViewer.HorizontalScrollBarVisibility="Disabled">

102
samples/ControlCatalog/Pages/ContextFlyoutPage.axaml

@ -0,0 +1,102 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.ContextFlyoutPage">
<UserControl.Styles>
<Style Selector="FlyoutPresenter.NoPadding">
<Setter Property="Padding" Value="0" />
</Style>
</UserControl.Styles>
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Context Flyout</TextBlock>
<TextBlock Classes="h2">A right click Flyout that can be applied to any control.</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<Border Background="{DynamicResource SystemAccentColor}"
Margin="16"
Padding="48,48,48,48">
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Standard _Menu Item" InputGesture="Ctrl+A" />
<MenuItem Header="_Disabled Menu Item" IsEnabled="False" InputGesture="Ctrl+D" />
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
</MenuItem>
<MenuItem Header="Menu Item with _Icon" InputGesture="Ctrl+Shift+B">
<MenuItem.Icon>
<Image Source="/Assets/github_icon.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<TextBlock Text="Defined in XAML"/>
</Border>
<Border Background="{DynamicResource SystemAccentColor}"
Margin="16"
Padding="48,48,48,48">
<Border.ContextMenu>
<ContextMenu Items="{Binding MenuItems}">
<ContextMenu.Styles>
<Style Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</ContextMenu.Styles>
</ContextMenu>
</Border.ContextMenu>
<TextBlock Text="Dynamically Generated"/>
</Border>
</StackPanel>
<TextBlock Text="Custom ContextFlyout for TextBox" />
<TextBox Name="TextBox" Width="150" HorizontalAlignment="Center" ContextMenu="{x:Null}">
<TextBox.ContextFlyout>
<Flyout FlyoutPresenterClasses="NoPadding">
<StackPanel Orientation="Horizontal">
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="40" />
<Setter Property="Width" Value="40" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Opacity" Value="0.5" />
</Style>
</StackPanel.Styles>
<Button Name="CutButton" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}">
<PathIcon Width="14" Height="14" Data="M5.22774,2.08072 C5.43359778,1.94704 5.7011484,1.98419259 5.86368634,2.15675215 L5.91939,2.22774 L12.5191,12.3904 C12.956,12.1419 13.4614,12.0000019 14,12.0000019 C15.6569,12.0000019 17,13.3431 17,15.0000019 C17,16.6569 15.6569,18.0000019 14,18.0000019 C12.3431,18.0000019 11,16.6569 11,15.0000019 C11,14.3201402 11.226152,13.693011 11.6073785,13.1899092 L11.7401,13.0269 L10,10.3474 L8.25991,13.0269 C8.72078,13.5543 9,14.2446 9,15.0000019 C9,16.6569 7.65685,18.0000019 6,18.0000019 C4.34315,18.0000019 3,16.6569 3,15.0000019 C3,13.3431 4.34315,12.0000019 6,12.0000019 C6.46163143,12.0000019 6.89890041,12.1042536 7.28955831,12.2905296 L7.4809,12.3904 L9.40382,9.42936 L5.08072,2.77238 C4.93033,2.54079 4.99615,2.23112 5.22774,2.08072 Z M14,13 C12.8954,13 12,13.8954 12,15 C12,16.1046 12.8954,17 14,17 C15.1046,17 16,16.1046 16,15 C16,13.8954 15.1046,13 14,13 Z M6,13 C4.89543,13 4,13.8954 4,15 C4,16.1046 4.89543,17 6,17 C7.10457,17 8,16.1046 8,15 C8,13.8954 7.10457,13 6,13 Z M14.7723,2.08072 C15.0039,2.23112 15.0697,2.54079 14.9193,2.77238 L11.1924,8.51133 L10.5962,7.59329 L14.0806,2.22774 C14.231,1.99615 14.5407,1.93033 14.7723,2.08072 Z" />
</Button>
<Button Name="CopyButton" Content="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}">
<PathIcon Width="14" Height="14" Data="M5.50280381,4.62704038 L5.5,6.75 L5.5,17.2542087 C5.5,19.0491342 6.95507456,20.5042087 8.75,20.5042087 L17.3662868,20.5044622 C17.057338,21.3782241 16.2239751,22.0042087 15.2444057,22.0042087 L8.75,22.0042087 C6.12664744,22.0042087 4,19.8775613 4,17.2542087 L4,6.75 C4,5.76928848 4.62744523,4.93512464 5.50280381,4.62704038 Z M17.75,2 C18.9926407,2 20,3.00735931 20,4.25 L20,17.25 C20,18.4926407 18.9926407,19.5 17.75,19.5 L8.75,19.5 C7.50735931,19.5 6.5,18.4926407 6.5,17.25 L6.5,4.25 C6.5,3.00735931 7.50735931,2 8.75,2 L17.75,2 Z M17.75,3.5 L8.75,3.5 C8.33578644,3.5 8,3.83578644 8,4.25 L8,17.25 C8,17.6642136 8.33578644,18 8.75,18 L17.75,18 C18.1642136,18 18.5,17.6642136 18.5,17.25 L18.5,4.25 C18.5,3.83578644 18.1642136,3.5 17.75,3.5 Z" />
</Button>
<Button Name="PasteButton" Content="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}">
<PathIcon Width="14" Height="14" Data="M13.75,2 C14.940864,2 15.9156449,2.92516159 15.9948092,4.09595119 L16,4.25 L16,4.25 C16,4.16530567 15.9953205,4.0817043 15.9862059,3.99944035 L17.75,4 C18.9926407,4 20,5.00735931 20,6.25 L20,19.75 C20,20.9926407 18.9926407,22 17.75,22 L6.25,22 C5.00735931,22 4,20.9926407 4,19.75 L4,6.25 C4,5.00735931 5.00735931,4 6.25,4 L8.01379413,3.99944035 C8.00733496,4.05773764 8.00310309,4.11670658 8.00118552,4.17626017 L8,4.25 C8,3.00735931 9.00735931,2 10.25,2 L13.75,2 Z M13.75,6.5 L10.25,6.5 C9.45594921,6.5 8.75796956,6.08867052 8.357512,5.4674625 L8.37902077,5.50019943 L8.37902077,5.50019943 L6.25,5.5 C5.83578644,5.5 5.5,5.83578644 5.5,6.25 L5.5,19.75 C5.5,20.1642136 5.83578644,20.5 6.25,20.5 L17.75,20.5 C18.1642136,20.5 18.5,20.1642136 18.5,19.75 L18.5,6.25 C18.5,5.83578644 18.1642136,5.5 17.75,5.5 L15.6209792,5.50019943 L15.642488,5.4674625 C15.2420304,6.08867052 14.5440508,6.5 13.75,6.5 Z M13.75,3.5 L10.25,3.5 C9.83578644,3.5 9.5,3.83578644 9.5,4.25 C9.5,4.66421356 9.83578644,5 10.25,5 L13.75,5 C14.1642136,5 14.5,4.66421356 14.5,4.25 C14.5,3.83578644 14.1642136,3.5 13.75,3.5 Z" />
</Button>
<Button Name="ClearButton" Content="Clear" Command="{Binding $parent[TextBox].Clear}">
<PathIcon Width="14" Height="14" Data="M3.52499419,3.71761187 L3.61611652,3.61611652 C4.0717282,3.16050485 4.79154862,3.13013074 5.28238813,3.52499419 L5.38388348,3.61611652 L14,12.233 L22.6161165,3.61611652 C23.1042719,3.12796116 23.8957281,3.12796116 24.3838835,3.61611652 C24.8720388,4.10427189 24.8720388,4.89572811 24.3838835,5.38388348 L15.767,14 L24.3838835,22.6161165 C24.8394952,23.0717282 24.8698693,23.7915486 24.4750058,24.2823881 L24.3838835,24.3838835 C23.9282718,24.8394952 23.2084514,24.8698693 22.7176119,24.4750058 L22.6161165,24.3838835 L14,15.767 L5.38388348,24.3838835 C4.89572811,24.8720388 4.10427189,24.8720388 3.61611652,24.3838835 C3.12796116,23.8957281 3.12796116,23.1042719 3.61611652,22.6161165 L12.233,14 L3.61611652,5.38388348 C3.16050485,4.9282718 3.13013074,4.20845138 3.52499419,3.71761187 L3.61611652,3.61611652 L3.52499419,3.71761187 Z" />
</Button>
</StackPanel>
</Flyout>
</TextBox.ContextFlyout>
</TextBox>
</StackPanel>
</UserControl>

45
samples/ControlCatalog/Pages/ContextFlyoutPage.axaml.cs

@ -0,0 +1,45 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public class ContextFlyoutPage : UserControl
{
private TextBox _textBox;
public ContextFlyoutPage()
{
InitializeComponent();
var vm = new ContextFlyoutPageViewModel();
vm.View = this;
DataContext = vm;
_textBox = this.FindControl<TextBox>("TextBox");
var cutButton = this.FindControl<Button>("CutButton");
cutButton.Click += CloseFlyout;
var copyButton = this.FindControl<Button>("CopyButton");
copyButton.Click += CloseFlyout;
var pasteButton = this.FindControl<Button>("PasteButton");
pasteButton.Click += CloseFlyout;
var clearButton = this.FindControl<Button>("ClearButton");
clearButton.Click += CloseFlyout;
}
private void CloseFlyout(object sender, RoutedEventArgs e)
{
_textBox.ContextFlyout.Hide();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

264
samples/ControlCatalog/Pages/FlyoutsPage.axaml

@ -0,0 +1,264 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="700"
x:Class="ControlCatalog.Pages.FlyoutsPage">
<UserControl.Resources>
<MenuFlyout x:Key="SharedMenuFlyout">
<MenuItem Header="Item 1">
<MenuItem Header="Subitem 1" />
<MenuItem Header="Subitem 2" />
<MenuItem Header="Subitem 3" />
</MenuItem>
<MenuItem Header="Item 2" InputGesture="Ctrl+A" />
<MenuItem Header="Item 3" />
</MenuFlyout>
<Flyout Placement="Bottom" x:Key="BasicFlyout">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</UserControl.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Disabled">
<StackPanel Spacing="10">
<TextBlock FontSize="18" Text="Button with a Flyout" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<Button Content="Click Me!" Flyout="{StaticResource BasicFlyout}" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Name="ButtonFlyoutXamlText" Padding="15" />
</Panel>
</StackPanel>
<TextBlock FontSize="18" Text="MenuFlyout" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<Button Content="Click Me!" Flyout="{StaticResource SharedMenuFlyout}" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Name="MenuFlyoutXamlText" Padding="15" />
</Panel>
</StackPanel>
<TextBlock FontSize="18" Text="Attached Flyouts" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
HorizontalAlignment="Left"
Height="100"
Name="AttachedFlyoutPanel">
<FlyoutBase.AttachedFlyout>
<Flyout>
<Panel Height="100">
<TextBlock Text="Attached Flyout!"
VerticalAlignment="Center"
Margin="10"/>
</Panel>
</Flyout>
</FlyoutBase.AttachedFlyout>
<TextBlock Text="Double click panel to launch AttachedFlyout"
VerticalAlignment="Center"
Margin="10"/>
</Panel>
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Name="AttachedFlyoutXamlText" Padding="15" />
</Panel>
</StackPanel>
<TextBlock FontSize="18" Text="Sharing Flyouts" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<StackPanel Orientation="Horizontal" Spacing="30">
<Button Content="Launch Flyout on this button" Flyout="{StaticResource SharedMenuFlyout}"/>
<Button Content="Launch Flyout on this button" Flyout="{StaticResource SharedMenuFlyout}"/>
</StackPanel>
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Name="SharedFlyoutXamlText" Padding="15" />
</Panel>
</StackPanel>
<TextBlock FontSize="18" Text="Flyout Placements" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<UniformGrid Columns="3">
<UniformGrid.Styles>
<Style Selector="Button">
<Setter Property="Margin" Value="10" />
</Style>
</UniformGrid.Styles>
<Button Content="Placement=Top">
<Button.Flyout>
<Flyout Placement="Top">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=Bottom">
<Button.Flyout>
<Flyout Placement="Bottom">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=Left">
<Button.Flyout>
<Flyout Placement="Left">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=Right">
<Button.Flyout>
<Flyout Placement="Right">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=TopEdgeAlignedLeft">
<Button.Flyout>
<Flyout Placement="TopEdgeAlignedLeft">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=TopEdgeAlignedRight">
<Button.Flyout>
<Flyout Placement="TopEdgeAlignedRight">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=BottomEdgeAlignedLeft">
<Button.Flyout>
<Flyout Placement="BottomEdgeAlignedLeft">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=BottomEdgeAlignedRight">
<Button.Flyout>
<Flyout Placement="BottomEdgeAlignedRight">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=LeftEdgeAlignedTop">
<Button.Flyout>
<Flyout Placement="LeftEdgeAlignedTop">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=LeftEdgeAlignedBottom">
<Button.Flyout>
<Flyout Placement="LeftEdgeAlignedBottom">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=RightEdgeAlignedBottom">
<Button.Flyout>
<Flyout Placement="RightEdgeAlignedTop">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="Placement=RightEdgeAlignedBottom">
<Button.Flyout>
<Flyout Placement="RightEdgeAlignedBottom">
<Panel Width="100" Height="100">
<TextBlock Text="Flyout Content!" />
</Panel>
</Flyout>
</Button.Flyout>
</Button>
</UniformGrid>
</Border>
</StackPanel>
<TextBlock FontSize="18" Text="Flyout ShowMode" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<WrapPanel Orientation="Horizontal">
<WrapPanel.Styles>
<Style Selector="Button">
<Setter Property="Margin" Value="4" />
</Style>
</WrapPanel.Styles>
<Button Content="ShowMode=Standard (default)">
<Button.Flyout>
<Flyout>
<StackPanel Width="200">
<TextBox />
<TextBlock Text="Standard ShowMode attempts to focus the Flyout when its opened" TextWrapping="Wrap"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="ShowMode=Transient">
<Button.Flyout>
<Flyout ShowMode="Transient">
<StackPanel Width="200">
<TextBox />
<TextBlock Text="Transient ShowMode does not focus the Flyout when opened" TextWrapping="Wrap"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button Content="ShowMode=TransientWithDismissOnPointerMoveAway">
<Button.Flyout>
<Flyout ShowMode="TransientWithDismissOnPointerMoveAway">
<StackPanel Width="200">
<TextBox />
<TextBlock Text="Show in Transient mode (no focus), but closes the Flyout when the pointer moves away" TextWrapping="Wrap"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</WrapPanel>
</Border>
</StackPanel>
</StackPanel>
</ScrollViewer>
</UserControl>

81
samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs

@ -0,0 +1,81 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Xaml;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public class FlyoutsPage : UserControl
{
public FlyoutsPage()
{
InitializeComponent();
var afp = this.FindControl<Panel>("AttachedFlyoutPanel");
if (afp != null)
{
afp.DoubleTapped += Afp_DoubleTapped;
}
SetXamlTexts();
}
private void Afp_DoubleTapped(object sender, RoutedEventArgs e)
{
if (sender is Panel p)
{
FlyoutBase.ShowAttachedFlyout(p);
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void SetXamlTexts()
{
var bfxt = this.FindControl<TextBlock>("ButtonFlyoutXamlText");
bfxt.Text = "<Button Content=\"Click me!\">\n" +
" <Button.Flyout>\n" +
" <Flyout>\n" +
" <Panel Width=\"100\" Height=\"100\">\n" +
" <TextBlock Text=\"Flyout Content!\" />\n" +
" </Panel>\n" +
" </Flyout>\n" +
" </Button.Flyout>\n</Button>";
var mfxt = this.FindControl<TextBlock>("MenuFlyoutXamlText");
mfxt.Text = "<Button Content=\"Click me!\">\n" +
" <Button.Flyout>\n" +
" <MenuFlyout>\n" +
" <MenuItem Header=\"Item 1\">\n" +
" <MenuItem Header=\"Item 2\">\n" +
" </MenuFlyout>\n" +
" </Button.Flyout>\n</Button>";
var afxt = this.FindControl<TextBlock>("AttachedFlyoutXamlText");
afxt.Text = "<Panel Name=\"AttachedFlyoutPanel\">\n" +
" <FlyoutBase.AttachedFlyout>\n" +
" <Flyout>\n" +
" <Panel Height=\"100\">\n" +
" <TextBlock Text=\"Attached Flyout\" />\n" +
" </Panel>\n" +
" </Flyout>\n" +
" </FlyoutBase.AttachedFlyout>\n</Panel>" +
"\n\n In DoubleTapped handler:\n" +
"FlyoutBase.ShowAttachedFlyout(AttachedFlyoutPanel);";
var sfxt = this.FindControl<TextBlock>("SharedFlyoutXamlText");
sfxt.Text = "Declare a flyout in Resources:\n" +
"<Window.Resources>\n" +
" <Flyout x:Key=\"SharedFlyout\">\n" +
" <Panel Width=\"100\" Height=\"100\">\n" +
" <TextBlock Text=\"Flyout Content!\" />\n" +
" </Panel>\n" +
" </Flyout>\n</Window.Resources>\n\n" +
"Then attach the flyout where you want it:\n" +
"<Button Content=\"Launch Flyout here\" Flyout=\"{StaticResource SharedFlyout}\" />";
}
}
}

2
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -13,7 +13,7 @@
<StackPanel Orientation="Vertical" Spacing="8"> <StackPanel Orientation="Vertical" Spacing="8">
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200" /> <TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200" />
<TextBox Width="200" Watermark="ReadOnly" IsReadOnly="True" Text="This is read only"/> <TextBox Width="200" Watermark="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" Watermark="Watermark" /> <TextBox Width="200" Watermark="Numeric Watermark" x:Name="numericWatermark"/>
<TextBox Width="200" <TextBox Width="200"
Watermark="Floating Watermark" Watermark="Floating Watermark"
UseFloatingWatermark="True" UseFloatingWatermark="True"

6
samples/ControlCatalog/Pages/TextBoxPage.xaml.cs

@ -13,6 +13,12 @@ namespace ControlCatalog.Pages
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
this.Get<TextBox>("numericWatermark")
.TextInputOptionsQuery += (s, a) =>
{
a.ContentType = Avalonia.Input.TextInput.TextInputContentType.Number;
};
} }
} }
} }

78
samples/ControlCatalog/ViewModels/ContextFlyoutPageViewModel.cs

@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.VisualTree;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class ContextFlyoutPageViewModel
{
public Control View { get; set; }
public ContextFlyoutPageViewModel()
{
OpenCommand = MiniCommand.CreateFromTask(Open);
SaveCommand = MiniCommand.Create(Save);
OpenRecentCommand = MiniCommand.Create<string>(OpenRecent);
MenuItems = new[]
{
new MenuItemViewModel { Header = "_Open...", Command = OpenCommand },
new MenuItemViewModel { Header = "Save", Command = SaveCommand },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
Items = new[]
{
new MenuItemViewModel
{
Header = "File1.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File1.txt"
},
new MenuItemViewModel
{
Header = "File2.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File2.txt"
},
}
},
};
}
public IReadOnlyList<MenuItemViewModel> MenuItems { get; set; }
public MiniCommand OpenCommand { get; }
public MiniCommand SaveCommand { get; }
public MiniCommand OpenRecentCommand { get; }
public async Task Open()
{
var window = View?.GetVisualRoot() as Window;
if (window == null)
return;
var dialog = new OpenFileDialog();
var result = await dialog.ShowAsync(window);
if (result != null)
{
foreach (var path in result)
{
System.Diagnostics.Debug.WriteLine($"Opened: {path}");
}
}
}
public void Save()
{
System.Diagnostics.Debug.WriteLine("Save");
}
public void OpenRecent(string path)
{
System.Diagnostics.Debug.WriteLine($"Open recent: {path}");
}
}
}

3
samples/RenderDemo/MainWindow.xaml

@ -57,6 +57,9 @@
<TabItem Header="LineBounds"> <TabItem Header="LineBounds">
<pages:LineBoundsPage /> <pages:LineBoundsPage />
</TabItem> </TabItem>
<TabItem Header="Path Measurement">
<pages:PathMeasurementPage />
</TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>
</Window> </Window>

89
samples/RenderDemo/Pages/PathMeasurementPage.cs

@ -0,0 +1,89 @@
using System;
using System.Diagnostics;
using System.Drawing.Drawing2D;
using System.Security.Cryptography;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Media.Immutable;
using Avalonia.Threading;
using Avalonia.Visuals.Media.Imaging;
namespace RenderDemo.Pages
{
public class PathMeasurementPage : Control
{
static PathMeasurementPage()
{
AffectsRender<PathMeasurementPage>(BoundsProperty);
}
private RenderTargetBitmap _bitmap;
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_bitmap = new RenderTargetBitmap(new PixelSize(500, 500), new Vector(96, 96));
base.OnAttachedToLogicalTree(e);
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_bitmap.Dispose();
_bitmap = null;
base.OnDetachedFromLogicalTree(e);
}
readonly IPen strokePen = new ImmutablePen(Brushes.DarkBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round);
readonly IPen strokePen1 = new ImmutablePen(Brushes.Purple, 10d, null, PenLineCap.Round, PenLineJoin.Round);
readonly IPen strokePen2 = new ImmutablePen(Brushes.Green, 10d, null, PenLineCap.Round, PenLineJoin.Round);
readonly IPen strokePen3 = new ImmutablePen(Brushes.LightBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round);
readonly IPen strokePen4 = new ImmutablePen(Brushes.Red, 1d, null, PenLineCap.Round, PenLineJoin.Round);
public override void Render(DrawingContext context)
{
using (var ctxi = _bitmap.CreateDrawingContext(null))
using (var bitmapCtx = new DrawingContext(ctxi, false))
{
ctxi.Clear(default);
var basePath = new PathGeometry();
using (var basePathCtx = basePath.Open())
{
basePathCtx.BeginFigure(new Point(20, 20), false);
basePathCtx.LineTo(new Point(400, 50));
basePathCtx.LineTo(new Point(80, 100));
basePathCtx.LineTo(new Point(300, 150));
basePathCtx.EndFigure(false);
}
bitmapCtx.DrawGeometry(null, strokePen, basePath);
var length = basePath.PlatformImpl.ContourLength;
if (basePath.PlatformImpl.TryGetSegment(length * 0.05, length * 0.2, true, out var dst1))
bitmapCtx.DrawGeometry(null, strokePen1, dst1);
if (basePath.PlatformImpl.TryGetSegment(length * 0.2, length * 0.8, true, out var dst2))
bitmapCtx.DrawGeometry(null, strokePen2, dst2);
if (basePath.PlatformImpl.TryGetSegment(length * 0.8, length * 0.95, true, out var dst3))
bitmapCtx.DrawGeometry(null, strokePen3, dst3);
var pathBounds = basePath.GetRenderBounds(strokePen);
bitmapCtx.DrawRectangle(null, strokePen4, pathBounds);
}
context.DrawImage(_bitmap,
new Rect(0, 0, 500, 500),
new Rect(0, 0, 500, 500));
base.Render(context);
}
}
}

96
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -0,0 +1,96 @@
using System;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Input;
using Avalonia.Input.TextInput;
namespace Avalonia.Android
{
class AndroidInputMethod<TView> : ITextInputMethodImpl
where TView: View, IInitEditorInfo
{
private readonly TView _host;
private readonly InputMethodManager _imm;
private IInputElement _inputElement;
public AndroidInputMethod(TView host)
{
if (host.OnCheckIsTextEditor() == false)
throw new InvalidOperationException("Host should return true from OnCheckIsTextEditor()");
_host = host;
_imm = host.Context.GetSystemService(Context.InputMethodService).JavaCast<InputMethodManager>();
_host.Focusable = true;
_host.FocusableInTouchMode = true;
_host.ViewTreeObserver.AddOnGlobalLayoutListener(new SoftKeyboardListner(_host));
}
public void Reset()
{
_imm.RestartInput(_host);
}
public void SetActive(bool active)
{
if (active)
{
_host.RequestFocus();
Reset();
_imm.ShowSoftInput(_host, ShowFlags.Implicit);
}
else
_imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.None);
}
public void SetCursorRect(Rect rect)
{
}
public void SetOptions(TextInputOptionsQueryEventArgs options)
{
if (_inputElement != null)
{
_inputElement.PointerReleased -= RestoreSoftKeyboard;
}
_inputElement = options.Source as InputElement;
if (_inputElement == null)
{
_imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.None);
}
_host.InitEditorInfo((outAttrs) =>
{
outAttrs.InputType = options.ContentType switch
{
TextInputContentType.Email => global::Android.Text.InputTypes.TextVariationEmailAddress,
TextInputContentType.Number => global::Android.Text.InputTypes.ClassNumber,
TextInputContentType.Password => global::Android.Text.InputTypes.TextVariationPassword,
TextInputContentType.Phone => global::Android.Text.InputTypes.ClassPhone,
TextInputContentType.Url => global::Android.Text.InputTypes.TextVariationUri,
_ => global::Android.Text.InputTypes.ClassText
};
if (options.AutoCapitalization)
{
outAttrs.InitialCapsMode = global::Android.Text.CapitalizationMode.Sentences;
outAttrs.InputType |= global::Android.Text.InputTypes.TextFlagCapSentences;
}
if (options.Multiline)
outAttrs.InputType |= global::Android.Text.InputTypes.TextFlagMultiLine;
});
//_inputElement.PointerReleased += RestoreSoftKeyboard;
}
private void RestoreSoftKeyboard(object sender, PointerReleasedEventArgs e)
{
_imm.ShowSoftInput(_host, ShowFlags.Implicit);
}
}
}

4
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -15,7 +15,6 @@ namespace Avalonia.Android
if (_content != null) if (_content != null)
View.Content = _content; View.Content = _content;
SetContentView(View); SetContentView(View);
TakeKeyEvents(true);
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);
} }
@ -32,8 +31,5 @@ namespace Avalonia.Android
View.Content = value; View.Content = value;
} }
} }
public override bool DispatchKeyEvent(KeyEvent e) =>
View.DispatchKeyEvent(e) ? true : base.DispatchKeyEvent(e);
} }
} }

12
src/Android/Avalonia.Android/IInitEditorInfo.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using Android.Views.InputMethods;
namespace Avalonia.Android
{
interface IInitEditorInfo
{
void InitEditorInfo(Action<EditorInfo> init);
}
}

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@ -32,7 +32,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
RowBytes = buffer.stride * (Format == PixelFormat.Rgb565 ? 2 : 4); RowBytes = buffer.stride * (Format == PixelFormat.Rgb565 ? 2 : 4);
Address = buffer.bits; Address = buffer.bits;
Dpi = scaling * new Vector(96, 96); Dpi = new Vector(96, 96) * scaling;
} }
public void Dispose() public void Dispose()

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@ -13,7 +13,7 @@ namespace Avalonia.Android
bool _invalidateQueued; bool _invalidateQueued;
readonly object _lock = new object(); readonly object _lock = new object();
private readonly Handler _handler; private readonly Handler _handler;
public InvalidationAwareSurfaceView(Context context) : base(context) public InvalidationAwareSurfaceView(Context context) : base(context)
{ {

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

@ -4,14 +4,16 @@ using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.OpenGL; using Avalonia.Android.OpenGL;
using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces; using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform; using Avalonia.Platform;
@ -19,19 +21,20 @@ using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
{ {
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod
{ {
private readonly IGlPlatformSurface _gl; private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer; private readonly IFramebufferPlatformSurface _framebuffer;
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper; private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper; private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
private readonly ITextInputMethodImpl _textInputMethod;
private ViewImpl _view; private ViewImpl _view;
public TopLevelImpl(Context context, bool placeOnTop = false) public TopLevelImpl(Context context, bool placeOnTop = false)
{ {
_view = new ViewImpl(context, this, placeOnTop); _view = new ViewImpl(context, this, placeOnTop);
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this); _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot, _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
GetAvaloniaPointFromEvent); GetAvaloniaPointFromEvent);
@ -45,18 +48,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
} }
private bool _handleEvents;
public bool HandleEvents
{
get { return _handleEvents; }
set
{
_handleEvents = value;
_keyboardHelper.HandleEvents = _handleEvents;
}
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
new Point(e.GetX(pointerIndex), e.GetY(pointerIndex)) / RenderScaling; new Point(e.GetX(pointerIndex), e.GetY(pointerIndex)) / RenderScaling;
@ -144,7 +135,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
Resized?.Invoke(size); Resized?.Invoke(size);
} }
class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo
{ {
private readonly TopLevelImpl _tl; private readonly TopLevelImpl _tl;
private Size _oldSize; private Size _oldSize;
@ -191,6 +182,27 @@ namespace Avalonia.Android.Platform.SkiaPlatform
base.SurfaceChanged(holder, format, width, height); base.SurfaceChanged(holder, format, width, height);
} }
public sealed override bool OnCheckIsTextEditor()
{
return true;
}
private Action<EditorInfo> _initEditorInfo;
public void InitEditorInfo(Action<EditorInfo> init)
{
_initEditorInfo = init;
}
public sealed override IInputConnection OnCreateInputConnection(EditorInfo outAttrs)
{
if (_initEditorInfo != null)
_initEditorInfo(outAttrs);
return base.OnCreateInputConnection(outAttrs);
}
} }
public IPopupImpl CreatePopup() => null; public IPopupImpl CreatePopup() => null;
@ -209,6 +221,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public double Scaling => RenderScaling; public double Scaling => RenderScaling;
public ITextInputMethodImpl TextInputMethod => _textInputMethod;
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

83
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -1,12 +1,7 @@
using System; using System;
using System.ComponentModel;
using Android.Content;
using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
@ -14,14 +9,13 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{ {
internal class AndroidKeyboardEventsHelper<TView> : IDisposable where TView : TopLevelImpl, IAndroidView internal class AndroidKeyboardEventsHelper<TView> : IDisposable where TView : TopLevelImpl, IAndroidView
{ {
private TView _view; private readonly TView _view;
private IInputElement _lastFocusedElement;
public bool HandleEvents { get; set; } public bool HandleEvents { get; set; }
public AndroidKeyboardEventsHelper(TView view) public AndroidKeyboardEventsHelper(TView view)
{ {
this._view = view; _view = view;
HandleEvents = true; HandleEvents = true;
} }
@ -36,9 +30,20 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return DispatchKeyEventInternal(e, out callBase); return DispatchKeyEventInternal(e, out callBase);
} }
string? UnicodeTextInput(KeyEvent keyEvent)
{
return keyEvent.Action == KeyEventActions.Multiple
&& keyEvent.RepeatCount == 0
&& !string.IsNullOrEmpty(keyEvent?.Characters)
? keyEvent.Characters
: null;
}
private bool? DispatchKeyEventInternal(KeyEvent e, out bool callBase) private bool? DispatchKeyEventInternal(KeyEvent e, out bool callBase)
{ {
if (e.Action == KeyEventActions.Multiple) var unicodeTextInput = UnicodeTextInput(e);
if (e.Action == KeyEventActions.Multiple && unicodeTextInput == null)
{ {
callBase = true; callBase = true;
return null; return null;
@ -53,13 +58,14 @@ namespace Avalonia.Android.Platform.Specific.Helpers
_view.Input(rawKeyEvent); _view.Input(rawKeyEvent);
if (e.Action == KeyEventActions.Down && e.UnicodeChar >= 32) if ((e.Action == KeyEventActions.Down && e.UnicodeChar >= 32)
|| unicodeTextInput != null)
{ {
var rawTextEvent = new RawTextInputEventArgs( var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance, AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime), Convert.ToUInt32(e.EventTime),
_view.InputRoot, _view.InputRoot,
Convert.ToChar(e.UnicodeChar).ToString() unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString()
); );
_view.Input(rawTextEvent); _view.Input(rawTextEvent);
} }
@ -85,61 +91,6 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return rv; return rv;
} }
private bool NeedsKeyboard(IInputElement element)
{
//may be some other elements
return element is TextBox;
}
private void TryShowHideKeyboard(IInputElement element, bool value)
{
var input = _view.View.Context.GetSystemService(Context.InputMethodService).JavaCast<InputMethodManager>();
if (value)
{
//show keyboard
//may be in the future different keyboards support e.g. normal, only digits etc.
//Android.Text.InputTypes
input.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
}
else
{
//hide keyboard
input.HideSoftInputFromWindow(_view.View.WindowToken, HideSoftInputFlags.None);
}
}
public void UpdateKeyboardState(IInputElement element)
{
var focusedElement = element;
bool oldValue = NeedsKeyboard(_lastFocusedElement);
bool newValue = NeedsKeyboard(focusedElement);
if (newValue != oldValue || newValue)
{
TryShowHideKeyboard(focusedElement, newValue);
}
_lastFocusedElement = element;
}
public void ActivateAutoShowKeyboard()
{
var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged);
//just in case we've called more than once the method
kbDevice.PropertyChanged -= KeyboardDevice_PropertyChanged;
kbDevice.PropertyChanged += KeyboardDevice_PropertyChanged;
}
private void KeyboardDevice_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(KeyboardDevice.FocusedElement))
{
UpdateKeyboardState(KeyboardDevice.Instance.FocusedElement);
}
}
public void Dispose() public void Dispose()
{ {
HandleEvents = false; HandleEvents = false;

42
src/Android/Avalonia.Android/SoftKeyboardListner.cs

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
using Android.Content;
using Android.OS;
using Android.Util;
using Android.Views;
using Avalonia.Input;
namespace Avalonia.Android
{
class SoftKeyboardListner : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
private const int DefaultKeyboardHeightDP = 100;
private static readonly int EstimatedKeyboardDP = DefaultKeyboardHeightDP + (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop ? 48 : 0);
private readonly View _host;
private bool _wasKeyboard;
public SoftKeyboardListner(View view)
{
_host = view;
}
public void OnGlobalLayout()
{
int estimatedKeyboardHeight = (int)TypedValue.ApplyDimension(ComplexUnitType.Dip,
EstimatedKeyboardDP, _host.Resources.DisplayMetrics);
var rect = new global::Android.Graphics.Rect();
_host.GetWindowVisibleDisplayFrame(rect);
int heightDiff = _host.RootView.Height - (rect.Bottom - rect.Top);
var isKeyboard = heightDiff >= estimatedKeyboardHeight;
if (_wasKeyboard && !isKeyboard)
KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
_wasKeyboard = isKeyboard;
}
}
}

28
src/Android/Avalonia.AndroidTestApplication/MainActivity.cs

@ -16,18 +16,18 @@ namespace Avalonia.AndroidTestApplication
Icon = "@drawable/icon", Icon = "@drawable/icon",
LaunchMode = LaunchMode.SingleInstance/*, LaunchMode = LaunchMode.SingleInstance/*,
ScreenOrientation = ScreenOrientation.Landscape*/)] ScreenOrientation = ScreenOrientation.Landscape*/)]
public class MainBaseActivity : Activity public class MainBaseActivity : AvaloniaActivity
{ {
protected override void OnCreate(Bundle savedInstanceState) protected override void OnCreate(Bundle savedInstanceState)
{ {
base.OnCreate(savedInstanceState);
if (Avalonia.Application.Current == null) if (Avalonia.Application.Current == null)
{ {
AppBuilder.Configure<App>() AppBuilder.Configure<App>()
.UseAndroid() .UseAndroid()
.SetupWithoutStarting(); .SetupWithoutStarting();
} }
SetContentView(new AvaloniaView(this) { Content = App.CreateSimpleWindow() }); base.OnCreate(savedInstanceState);
Content = App.CreateSimpleWindow();
} }
} }
@ -72,13 +72,33 @@ namespace Avalonia.AndroidTestApplication
Height = 40, Height = 40,
Background = Brushes.LightGreen, Background = Brushes.LightGreen,
Foreground = Brushes.Black Foreground = Brushes.Black
} },
CreateTextBox(Input.TextInput.TextInputContentType.Normal),
CreateTextBox(Input.TextInput.TextInputContentType.Password),
CreateTextBox(Input.TextInput.TextInputContentType.Email),
CreateTextBox(Input.TextInput.TextInputContentType.Url),
CreateTextBox(Input.TextInput.TextInputContentType.Phone),
CreateTextBox(Input.TextInput.TextInputContentType.Number),
} }
} }
}; };
return window; return window;
} }
private static TextBox CreateTextBox(Input.TextInput.TextInputContentType contentType)
{
var textBox = new TextBox()
{
Margin = new Thickness(20, 10),
Watermark = contentType.ToString(),
BorderThickness = new Thickness(3),
FontSize = 20
};
textBox.TextInputOptionsQuery += (s, e) => { e.ContentType = contentType; };
return textBox;
}
} }
} }

4
src/Avalonia.Base/Data/BindingValue.cs

@ -108,12 +108,12 @@ namespace Avalonia.Data
/// Gets a value indicating whether the binding value represents either a binding or data /// Gets a value indicating whether the binding value represents either a binding or data
/// validation error. /// validation error.
/// </summary> /// </summary>
public bool HasError => Type.HasFlagCustom(BindingValueType.HasError); public bool HasError => Type.HasAllFlags(BindingValueType.HasError);
/// <summary> /// <summary>
/// Gets a value indicating whether the binding value has a value. /// Gets a value indicating whether the binding value has a value.
/// </summary> /// </summary>
public bool HasValue => Type.HasFlagCustom(BindingValueType.HasValue); public bool HasValue => Type.HasAllFlags(BindingValueType.HasValue);
/// <summary> /// <summary>
/// Gets the type of the binding value. /// Gets the type of the binding value.

54
src/Avalonia.Base/EnumExtensions.cs

@ -9,31 +9,67 @@ namespace Avalonia
public static class EnumExtensions public static class EnumExtensions
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum [Obsolete("This method is obsolete. Use HasAllFlags instead.")]
public static bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
=> value.HasAllFlags(flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasAllFlags<T>(this T value, T flags) where T : unmanaged, Enum
{
if (sizeof(T) == 1)
{
var byteValue = Unsafe.As<T, byte>(ref value);
var byteFlags = Unsafe.As<T, byte>(ref flags);
return (byteValue & byteFlags) == byteFlags;
}
else if (sizeof(T) == 2)
{
var shortValue = Unsafe.As<T, short>(ref value);
var shortFlags = Unsafe.As<T, short>(ref flags);
return (shortValue & shortFlags) == shortFlags;
}
else if (sizeof(T) == 4)
{
var intValue = Unsafe.As<T, int>(ref value);
var intFlags = Unsafe.As<T, int>(ref flags);
return (intValue & intFlags) == intFlags;
}
else if (sizeof(T) == 8)
{
var longValue = Unsafe.As<T, long>(ref value);
var longFlags = Unsafe.As<T, long>(ref flags);
return (longValue & longFlags) == longFlags;
}
else
throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf<T>() + " are not supported");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasAnyFlag<T>(this T value, T flags) where T : unmanaged, Enum
{ {
if (sizeof(T) == 1) if (sizeof(T) == 1)
{ {
var byteValue = Unsafe.As<T, byte>(ref value); var byteValue = Unsafe.As<T, byte>(ref value);
var byteFlag = Unsafe.As<T, byte>(ref flag); var byteFlags = Unsafe.As<T, byte>(ref flags);
return (byteValue & byteFlag) == byteFlag; return (byteValue & byteFlags) != 0;
} }
else if (sizeof(T) == 2) else if (sizeof(T) == 2)
{ {
var shortValue = Unsafe.As<T, short>(ref value); var shortValue = Unsafe.As<T, short>(ref value);
var shortFlag = Unsafe.As<T, short>(ref flag); var shortFlags = Unsafe.As<T, short>(ref flags);
return (shortValue & shortFlag) == shortFlag; return (shortValue & shortFlags) != 0;
} }
else if (sizeof(T) == 4) else if (sizeof(T) == 4)
{ {
var intValue = Unsafe.As<T, int>(ref value); var intValue = Unsafe.As<T, int>(ref value);
var intFlag = Unsafe.As<T, int>(ref flag); var intFlags = Unsafe.As<T, int>(ref flags);
return (intValue & intFlag) == intFlag; return (intValue & intFlags) != 0;
} }
else if (sizeof(T) == 8) else if (sizeof(T) == 8)
{ {
var longValue = Unsafe.As<T, long>(ref value); var longValue = Unsafe.As<T, long>(ref value);
var longFlag = Unsafe.As<T, long>(ref flag); var longFlags = Unsafe.As<T, long>(ref flags);
return (longValue & longFlag) == longFlag; return (longValue & longFlags) != 0;
} }
else else
throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf<T>() + " are not supported"); throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf<T>() + " are not supported");

15
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -30,6 +30,21 @@ namespace Avalonia.Utilities
return (-eps < delta) && (eps > delta); return (-eps < delta) && (eps > delta);
} }
/// <summary>
/// AreClose - Returns whether or not two doubles are "close". That is, whether or
/// not they are within epsilon of each other.
/// </summary>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
/// <param name="eps"> The fixed epsilon value used to compare.</param>
public static bool AreClose(double value1, double value2, double eps)
{
//in case they are Infinities (then epsilon check does not work)
if (value1 == value2) return true;
double delta = value1 - value2;
return (-eps < delta) && (eps > delta);
}
/// <summary> /// <summary>
/// AreClose - Returns whether or not two floats are "close". That is, whether or /// AreClose - Returns whether or not two floats are "close". That is, whether or
/// not they are within epsilon of each other. /// not they are within epsilon of each other.

4
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -372,8 +372,8 @@ namespace Avalonia.Utilities
const string implicitName = "op_Implicit"; const string implicitName = "op_Implicit";
const string explicitName = "op_Explicit"; const string explicitName = "op_Explicit";
bool allowImplicit = operatorType.HasFlagCustom(OperatorType.Implicit); bool allowImplicit = operatorType.HasAllFlags(OperatorType.Implicit);
bool allowExplicit = operatorType.HasFlagCustom(OperatorType.Explicit); bool allowExplicit = operatorType.HasAllFlags(OperatorType.Explicit);
foreach (MethodInfo method in fromType.GetMethods()) foreach (MethodInfo method in fromType.GetMethods())
{ {

12
src/Avalonia.Build.Tasks/DeterministicIdGenerator.cs

@ -0,0 +1,12 @@
using System;
using XamlX.Transform;
namespace Avalonia.Build.Tasks
{
public class DeterministicIdGenerator : IXamlIdentifierGenerator
{
private int _nextId = 1;
public string GenerateIdentifierPart() => (_nextId++).ToString();
}
}

4
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -22,7 +22,6 @@ using XamlX.IL;
namespace Avalonia.Build.Tasks namespace Avalonia.Build.Tasks
{ {
public static partial class XamlCompilerTaskExecutor public static partial class XamlCompilerTaskExecutor
{ {
static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml") static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
@ -99,7 +98,8 @@ namespace Avalonia.Build.Tasks
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage), XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
AvaloniaXamlIlLanguage.CustomValueConverter, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)), new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure))); new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
new DeterministicIdGenerator());
var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext", var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext",

2
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -2595,7 +2595,7 @@ namespace Avalonia.Collections
/// <returns>Whether the specified flag is set</returns> /// <returns>Whether the specified flag is set</returns>
private bool CheckFlag(CollectionViewFlags flags) private bool CheckFlag(CollectionViewFlags flags)
{ {
return _flags.HasFlagCustom(flags); return _flags.HasAllFlags(flags);
} }
/// <summary> /// <summary>

5
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -910,6 +910,11 @@ namespace Avalonia.Controls
// Clear all row selections // Clear all row selections
ClearRowSelection(resetAnchorSlot: true); ClearRowSelection(resetAnchorSlot: true);
if (DataConnection.CollectionView != null)
{
DataConnection.CollectionView.MoveCurrentTo(null);
}
} }
else else
{ {

1
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -3,6 +3,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Control
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
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. 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.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 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.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. 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. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.

52
src/Avalonia.Controls/Button.cs

@ -2,6 +2,7 @@ using System;
using System.Linq; using System.Linq;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -78,6 +79,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsPressedProperty = public static readonly StyledProperty<bool> IsPressedProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsPressed)); AvaloniaProperty.Register<Button, bool>(nameof(IsPressed));
/// <summary>
/// Defines the <see cref="Flyout"/> property
/// </summary>
public static readonly StyledProperty<FlyoutBase> FlyoutProperty =
AvaloniaProperty.Register<Button, FlyoutBase>(nameof(Flyout));
private ICommand _command; private ICommand _command;
private bool _commandCanExecute = true; private bool _commandCanExecute = true;
private KeyGesture _hotkey; private KeyGesture _hotkey;
@ -89,6 +96,7 @@ namespace Avalonia.Controls
{ {
FocusableProperty.OverrideDefaultValue(typeof(Button), true); FocusableProperty.OverrideDefaultValue(typeof(Button), true);
CommandProperty.Changed.Subscribe(CommandChanged); CommandProperty.Changed.Subscribe(CommandChanged);
CommandParameterProperty.Changed.Subscribe(CommandParameterChanged);
IsDefaultProperty.Changed.Subscribe(IsDefaultChanged); IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
IsCancelProperty.Changed.Subscribe(IsCancelChanged); IsCancelProperty.Changed.Subscribe(IsCancelChanged);
} }
@ -169,6 +177,15 @@ namespace Avalonia.Controls
private set { SetValue(IsPressedProperty, value); } private set { SetValue(IsPressedProperty, value); }
} }
/// <summary>
/// Gets or sets the Flyout that should be shown with this button
/// </summary>
public FlyoutBase Flyout
{
get => GetValue(FlyoutProperty);
set => SetValue(FlyoutProperty, value);
}
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
/// <inheritdoc/> /// <inheritdoc/>
@ -218,6 +235,7 @@ namespace Avalonia.Controls
if (Command != null) if (Command != null)
{ {
Command.CanExecuteChanged += CanExecuteChanged; Command.CanExecuteChanged += CanExecuteChanged;
CanExecuteChanged(this, EventArgs.Empty);
} }
} }
@ -255,6 +273,11 @@ namespace Avalonia.Controls
IsPressed = true; IsPressed = true;
e.Handled = true; e.Handled = true;
} }
else if (e.Key == Key.Escape && Flyout != null)
{
// If Flyout doesn't have focusable content, close the flyout here
Flyout.Hide();
}
base.OnKeyDown(e); base.OnKeyDown(e);
} }
@ -278,6 +301,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
protected virtual void OnClick() protected virtual void OnClick()
{ {
OpenFlyout();
var e = new RoutedEventArgs(ClickEvent); var e = new RoutedEventArgs(ClickEvent);
RaiseEvent(e); RaiseEvent(e);
@ -288,6 +313,11 @@ namespace Avalonia.Controls
} }
} }
protected virtual void OpenFlyout()
{
Flyout?.ShowAt(this);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e) protected override void OnPointerPressed(PointerPressedEventArgs e)
{ {
@ -336,6 +366,16 @@ namespace Avalonia.Controls
{ {
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>()); UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>());
} }
else if (change.Property == FlyoutProperty)
{
// If flyout is changed while one is already open, make sure we
// close the old one first
if (change.OldValue.GetValueOrDefault() is FlyoutBase oldFlyout &&
oldFlyout.IsOpen)
{
oldFlyout.Hide();
}
}
} }
protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value) protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
@ -379,6 +419,18 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Called when the <see cref="CommandParameter"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void CommandParameterChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is Button button)
{
button.CanExecuteChanged(button, EventArgs.Empty);
}
}
/// <summary> /// <summary>
/// Called when the <see cref="IsDefault"/> property changes. /// Called when the <see cref="IsDefault"/> property changes.
/// </summary> /// </summary>

73
src/Avalonia.Controls/Chrome/CaptionButtons.cs

@ -14,17 +14,21 @@ namespace Avalonia.Controls.Chrome
public class CaptionButtons : TemplatedControl public class CaptionButtons : TemplatedControl
{ {
private CompositeDisposable? _disposables; private CompositeDisposable? _disposables;
private Window? _hostWindow;
public void Attach(Window hostWindow) /// <summary>
/// Currently attached window.
/// </summary>
protected Window? HostWindow { get; private set; }
public virtual void Attach(Window hostWindow)
{ {
if (_disposables == null) if (_disposables == null)
{ {
_hostWindow = hostWindow; HostWindow = hostWindow;
_disposables = new CompositeDisposable _disposables = new CompositeDisposable
{ {
_hostWindow.GetObservable(Window.WindowStateProperty) HostWindow.GetObservable(Window.WindowStateProperty)
.Subscribe(x => .Subscribe(x =>
{ {
PseudoClasses.Set(":minimized", x == WindowState.Minimized); PseudoClasses.Set(":minimized", x == WindowState.Minimized);
@ -36,14 +40,45 @@ namespace Avalonia.Controls.Chrome
} }
} }
public void Detach() public virtual void Detach()
{ {
if (_disposables != null) if (_disposables != null)
{ {
_disposables.Dispose(); _disposables.Dispose();
_disposables = null; _disposables = null;
_hostWindow = null; HostWindow = null;
}
}
protected virtual void OnClose()
{
HostWindow?.Close();
}
protected virtual void OnRestore()
{
if (HostWindow != null)
{
HostWindow.WindowState = HostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
}
protected virtual void OnMinimize()
{
if (HostWindow != null)
{
HostWindow.WindowState = WindowState.Minimized;
}
}
protected virtual void OnToggleFullScreen()
{
if (HostWindow != null)
{
HostWindow.WindowState = HostWindow.WindowState == WindowState.FullScreen
? WindowState.Normal
: WindowState.FullScreen;
} }
} }
@ -56,31 +91,13 @@ namespace Avalonia.Controls.Chrome
var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton"); var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton");
var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton"); var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton");
closeButton.PointerReleased += (sender, e) => _hostWindow?.Close(); closeButton.PointerReleased += (sender, e) => OnClose();
restoreButton.PointerReleased += (sender, e) => restoreButton.PointerReleased += (sender, e) => OnRestore();
{
if (_hostWindow != null)
{
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
};
minimiseButton.PointerReleased += (sender, e) => minimiseButton.PointerReleased += (sender, e) => OnMinimize();
{
if (_hostWindow != null)
{
_hostWindow.WindowState = WindowState.Minimized;
}
};
fullScreenButton.PointerReleased += (sender, e) => fullScreenButton.PointerReleased += (sender, e) => OnToggleFullScreen();
{
if (_hostWindow != null)
{
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen;
}
};
} }
} }
} }

2
src/Avalonia.Controls/ComboBox.cs

@ -206,7 +206,7 @@ namespace Avalonia.Controls
return; return;
if (e.Key == Key.F4 || if (e.Key == Key.F4 ||
((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasFlagCustom(KeyModifiers.Alt))) ((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt)))
{ {
IsDropDownOpen = !IsDropDownOpen; IsDropDownOpen = !IsDropDownOpen;
e.Handled = true; e.Handled = true;

20
src/Avalonia.Controls/Control.cs

@ -40,6 +40,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<ContextMenu?> ContextMenuProperty = public static readonly StyledProperty<ContextMenu?> ContextMenuProperty =
AvaloniaProperty.Register<Control, ContextMenu?>(nameof(ContextMenu)); AvaloniaProperty.Register<Control, ContextMenu?>(nameof(ContextMenu));
/// <summary>
/// Defines the <see cref="ContextFlyout"/> property
/// </summary>
public static readonly StyledProperty<FlyoutBase?> ContextFlyoutProperty =
AvaloniaProperty.Register<Control, FlyoutBase?>(nameof(ContextFlyout));
/// <summary> /// <summary>
/// Event raised when an element wishes to be scrolled into view. /// Event raised when an element wishes to be scrolled into view.
/// </summary> /// </summary>
@ -76,6 +82,15 @@ namespace Avalonia.Controls
set => SetValue(ContextMenuProperty, value); set => SetValue(ContextMenuProperty, value);
} }
/// <summary>
/// Gets or sets a context flyout to the control
/// </summary>
public FlyoutBase? ContextFlyout
{
get => GetValue(ContextFlyoutProperty);
set => SetValue(ContextFlyoutProperty, value);
}
/// <summary> /// <summary>
/// Gets or sets a user-defined object attached to the control. /// Gets or sets a user-defined object attached to the control.
/// </summary> /// </summary>
@ -93,6 +108,11 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter) void ISetterValue.Initialize(ISetter setter)
{ {
if (setter is Setter s && s.Property == ContextFlyoutProperty)
{
return; // Allow ContextFlyout to not need wrapping in <Template>
}
throw new InvalidOperationException( throw new InvalidOperationException(
"Cannot use a control as a Setter value. Wrap the control in a <Template>."); "Cannot use a control as a Setter value. Wrap the control in a <Template>.");
} }

16
src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs

@ -72,24 +72,24 @@ namespace Avalonia.Controls.Converters
} }
} }
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Control)) if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Control))
{ {
s.Append("Ctrl"); s.Append("Ctrl");
} }
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Shift)) if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Shift))
{ {
Plus(s); Plus(s);
s.Append("Shift"); s.Append("Shift");
} }
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Alt)) if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Alt))
{ {
Plus(s); Plus(s);
s.Append("Alt"); s.Append("Alt");
} }
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Meta)) if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Meta))
{ {
Plus(s); Plus(s);
s.Append(meta); s.Append(meta);
@ -105,22 +105,22 @@ namespace Avalonia.Controls.Converters
{ {
var s = new StringBuilder(); var s = new StringBuilder();
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Control)) if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Control))
{ {
s.Append('⌃'); s.Append('⌃');
} }
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Alt)) if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Alt))
{ {
s.Append('⌥'); s.Append('⌥');
} }
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Shift)) if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Shift))
{ {
s.Append('⇧'); s.Append('⇧');
} }
if (gesture.KeyModifiers.HasFlagCustom(KeyModifiers.Meta)) if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Meta))
{ {
s.Append('⌘'); s.Append('⌘');
} }

50
src/Avalonia.Controls/Flyouts/Flyout.cs

@ -0,0 +1,50 @@
using Avalonia.Controls.Primitives;
using Avalonia.Metadata;
#nullable enable
namespace Avalonia.Controls
{
public class Flyout : FlyoutBase
{
/// <summary>
/// Defines the <see cref="Content"/> property
/// </summary>
public static readonly StyledProperty<object> ContentProperty =
AvaloniaProperty.Register<Flyout, object>(nameof(Content));
/// <summary>
/// Gets the Classes collection to apply to the FlyoutPresenter this Flyout is hosting
/// </summary>
public Classes FlyoutPresenterClasses => _classes ??= new Classes();
private Classes? _classes;
/// <summary>
/// Gets or sets the content to display in this flyout
/// </summary>
[Content]
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
protected override Control CreatePresenter()
{
return new FlyoutPresenter
{
[!ContentControl.ContentProperty] = this[!ContentProperty]
};
}
protected override void OnOpened()
{
if (_classes != null)
{
SetPresenterClasses(Popup.Child, FlyoutPresenterClasses);
}
base.OnOpened();
}
}
}

504
src/Avalonia.Controls/Flyouts/FlyoutBase.cs

@ -0,0 +1,504 @@
using System;
using System.ComponentModel;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Logging;
#nullable enable
namespace Avalonia.Controls.Primitives
{
public abstract class FlyoutBase : AvaloniaObject
{
static FlyoutBase()
{
Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged);
}
/// <summary>
/// Defines the <see cref="IsOpen"/> property
/// </summary>
private static readonly DirectProperty<FlyoutBase, bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<FlyoutBase, bool>(nameof(IsOpen),
x => x.IsOpen);
/// <summary>
/// Defines the <see cref="Target"/> property
/// </summary>
public static readonly DirectProperty<FlyoutBase, Control?> TargetProperty =
AvaloniaProperty.RegisterDirect<FlyoutBase, Control?>(nameof(Target), x => x.Target);
/// <summary>
/// Defines the <see cref="Placement"/> property
/// </summary>
public static readonly StyledProperty<FlyoutPlacementMode> PlacementProperty =
AvaloniaProperty.Register<FlyoutBase, FlyoutPlacementMode>(nameof(Placement));
/// <summary>
/// Defines the <see cref="ShowMode"/> property
/// </summary>
public static readonly DirectProperty<FlyoutBase, FlyoutShowMode> ShowModeProperty =
AvaloniaProperty.RegisterDirect<FlyoutBase, FlyoutShowMode>(nameof(ShowMode),
x => x.ShowMode, (x, v) => x.ShowMode = v);
/// <summary>
/// Defines the AttachedFlyout property
/// </summary>
public static readonly AttachedProperty<FlyoutBase?> AttachedFlyoutProperty =
AvaloniaProperty.RegisterAttached<FlyoutBase, Control, FlyoutBase?>("AttachedFlyout", null);
private bool _isOpen;
private Control? _target;
private FlyoutShowMode _showMode = FlyoutShowMode.Standard;
private Rect? enlargedPopupRect;
private IDisposable? transientDisposable;
protected Popup? Popup { get; private set; }
/// <summary>
/// Gets whether this Flyout is currently Open
/// </summary>
public bool IsOpen
{
get => _isOpen;
private set => SetAndRaise(IsOpenProperty, ref _isOpen, value);
}
/// <summary>
/// Gets or sets the desired placement
/// </summary>
public FlyoutPlacementMode Placement
{
get => GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
/// <summary>
/// Gets or sets the desired ShowMode
/// </summary>
public FlyoutShowMode ShowMode
{
get => _showMode;
set => SetAndRaise(ShowModeProperty, ref _showMode, value);
}
/// <summary>
/// Gets the Target used for showing the Flyout
/// </summary>
public Control? Target
{
get => _target;
private set => SetAndRaise(TargetProperty, ref _target, value);
}
public event EventHandler? Closed;
public event EventHandler<CancelEventArgs>? Closing;
public event EventHandler? Opened;
public event EventHandler? Opening;
public static FlyoutBase? GetAttachedFlyout(Control element)
{
return element.GetValue(AttachedFlyoutProperty);
}
public static void SetAttachedFlyout(Control element, FlyoutBase? value)
{
element.SetValue(AttachedFlyoutProperty, value);
}
public static void ShowAttachedFlyout(Control flyoutOwner)
{
var flyout = GetAttachedFlyout(flyoutOwner);
flyout?.ShowAt(flyoutOwner);
}
/// <summary>
/// Shows the Flyout at the given Control
/// </summary>
/// <param name="placementTarget">The control to show the Flyout at</param>
public void ShowAt(Control placementTarget)
{
ShowAtCore(placementTarget);
}
/// <summary>
/// Shows the Flyout for the given control at the current pointer location, as in a ContextFlyout
/// </summary>
/// <param name="placementTarget">The target control</param>
/// <param name="showAtPointer">True to show at pointer</param>
public void ShowAt(Control placementTarget, bool showAtPointer)
{
ShowAtCore(placementTarget, showAtPointer);
}
/// <summary>
/// Hides the Flyout
/// </summary>
public void Hide()
{
HideCore();
}
protected virtual void HideCore(bool canCancel = true)
{
if (!IsOpen)
{
return;
}
if (canCancel)
{
bool cancel = false;
var closing = new CancelEventArgs();
Closing?.Invoke(this, closing);
if (cancel || closing.Cancel)
{
return;
}
}
IsOpen = false;
Popup.IsOpen = false;
// Ensure this isn't active
transientDisposable?.Dispose();
transientDisposable = null;
OnClosed();
}
protected virtual void ShowAtCore(Control placementTarget, bool showAtPointer = false)
{
if (placementTarget == null)
throw new ArgumentNullException("placementTarget cannot be null");
if (Popup == null)
{
InitPopup();
}
if (IsOpen)
{
if (placementTarget == Target)
{
return;
}
else // Close before opening a new one
{
HideCore(false);
}
}
if (Popup.Parent != null && Popup.Parent != placementTarget)
{
((ISetLogicalParent)Popup).SetParent(null);
}
if (Popup.PlacementTarget != placementTarget)
{
Popup.PlacementTarget = Target = placementTarget;
((ISetLogicalParent)Popup).SetParent(placementTarget);
}
if (Popup.Child == null)
{
Popup.Child = CreatePresenter();
}
OnOpening();
PositionPopup(showAtPointer);
IsOpen = Popup.IsOpen = true;
OnOpened();
if (ShowMode == FlyoutShowMode.Standard)
{
// Try and focus content inside Flyout
if (Popup.Child.Focusable)
{
FocusManager.Instance?.Focus(Popup.Child);
}
else
{
var nextFocus = KeyboardNavigationHandler.GetNext(Popup.Child, NavigationDirection.Next);
if (nextFocus != null)
{
FocusManager.Instance?.Focus(nextFocus);
}
}
}
else if (ShowMode == FlyoutShowMode.TransientWithDismissOnPointerMoveAway)
{
transientDisposable = InputManager.Instance?.Process.Subscribe(HandleTransientDismiss);
}
}
private void HandleTransientDismiss(RawInputEventArgs args)
{
if (args is RawPointerEventArgs pArgs && pArgs.Type == RawPointerEventType.Move)
{
// In ShowMode = TransientWithDismissOnPointerMoveAway, the Flyout is kept
// shown as long as the pointer is within a certain px distance from the
// flyout itself. I'm not sure what WinUI uses, but I'm defaulting to
// 100px, which seems about right
// enlargedPopupRect is the Flyout bounds enlarged 100px
// For windowed popups, enlargedPopupRect is in screen coordinates,
// for overlay popups, its in OverlayLayer coordinates
if (enlargedPopupRect == null)
{
// Only do this once when the Flyout opens & cache the result
if (Popup?.Host is PopupRoot root)
{
// Get the popup root bounds and convert to screen coordinates
var tmp = root.Bounds.Inflate(100);
var scPt = root.PointToScreen(tmp.TopLeft);
enlargedPopupRect = new Rect(scPt.X, scPt.Y, tmp.Width, tmp.Height);
}
else if (Popup?.Host is OverlayPopupHost host)
{
// Overlay popups are in OverlayLayer coordinates, just use that
enlargedPopupRect = host.Bounds.Inflate(100);
}
return;
}
if (Popup?.Host is PopupRoot)
{
// As long as the pointer stays within the enlargedPopupRect
// the flyout stays open. If it leaves, close it
// Despite working in screen coordinates, leaving the TopLevel
// window will not close this (as pointer events stop), which
// does match UWP
var pt = pArgs.Root.PointToScreen(pArgs.Position);
if (!enlargedPopupRect?.Contains(new Point(pt.X, pt.Y)) ?? false)
{
HideCore(false);
enlargedPopupRect = null;
transientDisposable?.Dispose();
transientDisposable = null;
}
}
else if (Popup?.Host is OverlayPopupHost)
{
// Same as above here, but just different coordinate space
// so we don't need to translate
if (!enlargedPopupRect?.Contains(pArgs.Position) ?? false)
{
HideCore(false);
enlargedPopupRect = null;
transientDisposable?.Dispose();
transientDisposable = null;
}
}
}
}
protected virtual void OnOpening()
{
Opening?.Invoke(this, null);
}
protected virtual void OnOpened()
{
Opened?.Invoke(this, null);
}
protected virtual void OnClosing(CancelEventArgs args)
{
Closing?.Invoke(this, args);
}
protected virtual void OnClosed()
{
Closed?.Invoke(this, null);
}
/// <summary>
/// Used to create the content the Flyout displays
/// </summary>
/// <returns></returns>
protected abstract Control CreatePresenter();
private void InitPopup()
{
Popup = new Popup();
Popup.WindowManagerAddShadowHint = false;
Popup.IsLightDismissEnabled = true;
Popup.Opened += OnPopupOpened;
Popup.Closed += OnPopupClosed;
}
private void OnPopupOpened(object sender, EventArgs e)
{
IsOpen = true;
}
private void OnPopupClosed(object sender, EventArgs e)
{
HideCore();
}
private void PositionPopup(bool showAtPointer)
{
Size sz;
if(Popup.Child.DesiredSize == Size.Empty)
{
// Popup may not have been shown yet. Measure content
sz = LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness());
}
else
{
sz = Popup.Child.DesiredSize;
}
if (showAtPointer)
{
Popup.PlacementMode = PlacementMode.Pointer;
}
else
{
Popup.PlacementMode = PlacementMode.AnchorAndGravity;
Popup.PlacementConstraintAdjustment =
PopupPositioning.PopupPositionerConstraintAdjustment.SlideX |
PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;
}
var trgtBnds = Target?.Bounds ?? Rect.Empty;
switch (Placement)
{
case FlyoutPlacementMode.Top: //Above & centered
Popup.PlacementRect = new Rect(0, 0, trgtBnds.Width-1, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.Top;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Top;
break;
case FlyoutPlacementMode.TopEdgeAlignedLeft:
Popup.PlacementRect = new Rect(0, 0, 0, 0);
Popup.PlacementGravity = PopupPositioning.PopupGravity.TopRight;
break;
case FlyoutPlacementMode.TopEdgeAlignedRight:
Popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 10, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.TopLeft;
break;
case FlyoutPlacementMode.RightEdgeAlignedTop:
Popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 1, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.BottomRight;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Right;
break;
case FlyoutPlacementMode.Right: //Right & centered
Popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 1, trgtBnds.Height);
Popup.PlacementGravity = PopupPositioning.PopupGravity.Right;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Right;
break;
case FlyoutPlacementMode.RightEdgeAlignedBottom:
Popup.PlacementRect = new Rect(trgtBnds.Width - 1, trgtBnds.Height - 1, 1, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.TopRight;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Right;
break;
case FlyoutPlacementMode.Bottom: //Below & centered
Popup.PlacementRect = new Rect(0, trgtBnds.Height - 1, trgtBnds.Width, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.Bottom;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Bottom;
break;
case FlyoutPlacementMode.BottomEdgeAlignedLeft:
Popup.PlacementRect = new Rect(0, trgtBnds.Height - 1, 1, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.BottomRight;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Bottom;
break;
case FlyoutPlacementMode.BottomEdgeAlignedRight:
Popup.PlacementRect = new Rect(trgtBnds.Width - 1, trgtBnds.Height - 1, 1, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.BottomLeft;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Bottom;
break;
case FlyoutPlacementMode.LeftEdgeAlignedTop:
Popup.PlacementRect = new Rect(0, 0, 1, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.BottomLeft;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Left;
break;
case FlyoutPlacementMode.Left: //Left & centered
Popup.PlacementRect = new Rect(0, 0, 1, trgtBnds.Height);
Popup.PlacementGravity = PopupPositioning.PopupGravity.Left;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Left;
break;
case FlyoutPlacementMode.LeftEdgeAlignedBottom:
Popup.PlacementRect = new Rect(0, trgtBnds.Height - 1, 1, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.TopLeft;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.BottomLeft;
break;
//includes Auto (not sure what determines that)...
default:
//This is just FlyoutPlacementMode.Top behavior (above & centered)
Popup.PlacementRect = new Rect(-sz.Width / 2, 0, sz.Width, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.Top;
break;
}
}
private static void OnContextFlyoutPropertyChanged(AvaloniaPropertyChangedEventArgs args)
{
if (args.Sender is Control c)
{
if (args.OldValue is FlyoutBase)
{
c.PointerReleased -= OnControlWithContextFlyoutPointerReleased;
}
if (args.NewValue is FlyoutBase)
{
c.PointerReleased += OnControlWithContextFlyoutPointerReleased;
}
}
}
private static void OnControlWithContextFlyoutPointerReleased(object sender, PointerReleasedEventArgs e)
{
if (sender is Control c)
{
if (e.InitialPressMouseButton == MouseButton.Right &&
e.GetCurrentPoint(c).Properties.PointerUpdateKind == PointerUpdateKind.RightButtonReleased)
{
if (c.ContextFlyout != null)
{
if (c.ContextMenu != null)
{
Logger.TryGet(LogEventLevel.Verbose, "FlyoutBase")?.Log(c, "ContextMenu and ContextFlyout are both set, defaulting to ContextMenu");
return;
}
c.ContextFlyout.ShowAt(c, true);
}
}
}
}
internal static void SetPresenterClasses(IControl presenter, Classes classes)
{
//Remove any classes no longer in use, ignoring pseudoclasses
for (int i = presenter.Classes.Count - 1; i >= 0; i--)
{
if (!classes.Contains(presenter.Classes[i]) &&
!presenter.Classes[i].Contains(":"))
{
presenter.Classes.RemoveAt(i);
}
}
//Add new classes
presenter.Classes.AddRange(classes);
}
}
}

77
src/Avalonia.Controls/Flyouts/FlyoutPlacementMode.cs

@ -0,0 +1,77 @@
namespace Avalonia.Controls
{
public enum FlyoutPlacementMode
{
/// <summary>
/// Preferred location is above the target element
/// </summary>
Top = 0,
/// <summary>
/// Preferred location is below the target element
/// </summary>
Bottom = 1,
/// <summary>
/// Preferred location is to the left of the target element
/// </summary>
Left = 2,
/// <summary>
/// Preferred location is to the right of the target element
/// </summary>
Right = 3,
//TODO
// <summary>
// Preferred location is centered on the screen
// </summary>
//Full = 4,
/// <summary>
/// Preferred location is above the target element, with the left edge of the flyout
/// aligned with the left edge of the target element
/// </summary>
TopEdgeAlignedLeft = 5,
/// <summary>
/// Preferred location is above the target element, with the right edge of flyout aligned with right edge of the target element.
/// </summary>
TopEdgeAlignedRight = 6,
/// <summary>
/// Preferred location is below the target element, with the left edge of flyout aligned with left edge of the target element.
/// </summary>
BottomEdgeAlignedLeft = 7,
/// <summary>
/// Preferred location is below the target element, with the right edge of flyout aligned with right edge of the target element.
/// </summary>
BottomEdgeAlignedRight = 8,
/// <summary>
/// Preferred location is to the left of the target element, with the top edge of flyout aligned with top edge of the target element.
/// </summary>
LeftEdgeAlignedTop = 9,
/// <summary>
/// Preferred location is to the left of the target element, with the bottom edge of flyout aligned with bottom edge of the target element.
/// </summary>
LeftEdgeAlignedBottom = 10,
/// <summary>
/// Preferred location is to the right of the target element, with the top edge of flyout aligned with top edge of the target element.
/// </summary>
RightEdgeAlignedTop = 11,
/// <summary>
/// Preferred location is to the right of the target element, with the bottom edge of flyout aligned with bottom edge of the target element.
/// </summary>
RightEdgeAlignedBottom = 12,
/// <summary>
/// Preferred location is determined automatically.
/// </summary>
Auto = 13
}
}

33
src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs

@ -0,0 +1,33 @@
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.LogicalTree;
namespace Avalonia.Controls
{
public class FlyoutPresenter : ContentControl
{
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<FlyoutPresenter>();
public CornerRadius CornerRadius
{
get => GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
var host = this.FindLogicalAncestorOfType<Popup>();
if (host != null)
{
host.IsOpen = false;
e.Handled = true;
}
}
base.OnKeyDown(e);
}
}
}

24
src/Avalonia.Controls/Flyouts/FlyoutShowMode.cs

@ -0,0 +1,24 @@
namespace Avalonia.Controls
{
// Note: FlyoutShowMode.Auto was removed. MS Docs just say:
// The show mode is determined automatically based on the method used to show the flyout.
// and AFAICT Flyouts generally open with "Standard" behavior
public enum FlyoutShowMode
{
/// <summary>
/// Behavior is typical of a flyout shown reactively, like a context menu. The open flyout takes focus. For a CommandBarFlyout, it opens in it's expanded state.
/// </summary>
Standard,
/// <summary>
/// Behavior is typical of a flyout shown proactively. The open flyout does not take focus. For a CommandBarFlyout, it opens in it's collapsed state.
/// </summary>
Transient,
/// <summary>
/// The flyout exhibits Transient behavior while the cursor is close to it, but is dismissed when the cursor moves away.
/// </summary>
TransientWithDismissOnPointerMoveAway
}
}

75
src/Avalonia.Controls/Flyouts/MenuFlyout.cs

@ -0,0 +1,75 @@
using System.Collections;
using Avalonia.Collections;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Metadata;
#nullable enable
namespace Avalonia.Controls
{
public class MenuFlyout : FlyoutBase
{
public MenuFlyout()
{
_items = new AvaloniaList<object>();
}
/// <summary>
/// Defines the <see cref="Items"/> property
/// </summary>
public static readonly DirectProperty<MenuFlyout, IEnumerable> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<MenuFlyout>(x => x.Items,
(x, v) => x.Items = v);
/// <summary>
/// Defines the <see cref="ItemTemplate"/> property
/// </summary>
public static readonly DirectProperty<MenuFlyout, IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.RegisterDirect<MenuFlyout, IDataTemplate?>(nameof(ItemTemplate),
x => x.ItemTemplate, (x, v) => x.ItemTemplate = v);
public Classes FlyoutPresenterClasses => _classes ??= new Classes();
/// <summary>
/// Gets or sets the items of the MenuFlyout
/// </summary>
[Content]
public IEnumerable Items
{
get => _items;
set => SetAndRaise(ItemsProperty, ref _items, value);
}
/// <summary>
/// Gets or sets the template used for the items
/// </summary>
public IDataTemplate? ItemTemplate
{
get => _itemTemplate;
set => SetAndRaise(ItemTemplateProperty, ref _itemTemplate, value);
}
private Classes? _classes;
private IEnumerable _items;
private IDataTemplate? _itemTemplate;
protected override Control CreatePresenter()
{
return new MenuFlyoutPresenter
{
[!ItemsControl.ItemsProperty] = this[!ItemsProperty],
[!ItemsControl.ItemTemplateProperty] = this[!ItemTemplateProperty]
};
}
protected override void OnOpened()
{
if (_classes != null)
{
SetPresenterClasses(Popup.Child, FlyoutPresenterClasses);
}
base.OnOpened();
}
}
}

55
src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs

@ -0,0 +1,55 @@
using System;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
namespace Avalonia.Controls
{
public class MenuFlyoutPresenter : MenuBase
{
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<FlyoutPresenter>();
public CornerRadius CornerRadius
{
get => GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public MenuFlyoutPresenter()
:base(new DefaultMenuInteractionHandler(true))
{
}
public override void Close()
{
// DefaultMenuInteractionHandler calls this
var host = this.FindLogicalAncestorOfType<Popup>();
if (host != null)
{
for (int i = 0; i < LogicalChildren.Count; i++)
{
if (LogicalChildren[i] is MenuItem item)
{
item.IsSubMenuOpen = false;
}
}
SelectedIndex = -1;
host.IsOpen = false;
}
}
public override void Open()
{
throw new NotSupportedException("Use MenuFlyout.ShowAt(Control) instead");
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
}
}
}

10
src/Avalonia.Controls/Grid.cs

@ -2355,7 +2355,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private bool CheckFlags(Flags flags) private bool CheckFlags(Flags flags)
{ {
return _flags.HasFlagCustom(flags); return _flags.HasAllFlags(flags);
} }
private static void OnShowGridLinesPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) private static void OnShowGridLinesPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
@ -2790,10 +2790,10 @@ namespace Avalonia.Controls
internal LayoutTimeSizeType SizeTypeU; internal LayoutTimeSizeType SizeTypeU;
internal LayoutTimeSizeType SizeTypeV; internal LayoutTimeSizeType SizeTypeV;
internal int Next; internal int Next;
internal bool IsStarU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Star); internal bool IsStarU => SizeTypeU.HasAllFlags(LayoutTimeSizeType.Star);
internal bool IsAutoU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Auto); internal bool IsAutoU => SizeTypeU.HasAllFlags(LayoutTimeSizeType.Auto);
internal bool IsStarV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Star); internal bool IsStarV => SizeTypeV.HasAllFlags(LayoutTimeSizeType.Star);
internal bool IsAutoV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Auto); internal bool IsAutoV => SizeTypeV.HasAllFlags(LayoutTimeSizeType.Auto);
} }
/// <summary> /// <summary>

22
src/Avalonia.Controls/GridSplitter.cs

@ -609,10 +609,20 @@ namespace Avalonia.Controls
private void MoveSplitter(double horizontalChange, double verticalChange) private void MoveSplitter(double horizontalChange, double verticalChange)
{ {
Debug.Assert(_resizeData != null, "_resizeData should not be null when calling MoveSplitter"); Debug.Assert(_resizeData != null, "_resizeData should not be null when calling MoveSplitter");
// Calculate the offset to adjust the splitter. // Calculate the offset to adjust the splitter. If layout rounding is enabled, we
var delta = _resizeData.ResizeDirection == GridResizeDirection.Columns ? horizontalChange : verticalChange; // need to round to an integer physical pixel value to avoid round-ups of children that
// expand the bounds of the Grid. In practice this only happens in high dpi because
// horizontal/vertical offsets here are never fractional (they correspond to mouse movement
// across logical pixels). Rounding error only creeps in when converting to a physical
// display with something other than the logical 96 dpi.
double delta = _resizeData.ResizeDirection == GridResizeDirection.Columns ? horizontalChange : verticalChange;
if (UseLayoutRounding)
{
delta = LayoutHelper.RoundLayoutValue(delta, LayoutHelper.GetLayoutScale(this));
}
DefinitionBase definition1 = _resizeData.Definition1; DefinitionBase definition1 = _resizeData.Definition1;
DefinitionBase definition2 = _resizeData.Definition2; DefinitionBase definition2 = _resizeData.Definition2;
@ -622,11 +632,11 @@ namespace Avalonia.Controls
double actualLength2 = GetActualLength(definition2); double actualLength2 = GetActualLength(definition2);
// When splitting, Check to see if the total pixels spanned by the definitions // When splitting, Check to see if the total pixels spanned by the definitions
// is the same asbefore starting resize. If not cancel the drag // is the same as before starting resize. If not cancel the drag.
if (_resizeData.SplitBehavior == SplitBehavior.Split && if (_resizeData.SplitBehavior == SplitBehavior.Split &&
!MathUtilities.AreClose( !MathUtilities.AreClose(
actualLength1 + actualLength2, actualLength1 + actualLength2,
_resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength)) _resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength, LayoutHelper.LayoutEpsilon))
{ {
CancelResize(); CancelResize();

8
src/Avalonia.Controls/ListBox.cs

@ -135,8 +135,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource( e.Handled = UpdateSelectionFromEventSource(
e.Source, e.Source,
true, true,
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift), e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
e.KeyModifiers.HasFlagCustom(KeyModifiers.Control)); e.KeyModifiers.HasAllFlags(KeyModifiers.Control));
} }
} }
@ -154,8 +154,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource( e.Handled = UpdateSelectionFromEventSource(
e.Source, e.Source,
true, true,
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift), e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
e.KeyModifiers.HasFlagCustom(KeyModifiers.Control), e.KeyModifiers.HasAllFlags(KeyModifiers.Control),
point.Properties.IsRightButtonPressed); point.Properties.IsRightButtonPressed);
} }
} }

24
src/Avalonia.Controls/NativeControlHost.cs

@ -16,30 +16,16 @@ namespace Avalonia.Controls
private bool _queuedForDestruction; private bool _queuedForDestruction;
private bool _queuedForMoveResize; private bool _queuedForMoveResize;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>(); private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
static NativeControlHost()
{
IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
}
public NativeControlHost()
{
_propertyChangedHandler = PropertyChangedHandler;
}
private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
=> host.UpdateHost();
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_currentRoot = e.Root as TopLevel; _currentRoot = e.Root as TopLevel;
var visual = (IVisual)this; var visual = (IVisual)this;
while (visual != _currentRoot) while (visual != null)
{ {
if (visual is Visual v) if (visual is Visual v)
{ {
v.PropertyChanged += _propertyChangedHandler; v.PropertyChanged += PropertyChangedHandler;
_propertyChangedSubscriptions.Add(v); _propertyChangedSubscriptions.Add(v);
} }
@ -51,7 +37,7 @@ namespace Avalonia.Controls
private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e) private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
{ {
if (e.IsEffectiveValueChange && e.Property == BoundsProperty) if (e.IsEffectiveValueChange && (e.Property == BoundsProperty || e.Property == IsVisibleProperty))
EnqueueForMoveResize(); EnqueueForMoveResize();
} }
@ -61,7 +47,7 @@ namespace Avalonia.Controls
if (_propertyChangedSubscriptions != null) if (_propertyChangedSubscriptions != null)
{ {
foreach (var v in _propertyChangedSubscriptions) foreach (var v in _propertyChangedSubscriptions)
v.PropertyChanged -= _propertyChangedHandler; v.PropertyChanged -= PropertyChangedHandler;
_propertyChangedSubscriptions.Clear(); _propertyChangedSubscriptions.Clear();
} }
UpdateHost(); UpdateHost();
@ -134,7 +120,7 @@ namespace Avalonia.Controls
private Rect? GetAbsoluteBounds() private Rect? GetAbsoluteBounds()
{ {
var bounds = Bounds; var bounds = Bounds;
var position = this.TranslatePoint(bounds.Position, _currentRoot); var position = this.TranslatePoint(default, _currentRoot);
if (position == null) if (position == null)
return null; return null;
return new Rect(position.Value, bounds.Size); return new Rect(position.Value, bounds.Size);

2
src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs

@ -16,7 +16,7 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// The default for the platform. /// The default for the platform.
/// </summary> /// </summary>
Default = SystemChrome, Default = PreferSystemChrome,
/// <summary> /// <summary>
/// Use SystemChrome /// Use SystemChrome

12
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) if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None)
return effect; // No need to check for the modifiers. return effect; // No need to check for the modifiers.
if (effect.HasFlagCustom(DragDropEffects.Link) && modifiers.HasFlagCustom(RawInputModifiers.Alt)) if (effect.HasAllFlags(DragDropEffects.Link) && modifiers.HasAllFlags(RawInputModifiers.Alt))
return DragDropEffects.Link; return DragDropEffects.Link;
if (effect.HasFlagCustom(DragDropEffects.Copy) && modifiers.HasFlagCustom(RawInputModifiers.Control)) if (effect.HasAllFlags(DragDropEffects.Copy) && modifiers.HasAllFlags(RawInputModifiers.Control))
return DragDropEffects.Copy; return DragDropEffects.Copy;
return DragDropEffects.Move; return DragDropEffects.Move;
} }
private StandardCursorType GetCursorForDropEffect(DragDropEffects effects) private StandardCursorType GetCursorForDropEffect(DragDropEffects effects)
{ {
if (effects.HasFlagCustom(DragDropEffects.Copy)) if (effects.HasAllFlags(DragDropEffects.Copy))
return StandardCursorType.DragCopy; return StandardCursorType.DragCopy;
if (effects.HasFlagCustom(DragDropEffects.Move)) if (effects.HasAllFlags(DragDropEffects.Move))
return StandardCursorType.DragMove; return StandardCursorType.DragMove;
if (effects.HasFlagCustom(DragDropEffects.Link)) if (effects.HasAllFlags(DragDropEffects.Link))
return StandardCursorType.DragLink; return StandardCursorType.DragLink;
return StandardCursorType.No; return StandardCursorType.No;
} }
@ -161,7 +161,7 @@ namespace Avalonia.Platform
void CheckDraggingAccepted(RawInputModifiers changedMouseButton) void CheckDraggingAccepted(RawInputModifiers changedMouseButton)
{ {
if (_initialInputModifiers.Value.HasFlagCustom(changedMouseButton)) if (_initialInputModifiers.Value.HasAllFlags(changedMouseButton))
{ {
var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers); var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers);
UpdateCursor(null, DragDropEffects.None); UpdateCursor(null, DragDropEffects.None);

14
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -378,7 +378,7 @@ namespace Avalonia.Controls.Presenters
var useLayoutRounding = UseLayoutRounding; var useLayoutRounding = UseLayoutRounding;
var availableSize = finalSize; var availableSize = finalSize;
var sizeForChild = availableSize; var sizeForChild = availableSize;
var scale = GetLayoutScale(); var scale = LayoutHelper.GetLayoutScale(this);
var originX = offset.X; var originX = offset.X;
var originY = offset.Y; var originY = offset.Y;
@ -462,18 +462,6 @@ namespace Avalonia.Controls.Presenters
PseudoClasses.Set(":empty", Content is null); PseudoClasses.Set(":empty", Content is null);
} }
private double GetLayoutScale()
{
var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
if (result == 0 || double.IsNaN(result) || double.IsInfinity(result))
{
throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}");
}
return result;
}
private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e) private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e)
{ {
var host = e.NewValue as IContentPresenterHost; var host = e.NewValue as IContentPresenterHost;

12
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -253,8 +253,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
{ {
public static void ValidateEdge(this PopupAnchor edge) public static void ValidateEdge(this PopupAnchor edge)
{ {
if (edge.HasFlagCustom(PopupAnchor.Left) && edge.HasFlagCustom(PopupAnchor.Right) || if (edge.HasAllFlags(PopupAnchor.Left | PopupAnchor.Right) ||
edge.HasFlagCustom(PopupAnchor.Top) && edge.HasFlagCustom(PopupAnchor.Bottom)) edge.HasAllFlags(PopupAnchor.Top | PopupAnchor.Bottom))
throw new ArgumentException("Opposite edges specified"); throw new ArgumentException("Opposite edges specified");
} }
@ -265,10 +265,10 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
public static PopupAnchor Flip(this PopupAnchor edge) public static PopupAnchor Flip(this PopupAnchor edge)
{ {
if (edge.HasFlagCustom(PopupAnchor.HorizontalMask)) if (edge.HasAnyFlag(PopupAnchor.HorizontalMask))
edge ^= PopupAnchor.HorizontalMask; edge ^= PopupAnchor.HorizontalMask;
if (edge.HasFlagCustom(PopupAnchor.VerticalMask)) if (edge.HasAnyFlag(PopupAnchor.VerticalMask))
edge ^= PopupAnchor.VerticalMask; edge ^= PopupAnchor.VerticalMask;
return edge; return edge;
@ -276,14 +276,14 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
public static PopupAnchor FlipX(this PopupAnchor edge) public static PopupAnchor FlipX(this PopupAnchor edge)
{ {
if (edge.HasFlagCustom(PopupAnchor.HorizontalMask)) if (edge.HasAnyFlag(PopupAnchor.HorizontalMask))
edge ^= PopupAnchor.HorizontalMask; edge ^= PopupAnchor.HorizontalMask;
return edge; return edge;
} }
public static PopupAnchor FlipY(this PopupAnchor edge) public static PopupAnchor FlipY(this PopupAnchor edge)
{ {
if (edge.HasFlagCustom(PopupAnchor.VerticalMask)) if (edge.HasAnyFlag(PopupAnchor.VerticalMask))
edge ^= PopupAnchor.VerticalMask; edge ^= PopupAnchor.VerticalMask;
return edge; return edge;
} }

36
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@ -42,16 +42,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
private static Point GetAnchorPoint(Rect anchorRect, PopupAnchor edge) private static Point GetAnchorPoint(Rect anchorRect, PopupAnchor edge)
{ {
double x, y; double x, y;
if (edge.HasFlagCustom(PopupAnchor.Left)) if (edge.HasAllFlags(PopupAnchor.Left))
x = anchorRect.X; x = anchorRect.X;
else if (edge.HasFlagCustom(PopupAnchor.Right)) else if (edge.HasAllFlags(PopupAnchor.Right))
x = anchorRect.Right; x = anchorRect.Right;
else else
x = anchorRect.X + anchorRect.Width / 2; x = anchorRect.X + anchorRect.Width / 2;
if (edge.HasFlagCustom(PopupAnchor.Top)) if (edge.HasAllFlags(PopupAnchor.Top))
y = anchorRect.Y; y = anchorRect.Y;
else if (edge.HasFlagCustom(PopupAnchor.Bottom)) else if (edge.HasAllFlags(PopupAnchor.Bottom))
y = anchorRect.Bottom; y = anchorRect.Bottom;
else else
y = anchorRect.Y + anchorRect.Height / 2; 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) private static Point Gravitate(Point anchorPoint, Size size, PopupGravity gravity)
{ {
double x, y; double x, y;
if (gravity.HasFlagCustom(PopupGravity.Left)) if (gravity.HasAllFlags(PopupGravity.Left))
x = -size.Width; x = -size.Width;
else if (gravity.HasFlagCustom(PopupGravity.Right)) else if (gravity.HasAllFlags(PopupGravity.Right))
x = 0; x = 0;
else else
x = -size.Width / 2; x = -size.Width / 2;
if (gravity.HasFlagCustom(PopupGravity.Top)) if (gravity.HasAllFlags(PopupGravity.Top))
y = -size.Height; y = -size.Height;
else if (gravity.HasFlagCustom(PopupGravity.Bottom)) else if (gravity.HasAllFlags(PopupGravity.Bottom))
y = 0; y = 0;
else else
y = -size.Height / 2; y = -size.Height / 2;
@ -125,10 +125,10 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
bool FitsInBounds(Rect rc, PopupAnchor edge = PopupAnchor.AllMask) bool FitsInBounds(Rect rc, PopupAnchor edge = PopupAnchor.AllMask)
{ {
if (edge.HasFlagCustom(PopupAnchor.Left) && rc.X < bounds.X || if (edge.HasAllFlags(PopupAnchor.Left) && rc.X < bounds.X ||
edge.HasFlagCustom(PopupAnchor.Top) && rc.Y < bounds.Y || edge.HasAllFlags(PopupAnchor.Top) && rc.Y < bounds.Y ||
edge.HasFlagCustom(PopupAnchor.Right) && rc.Right > bounds.Right || edge.HasAllFlags(PopupAnchor.Right) && rc.Right > bounds.Right ||
edge.HasFlagCustom(PopupAnchor.Bottom) && rc.Bottom > bounds.Bottom) edge.HasAllFlags(PopupAnchor.Bottom) && rc.Bottom > bounds.Bottom)
{ {
return false; return false;
} }
@ -147,7 +147,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
// If flipping geometry and anchor is allowed and helps, use the flipped one, // If flipping geometry and anchor is allowed and helps, use the flipped one,
// otherwise leave it as is // otherwise leave it as is
if (!FitsInBounds(geo, PopupAnchor.HorizontalMask) if (!FitsInBounds(geo, PopupAnchor.HorizontalMask)
&& constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipX)) && constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.FlipX))
{ {
var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX()); var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX());
if (FitsInBounds(flipped, PopupAnchor.HorizontalMask)) if (FitsInBounds(flipped, PopupAnchor.HorizontalMask))
@ -155,7 +155,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
} }
// If sliding is allowed, try moving the rect into the bounds // If sliding is allowed, try moving the rect into the bounds
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideX)) if (constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.SlideX))
{ {
geo = geo.WithX(Math.Max(geo.X, bounds.X)); geo = geo.WithX(Math.Max(geo.X, bounds.X));
if (geo.Right > bounds.Right) if (geo.Right > bounds.Right)
@ -163,7 +163,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
} }
// Resize the rect horizontally if allowed. // Resize the rect horizontally if allowed.
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeX)) if (constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.ResizeX))
{ {
var unconstrainedRect = geo; var unconstrainedRect = geo;
@ -186,7 +186,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
// If flipping geometry and anchor is allowed and helps, use the flipped one, // If flipping geometry and anchor is allowed and helps, use the flipped one,
// otherwise leave it as is // otherwise leave it as is
if (!FitsInBounds(geo, PopupAnchor.VerticalMask) if (!FitsInBounds(geo, PopupAnchor.VerticalMask)
&& constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipY)) && constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.FlipY))
{ {
var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY()); var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY());
if (FitsInBounds(flipped, PopupAnchor.VerticalMask)) if (FitsInBounds(flipped, PopupAnchor.VerticalMask))
@ -194,7 +194,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
} }
// If sliding is allowed, try moving the rect into the bounds // If sliding is allowed, try moving the rect into the bounds
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideY)) if (constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.SlideY))
{ {
geo = geo.WithY(Math.Max(geo.Y, bounds.Y)); geo = geo.WithY(Math.Max(geo.Y, bounds.Y));
if (geo.Bottom > bounds.Bottom) if (geo.Bottom > bounds.Bottom)
@ -202,7 +202,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
} }
// Resize the rect vertically if allowed. // Resize the rect vertically if allowed.
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeY)) if (constraintAdjustment.HasAllFlags(PopupPositionerConstraintAdjustment.ResizeY))
{ {
var unconstrainedRect = geo; var unconstrainedRect = geo;

12
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -321,7 +321,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Gets a value indicating whether <see cref="SelectionMode.AlwaysSelected"/> is set. /// Gets a value indicating whether <see cref="SelectionMode.AlwaysSelected"/> is set.
/// </summary> /// </summary>
protected bool AlwaysSelected => SelectionMode.HasFlagCustom(SelectionMode.AlwaysSelected); protected bool AlwaysSelected => SelectionMode.HasAllFlags(SelectionMode.AlwaysSelected);
/// <inheritdoc/> /// <inheritdoc/>
public override void BeginInit() public override void BeginInit()
@ -501,7 +501,7 @@ namespace Avalonia.Controls.Primitives
if (ItemCount > 0 && if (ItemCount > 0 &&
Match(keymap.SelectAll) && Match(keymap.SelectAll) &&
SelectionMode.HasFlagCustom(SelectionMode.Multiple)) SelectionMode.HasAllFlags(SelectionMode.Multiple))
{ {
Selection.SelectAll(); Selection.SelectAll();
e.Handled = true; e.Handled = true;
@ -530,7 +530,7 @@ namespace Avalonia.Controls.Primitives
else if (change.Property == SelectionModeProperty && _selection is object) else if (change.Property == SelectionModeProperty && _selection is object)
{ {
var newValue = change.NewValue.GetValueOrDefault<SelectionMode>(); var newValue = change.NewValue.GetValueOrDefault<SelectionMode>();
_selection.SingleSelect = !newValue.HasFlagCustom(SelectionMode.Multiple); _selection.SingleSelect = !newValue.HasAllFlags(SelectionMode.Multiple);
} }
} }
@ -591,8 +591,8 @@ namespace Avalonia.Controls.Primitives
} }
var mode = SelectionMode; var mode = SelectionMode;
var multi = mode.HasFlagCustom(SelectionMode.Multiple); var multi = mode.HasAllFlags(SelectionMode.Multiple);
var toggle = toggleModifier || mode.HasFlagCustom(SelectionMode.Toggle); var toggle = toggleModifier || mode.HasAllFlags(SelectionMode.Toggle);
var range = multi && rangeModifier; var range = multi && rangeModifier;
if (!select) if (!select)
@ -869,7 +869,7 @@ namespace Avalonia.Controls.Primitives
{ {
return new InternalSelectionModel return new InternalSelectionModel
{ {
SingleSelect = !SelectionMode.HasFlagCustom(SelectionMode.Multiple), SingleSelect = !SelectionMode.HasAllFlags(SelectionMode.Multiple),
}; };
} }

2
src/Avalonia.Controls/ProgressBar.cs

@ -138,6 +138,8 @@ namespace Avalonia.Controls
static ProgressBar() static ProgressBar()
{ {
ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e)); ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MinimumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MaximumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e)); IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
} }

5
src/Avalonia.Controls/RelativePanel.cs

@ -55,7 +55,10 @@ namespace Avalonia.Controls
_childGraph.Measure(availableSize); _childGraph.Measure(availableSize);
_childGraph.Reset(false); _childGraph.Reset(false);
var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN()); var calcWidth = Width.IsNaN() && HorizontalAlignment != HorizontalAlignment.Stretch;
var calcHeight = Height.IsNaN() && VerticalAlignment != VerticalAlignment.Stretch;
var boundingSize = _childGraph.GetBoundingSize(calcWidth, calcHeight);
_childGraph.Reset(); _childGraph.Reset();
_childGraph.Measure(boundingSize); _childGraph.Measure(boundingSize);
return boundingSize; return boundingSize;

4
src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs

@ -53,8 +53,8 @@ namespace Avalonia.Controls
{ {
return _owner.GetElementImpl( return _owner.GetElementImpl(
index, index,
options.HasFlagCustom(ElementRealizationOptions.ForceCreate), options.HasAllFlags(ElementRealizationOptions.ForceCreate),
options.HasFlagCustom(ElementRealizationOptions.SuppressAutoRecycle)); options.HasAllFlags(ElementRealizationOptions.SuppressAutoRecycle));
} }
protected override object GetItemAtCore(int index) => _owner.ItemsSourceView.GetAt(index); protected override object GetItemAtCore(int index) => _owner.ItemsSourceView.GetAt(index);

2
src/Avalonia.Controls/ScrollViewer.cs

@ -120,7 +120,7 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty = public static readonly AttachedProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>( AvaloniaProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>(
nameof(HorizontalScrollBarVisibility), nameof(HorizontalScrollBarVisibility),
ScrollBarVisibility.Hidden); ScrollBarVisibility.Disabled);
/// <summary> /// <summary>
/// Defines the VerticalScrollBarMaximum property. /// Defines the VerticalScrollBarMaximum property.

84
src/Avalonia.Controls/Selection/InternalSelectionModel.cs

@ -13,8 +13,9 @@ namespace Avalonia.Controls.Selection
internal class InternalSelectionModel : SelectionModel<object?> internal class InternalSelectionModel : SelectionModel<object?>
{ {
private IList? _writableSelectedItems; private IList? _writableSelectedItems;
private bool _ignoreModelChanges; private int _ignoreModelChanges;
private bool _ignoreSelectedItemsChanges; private bool _ignoreSelectedItemsChanges;
private bool _isResetting;
public InternalSelectionModel() public InternalSelectionModel()
{ {
@ -79,10 +80,12 @@ namespace Avalonia.Controls.Selection
try try
{ {
_ignoreSelectedItemsChanges = true; _ignoreSelectedItemsChanges = true;
++_ignoreModelChanges;
base.SetSource(value); base.SetSource(value);
} }
finally finally
{ {
--_ignoreModelChanges;
_ignoreSelectedItemsChanges = false; _ignoreSelectedItemsChanges = false;
} }
@ -92,17 +95,14 @@ namespace Avalonia.Controls.Selection
} }
else else
{ {
foreach (var i in oldSelection) SyncFromSelectedItems();
{
var index = ItemsView!.IndexOf(i);
Select(index);
}
} }
} }
private void SyncToSelectedItems() private void SyncToSelectedItems()
{ {
if (_writableSelectedItems is object) if (_writableSelectedItems is object &&
!SequenceEqual(_writableSelectedItems, base.SelectedItems))
{ {
try try
{ {
@ -130,17 +130,31 @@ namespace Avalonia.Controls.Selection
try try
{ {
_ignoreModelChanges = true; ++_ignoreModelChanges;
using (BatchUpdate()) using (BatchUpdate())
{ {
Clear(); Clear();
Add(_writableSelectedItems);
for (var i = 0; i < _writableSelectedItems.Count; ++i)
{
var index = IndexOf(Source, _writableSelectedItems[i]);
if (index != -1)
{
Select(index);
}
else
{
_writableSelectedItems.RemoveAt(i);
--i;
}
}
} }
} }
finally finally
{ {
_ignoreModelChanges = false; --_ignoreModelChanges;
} }
} }
@ -162,7 +176,7 @@ namespace Avalonia.Controls.Selection
private void OnSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) private void OnSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
{ {
if (_ignoreModelChanges) if (_ignoreModelChanges > 0)
{ {
return; return;
} }
@ -191,6 +205,28 @@ namespace Avalonia.Controls.Selection
} }
} }
private protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
++_ignoreModelChanges;
_isResetting = true;
}
base.OnSourceCollectionChanged(e);
}
protected override void OnSourceCollectionChangeFinished()
{
base.OnSourceCollectionChangeFinished();
if (_isResetting)
{
--_ignoreModelChanges;
_isResetting = false;
}
}
private void OnSourceReset(object sender, EventArgs e) => SyncFromSelectedItems(); private void OnSourceReset(object sender, EventArgs e) => SyncFromSelectedItems();
private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
@ -222,7 +258,7 @@ namespace Avalonia.Controls.Selection
{ {
using var operation = BatchUpdate(); using var operation = BatchUpdate();
_ignoreModelChanges = true; ++_ignoreModelChanges;
switch (e.Action) switch (e.Action)
{ {
@ -244,7 +280,7 @@ namespace Avalonia.Controls.Selection
} }
finally finally
{ {
_ignoreModelChanges = false; --_ignoreModelChanges;
} }
} }
@ -274,5 +310,27 @@ namespace Avalonia.Controls.Selection
return -1; return -1;
} }
private static bool SequenceEqual(IList first, IReadOnlyList<object?> second)
{
if (first is IEnumerable<object?> e)
{
return e.SequenceEqual(second);
}
var comparer = EqualityComparer<object?>.Default;
var e1 = first.GetEnumerator();
using var e2 = second.GetEnumerator();
while (e1.MoveNext())
{
if (!(e2.MoveNext() && comparer.Equals(e1.Current, e2.Current)))
{
return false;
}
}
return !e2.MoveNext();
}
} }
} }

4
src/Avalonia.Controls/Slider.cs

@ -341,7 +341,9 @@ namespace Avalonia.Controls
var pointNum = orient ? x.Position.X : x.Position.Y; var pointNum = orient ? x.Position.X : x.Position.Y;
var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d); var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d);
var invert = orient ? 0 : 1; var invert = orient ?
IsDirectionReversed ? 1 : 0 :
IsDirectionReversed ? 0 : 1;
var calcVal = Math.Abs(invert - logicalPos); var calcVal = Math.Abs(invert - logicalPos);
var range = Maximum - Minimum; var range = Maximum - Minimum;
var finalValue = calcVal * range + Minimum; var finalValue = calcVal * range + Minimum;

7
src/Avalonia.Controls/TextBox.cs

@ -492,7 +492,8 @@ namespace Avalonia.Controls
{ {
base.OnLostFocus(e); base.OnLostFocus(e);
if (ContextMenu == null || !ContextMenu.IsOpen) if ((ContextFlyout == null || !ContextFlyout.IsOpen) &&
(ContextMenu == null || !ContextMenu.IsOpen))
{ {
ClearSelection(); ClearSelection();
RevealPassword = false; RevealPassword = false;
@ -600,7 +601,7 @@ namespace Avalonia.Controls
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>(); var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e)); bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.KeyModifiers.HasFlagCustom(keymap.SelectionModifiers); bool DetectSelection() => e.KeyModifiers.HasAllFlags(keymap.SelectionModifiers);
if (Match(keymap.SelectAll)) if (Match(keymap.SelectAll))
{ {
@ -718,7 +719,7 @@ namespace Avalonia.Controls
} }
else else
{ {
bool hasWholeWordModifiers = modifiers.HasFlagCustom(keymap.WholeWordTextActionModifiers); bool hasWholeWordModifiers = modifiers.HasAllFlags(keymap.WholeWordTextActionModifiers);
switch (e.Key) switch (e.Key)
{ {
case Key.Left: case Key.Left:

10
src/Avalonia.Controls/TreeView.cs

@ -412,7 +412,7 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource( e.Handled = UpdateSelectionFromEventSource(
e.Source, e.Source,
true, true,
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift)); e.KeyModifiers.HasAllFlags(KeyModifiers.Shift));
} }
} }
@ -521,8 +521,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource( e.Handled = UpdateSelectionFromEventSource(
e.Source, e.Source,
true, true,
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift), e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
e.KeyModifiers.HasFlagCustom(KeyModifiers.Control), e.KeyModifiers.HasAllFlags(KeyModifiers.Control),
point.Properties.IsRightButtonPressed); point.Properties.IsRightButtonPressed);
} }
} }
@ -558,8 +558,8 @@ namespace Avalonia.Controls
} }
var mode = SelectionMode; var mode = SelectionMode;
var toggle = toggleModifier || mode.HasFlagCustom(SelectionMode.Toggle); var toggle = toggleModifier || mode.HasAllFlags(SelectionMode.Toggle);
var multi = mode.HasFlagCustom(SelectionMode.Multiple); var multi = mode.HasAllFlags(SelectionMode.Multiple);
var range = multi && rangeModifier && selectedContainer != null; var range = multi && rangeModifier && selectedContainer != null;
if (rightButton) if (rightButton)

8
src/Avalonia.Controls/Window.cs

@ -859,19 +859,19 @@ namespace Avalonia.Controls
var constraint = clientSize; var constraint = clientSize;
var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity; var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity;
if (sizeToContent.HasFlagCustom(SizeToContent.Width)) if (sizeToContent.HasAllFlags(SizeToContent.Width))
{ {
constraint = constraint.WithWidth(maxAutoSize.Width); constraint = constraint.WithWidth(maxAutoSize.Width);
} }
if (sizeToContent.HasFlagCustom(SizeToContent.Height)) if (sizeToContent.HasAllFlags(SizeToContent.Height))
{ {
constraint = constraint.WithHeight(maxAutoSize.Height); constraint = constraint.WithHeight(maxAutoSize.Height);
} }
var result = base.MeasureOverride(constraint); var result = base.MeasureOverride(constraint);
if (!sizeToContent.HasFlagCustom(SizeToContent.Width)) if (!sizeToContent.HasAllFlags(SizeToContent.Width))
{ {
if (!double.IsInfinity(availableSize.Width)) if (!double.IsInfinity(availableSize.Width))
{ {
@ -883,7 +883,7 @@ namespace Avalonia.Controls
} }
} }
if (!sizeToContent.HasFlagCustom(SizeToContent.Height)) if (!sizeToContent.HasAllFlags(SizeToContent.Height))
{ {
if (!double.IsInfinity(availableSize.Height)) if (!double.IsInfinity(availableSize.Height))
{ {

12
src/Avalonia.Diagnostics/DevToolsExtensions.cs

@ -15,7 +15,7 @@ namespace Avalonia
/// <param name="root">The window to attach DevTools to.</param> /// <param name="root">The window to attach DevTools to.</param>
public static void AttachDevTools(this TopLevel root) public static void AttachDevTools(this TopLevel root)
{ {
DevTools.Attach(root, new KeyGesture(Key.F12)); DevTools.Attach(root, new DevToolsOptions());
} }
/// <summary> /// <summary>
@ -27,5 +27,15 @@ namespace Avalonia
{ {
DevTools.Attach(root, gesture); DevTools.Attach(root, gesture);
} }
/// <summary>
/// Attaches DevTools to a window, to be opened with the specified options.
/// </summary>
/// <param name="root">The window to attach DevTools to.</param>
/// <param name="options">Additional settings of DevTools.</param>
public static void AttachDevTools(this TopLevel root, DevToolsOptions options)
{
DevTools.Attach(root, options);
}
} }
} }

24
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -6,6 +6,8 @@ using Avalonia.Diagnostics.Views;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
#nullable enable
namespace Avalonia.Diagnostics namespace Avalonia.Diagnostics
{ {
public static class DevTools public static class DevTools
@ -13,12 +15,20 @@ namespace Avalonia.Diagnostics
private static readonly Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>(); private static readonly Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>();
public static IDisposable Attach(TopLevel root, KeyGesture gesture) public static IDisposable Attach(TopLevel root, KeyGesture gesture)
{
return Attach(root, new DevToolsOptions()
{
Gesture = gesture,
});
}
public static IDisposable Attach(TopLevel root, DevToolsOptions options)
{ {
void PreviewKeyDown(object sender, KeyEventArgs e) void PreviewKeyDown(object sender, KeyEventArgs e)
{ {
if (gesture.Matches(e)) if (options.Gesture.Matches(e))
{ {
Open(root); Open(root, options);
} }
} }
@ -28,7 +38,9 @@ namespace Avalonia.Diagnostics
RoutingStrategies.Tunnel); RoutingStrategies.Tunnel);
} }
public static IDisposable Open(TopLevel root) public static IDisposable Open(TopLevel root) => Open(root, new DevToolsOptions());
public static IDisposable Open(TopLevel root, DevToolsOptions options)
{ {
if (s_open.TryGetValue(root, out var window)) if (s_open.TryGetValue(root, out var window))
{ {
@ -38,15 +50,15 @@ namespace Avalonia.Diagnostics
{ {
window = new MainWindow window = new MainWindow
{ {
Width = 1024,
Height = 512,
Root = root, Root = root,
Width = options.Size.Width,
Height = options.Size.Height,
}; };
window.Closed += DevToolsClosed; window.Closed += DevToolsClosed;
s_open.Add(root, window); s_open.Add(root, window);
if (root is Window inspectedWindow) if (options.ShowAsChildWindow && root is Window inspectedWindow)
{ {
window.Show(inspectedWindow); window.Show(inspectedWindow);
} }

26
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -0,0 +1,26 @@
using Avalonia.Input;
namespace Avalonia.Diagnostics
{
/// <summary>
/// Describes options used to customize DevTools.
/// </summary>
public class DevToolsOptions
{
/// <summary>
/// Gets or sets the key gesture used to open DevTools.
/// </summary>
public KeyGesture Gesture { get; set; } = new KeyGesture(Key.F12);
/// <summary>
/// Gets or sets a value indicating whether DevTools should be displayed as a child window
/// of the window being inspected. The default value is true.
/// </summary>
public bool ShowAsChildWindow { get; set; } = true;
/// <summary>
/// Gets or sets the initial size of the DevTools window. The default value is 1024x512.
/// </summary>
public Size Size { get; set; } = new Size(1024, 512);
}
}

16
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs

@ -76,13 +76,13 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
protected override async Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode) protected override async Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
{ {
FcitxKeyState state = default; FcitxKeyState state = default;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Control)) if (args.Modifiers.HasAllFlags(RawInputModifiers.Control))
state |= FcitxKeyState.FcitxKeyState_Ctrl; state |= FcitxKeyState.FcitxKeyState_Ctrl;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Alt)) if (args.Modifiers.HasAllFlags(RawInputModifiers.Alt))
state |= FcitxKeyState.FcitxKeyState_Alt; state |= FcitxKeyState.FcitxKeyState_Alt;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Shift)) if (args.Modifiers.HasAllFlags(RawInputModifiers.Shift))
state |= FcitxKeyState.FcitxKeyState_Shift; state |= FcitxKeyState.FcitxKeyState_Shift;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Meta)) if (args.Modifiers.HasAllFlags(RawInputModifiers.Meta))
state |= FcitxKeyState.FcitxKeyState_Super; state |= FcitxKeyState.FcitxKeyState_Super;
var type = args.Type == RawKeyEventType.KeyDown ? var type = args.Type == RawKeyEventType.KeyDown ?
@ -126,13 +126,13 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{ {
var state = (FcitxKeyState)ev.state; var state = (FcitxKeyState)ev.state;
KeyModifiers mods = default; KeyModifiers mods = default;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Ctrl)) if (state.HasAllFlags(FcitxKeyState.FcitxKeyState_Ctrl))
mods |= KeyModifiers.Control; mods |= KeyModifiers.Control;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Alt)) if (state.HasAllFlags(FcitxKeyState.FcitxKeyState_Alt))
mods |= KeyModifiers.Alt; mods |= KeyModifiers.Alt;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Shift)) if (state.HasAllFlags(FcitxKeyState.FcitxKeyState_Shift))
mods |= KeyModifiers.Shift; mods |= KeyModifiers.Shift;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Super)) if (state.HasAllFlags(FcitxKeyState.FcitxKeyState_Super))
mods |= KeyModifiers.Meta; mods |= KeyModifiers.Meta;
FireForward(new X11InputMethodForwardedKey FireForward(new X11InputMethodForwardedKey
{ {

18
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs

@ -33,18 +33,18 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
{ {
var state = (IBusModifierMask)k.state; var state = (IBusModifierMask)k.state;
KeyModifiers mods = default; KeyModifiers mods = default;
if (state.HasFlagCustom(IBusModifierMask.ControlMask)) if (state.HasAllFlags(IBusModifierMask.ControlMask))
mods |= KeyModifiers.Control; mods |= KeyModifiers.Control;
if (state.HasFlagCustom(IBusModifierMask.Mod1Mask)) if (state.HasAllFlags(IBusModifierMask.Mod1Mask))
mods |= KeyModifiers.Alt; mods |= KeyModifiers.Alt;
if (state.HasFlagCustom(IBusModifierMask.ShiftMask)) if (state.HasAllFlags(IBusModifierMask.ShiftMask))
mods |= KeyModifiers.Shift; mods |= KeyModifiers.Shift;
if (state.HasFlagCustom(IBusModifierMask.Mod4Mask)) if (state.HasAllFlags(IBusModifierMask.Mod4Mask))
mods |= KeyModifiers.Meta; mods |= KeyModifiers.Meta;
FireForward(new X11InputMethodForwardedKey FireForward(new X11InputMethodForwardedKey
{ {
KeyVal = (int)k.keyval, KeyVal = (int)k.keyval,
Type = state.HasFlagCustom(IBusModifierMask.ReleaseMask) ? RawKeyEventType.KeyUp : RawKeyEventType.KeyDown, Type = state.HasAllFlags(IBusModifierMask.ReleaseMask) ? RawKeyEventType.KeyUp : RawKeyEventType.KeyDown,
Modifiers = mods Modifiers = mods
}); });
} }
@ -82,13 +82,13 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
protected override Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode) protected override Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
{ {
IBusModifierMask state = default; IBusModifierMask state = default;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Control)) if (args.Modifiers.HasAllFlags(RawInputModifiers.Control))
state |= IBusModifierMask.ControlMask; state |= IBusModifierMask.ControlMask;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Alt)) if (args.Modifiers.HasAllFlags(RawInputModifiers.Alt))
state |= IBusModifierMask.Mod1Mask; state |= IBusModifierMask.Mod1Mask;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Shift)) if (args.Modifiers.HasAllFlags(RawInputModifiers.Shift))
state |= IBusModifierMask.ShiftMask; state |= IBusModifierMask.ShiftMask;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Meta)) if (args.Modifiers.HasAllFlags(RawInputModifiers.Meta))
state |= IBusModifierMask.Mod4Mask; state |= IBusModifierMask.Mod4Mask;
if (args.Type == RawKeyEventType.KeyUp) if (args.Type == RawKeyEventType.KeyUp)

8
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -223,13 +223,13 @@ namespace Avalonia.FreeDesktop
return null; return null;
var lst = new List<string>(); var lst = new List<string>();
var mod = item.Gesture; var mod = item.Gesture;
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Control)) if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Control))
lst.Add("Control"); lst.Add("Control");
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Alt)) if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Alt))
lst.Add("Alt"); lst.Add("Alt");
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Shift)) if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Shift))
lst.Add("Shift"); lst.Add("Shift");
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Meta)) if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Meta))
lst.Add("Super"); lst.Add("Super");
lst.Add(item.Gesture.Key.ToString()); lst.Add(item.Gesture.Key.ToString());
return new[] { lst.ToArray() }; return new[] { lst.ToArray() };

4
src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs

@ -33,11 +33,11 @@ namespace Avalonia.Headless.Vnc
{ {
Window?.MouseMove(pt); Window?.MouseMove(pt);
foreach (var btn in CheckedButtons) foreach (var btn in CheckedButtons)
if (_previousButtons.HasFlagCustom(btn) && !buttons.HasFlagCustom(btn)) if (_previousButtons.HasAllFlags(btn) && !buttons.HasAllFlags(btn))
Window?.MouseUp(pt, TranslateButton(btn), modifiers); Window?.MouseUp(pt, TranslateButton(btn), modifiers);
foreach (var btn in CheckedButtons) foreach (var btn in CheckedButtons)
if (!_previousButtons.HasFlagCustom(btn) && buttons.HasFlagCustom(btn)) if (!_previousButtons.HasAllFlags(btn) && buttons.HasAllFlags(btn))
Window?.MouseDown(pt, TranslateButton(btn), modifiers); Window?.MouseDown(pt, TranslateButton(btn), modifiers);
_previousButtons = buttons; _previousButtons = buttons;
}, DispatcherPriority.Input); }, DispatcherPriority.Input);

32
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -104,6 +104,9 @@ namespace Avalonia.Headless
} }
public Rect Bounds { get; set; } public Rect Bounds { get; set; }
public double ContourLength { get; } = 0;
public virtual bool FillContains(Point point) => Bounds.Contains(point); public virtual bool FillContains(Point point) => Bounds.Contains(point);
public Rect GetRenderBounds(IPen pen) public Rect GetRenderBounds(IPen pen)
@ -126,6 +129,25 @@ namespace Avalonia.Headless
public ITransformedGeometryImpl WithTransform(Matrix transform) => public ITransformedGeometryImpl WithTransform(Matrix transform) =>
new HeadlessTransformedGeometryStub(this, transform); new HeadlessTransformedGeometryStub(this, transform);
public bool TryGetPointAtDistance(double distance, out Point point)
{
point = new Point();
return false;
}
public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
{
point = new Point();
tangent = new Point();
return false;
}
public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
{
segmentGeometry = null;
return false;
}
} }
class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl
@ -359,6 +381,16 @@ namespace Avalonia.Headless
} }
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
}
public void PopBitmapBlendMode()
{
}
public void Custom(ICustomDrawOperation custom) public void Custom(ICustomDrawOperation custom)
{ {

2
src/Avalonia.Input/AccessKeyHandler.cs

@ -177,7 +177,7 @@ namespace Avalonia.Input
{ {
bool menuIsOpen = MainMenu?.IsOpen == true; bool menuIsOpen = MainMenu?.IsOpen == true;
if (e.KeyModifiers.HasFlagCustom(KeyModifiers.Alt) || menuIsOpen) if (e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) || menuIsOpen)
{ {
// If any other key is pressed with the Alt key held down, or the main menu is open, // If any other key is pressed with the Alt key held down, or the main menu is open,
// find all controls who have registered that access key. // find all controls who have registered that access key.

8
src/Avalonia.Input/KeyGesture.cs

@ -115,24 +115,24 @@ namespace Avalonia.Input
} }
} }
if (KeyModifiers.HasFlagCustom(KeyModifiers.Control)) if (KeyModifiers.HasAllFlags(KeyModifiers.Control))
{ {
s.Append("Ctrl"); s.Append("Ctrl");
} }
if (KeyModifiers.HasFlagCustom(KeyModifiers.Shift)) if (KeyModifiers.HasAllFlags(KeyModifiers.Shift))
{ {
Plus(s); Plus(s);
s.Append("Shift"); s.Append("Shift");
} }
if (KeyModifiers.HasFlagCustom(KeyModifiers.Alt)) if (KeyModifiers.HasAllFlags(KeyModifiers.Alt))
{ {
Plus(s); Plus(s);
s.Append("Alt"); s.Append("Alt");
} }
if (KeyModifiers.HasFlagCustom(KeyModifiers.Meta)) if (KeyModifiers.HasAllFlags(KeyModifiers.Meta))
{ {
Plus(s); Plus(s);
s.Append("Cmd"); s.Append("Cmd");

4
src/Avalonia.Input/PointerEventArgs.cs

@ -107,7 +107,9 @@ namespace Avalonia.Input
None, None,
Left, Left,
Right, Right,
Middle Middle,
XButton1,
XButton2
} }
public class PointerPressedEventArgs : PointerEventArgs public class PointerPressedEventArgs : PointerEventArgs

14
src/Avalonia.Input/PointerPoint.cs

@ -31,11 +31,11 @@ namespace Avalonia.Input
{ {
PointerUpdateKind = kind; PointerUpdateKind = kind;
IsLeftButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.LeftMouseButton); IsLeftButtonPressed = modifiers.HasAllFlags(RawInputModifiers.LeftMouseButton);
IsMiddleButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.MiddleMouseButton); IsMiddleButtonPressed = modifiers.HasAllFlags(RawInputModifiers.MiddleMouseButton);
IsRightButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.RightMouseButton); IsRightButtonPressed = modifiers.HasAllFlags(RawInputModifiers.RightMouseButton);
IsXButton1Pressed = modifiers.HasFlagCustom(RawInputModifiers.XButton1MouseButton); IsXButton1Pressed = modifiers.HasAllFlags(RawInputModifiers.XButton1MouseButton);
IsXButton2Pressed = modifiers.HasFlagCustom(RawInputModifiers.XButton2MouseButton); IsXButton2Pressed = modifiers.HasAllFlags(RawInputModifiers.XButton2MouseButton);
// The underlying input source might be reporting the previous state, // The underlying input source might be reporting the previous state,
// so make sure that we reflect the current state // so make sure that we reflect the current state
@ -90,6 +90,10 @@ namespace Avalonia.Input
return MouseButton.Middle; return MouseButton.Middle;
if (kind == PointerUpdateKind.RightButtonPressed || kind == PointerUpdateKind.RightButtonReleased) if (kind == PointerUpdateKind.RightButtonPressed || kind == PointerUpdateKind.RightButtonReleased)
return MouseButton.Right; return MouseButton.Right;
if (kind == PointerUpdateKind.XButton1Pressed || kind == PointerUpdateKind.XButton1Released)
return MouseButton.XButton1;
if (kind == PointerUpdateKind.XButton2Pressed || kind == PointerUpdateKind.XButton2Released)
return MouseButton.XButton2;
return MouseButton.None; return MouseButton.None;
} }
} }

6
src/Avalonia.Interactivity/EventRoute.cs

@ -88,14 +88,14 @@ namespace Avalonia.Interactivity
} }
else else
{ {
if (_event.RoutingStrategies.HasFlagCustom(RoutingStrategies.Tunnel)) if (_event.RoutingStrategies.HasAllFlags(RoutingStrategies.Tunnel))
{ {
e.Route = RoutingStrategies.Tunnel; e.Route = RoutingStrategies.Tunnel;
RaiseEventImpl(e); RaiseEventImpl(e);
_event.InvokeRouteFinished(e); _event.InvokeRouteFinished(e);
} }
if (_event.RoutingStrategies.HasFlagCustom(RoutingStrategies.Bubble)) if (_event.RoutingStrategies.HasAllFlags(RoutingStrategies.Bubble))
{ {
e.Route = RoutingStrategies.Bubble; e.Route = RoutingStrategies.Bubble;
RaiseEventImpl(e); RaiseEventImpl(e);
@ -159,7 +159,7 @@ namespace Avalonia.Interactivity
// Raise the event handler. // Raise the event handler.
if (entry.Handler is object && if (entry.Handler is object &&
entry.Routes.HasFlagCustom(e.Route) && entry.Routes.HasAllFlags(e.Route) &&
(!e.Handled || entry.HandledEventsToo)) (!e.Handled || entry.HandledEventsToo))
{ {
if (entry.Adapter is object) if (entry.Adapter is object)

4
src/Avalonia.Interactivity/Interactive.cs

@ -155,8 +155,8 @@ namespace Avalonia.Interactivity
var result = new EventRoute(e); var result = new EventRoute(e);
var hasClassHandlers = e.HasRaisedSubscriptions; var hasClassHandlers = e.HasRaisedSubscriptions;
if (e.RoutingStrategies.HasFlagCustom(RoutingStrategies.Bubble) || if (e.RoutingStrategies.HasAllFlags(RoutingStrategies.Bubble) ||
e.RoutingStrategies.HasFlagCustom(RoutingStrategies.Tunnel)) e.RoutingStrategies.HasAllFlags(RoutingStrategies.Tunnel))
{ {
IInteractive? element = this; IInteractive? element = this;

25
src/Avalonia.Layout/LayoutHelper.cs

@ -9,6 +9,12 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
public static class LayoutHelper public static class LayoutHelper
{ {
/// <summary>
/// Epsilon value used for certain layout calculations.
/// Based on the value in WPF LayoutDoubleUtil.
/// </summary>
public static double LayoutEpsilon { get; } = 0.00000153;
/// <summary> /// <summary>
/// Calculates a control's size based on its <see cref="ILayoutable.Width"/>, /// Calculates a control's size based on its <see cref="ILayoutable.Width"/>,
/// <see cref="ILayoutable.Height"/>, <see cref="ILayoutable.MinWidth"/>, /// <see cref="ILayoutable.Height"/>, <see cref="ILayoutable.MinWidth"/>,
@ -82,6 +88,25 @@ namespace Avalonia.Layout
InnerInvalidateMeasure(control); InnerInvalidateMeasure(control);
} }
/// <summary>
/// Obtains layout scale of the given control.
/// </summary>
/// <param name="control">The control.</param>
/// <exception cref="Exception">Thrown when control has no root or returned layout scaling is invalid.</exception>
public static double GetLayoutScale(ILayoutable control)
{
var visualRoot = control.VisualRoot;
var result = (visualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
if (result == 0 || double.IsNaN(result) || double.IsInfinity(result))
{
throw new Exception($"Invalid LayoutScaling returned from {visualRoot.GetType()}");
}
return result;
}
/// <summary> /// <summary>
/// Rounds a size to integer values for layout purposes, compensating for high DPI screen /// Rounds a size to integer values for layout purposes, compensating for high DPI screen
/// coordinates. /// coordinates.

16
src/Avalonia.Layout/Layoutable.cs

@ -590,7 +590,7 @@ namespace Avalonia.Layout
if (UseLayoutRounding) if (UseLayoutRounding)
{ {
var scale = GetLayoutScale(); var scale = LayoutHelper.GetLayoutScale(this);
width = LayoutHelper.RoundLayoutValue(width, scale); width = LayoutHelper.RoundLayoutValue(width, scale);
height = LayoutHelper.RoundLayoutValue(height, scale); height = LayoutHelper.RoundLayoutValue(height, scale);
} }
@ -652,7 +652,7 @@ namespace Avalonia.Layout
var horizontalAlignment = HorizontalAlignment; var horizontalAlignment = HorizontalAlignment;
var verticalAlignment = VerticalAlignment; var verticalAlignment = VerticalAlignment;
var size = availableSizeMinusMargins; var size = availableSizeMinusMargins;
var scale = GetLayoutScale(); var scale = LayoutHelper.GetLayoutScale(this);
var useLayoutRounding = UseLayoutRounding; var useLayoutRounding = UseLayoutRounding;
if (horizontalAlignment != HorizontalAlignment.Stretch) if (horizontalAlignment != HorizontalAlignment.Stretch)
@ -833,17 +833,5 @@ namespace Avalonia.Layout
{ {
return new Size(Math.Max(size.Width, 0), Math.Max(size.Height, 0)); return new Size(Math.Max(size.Width, 0), Math.Max(size.Height, 0));
} }
private double GetLayoutScale()
{
var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
if (result == 0 || double.IsNaN(result) || double.IsInfinity(result))
{
throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}");
}
return result;
}
} }
} }

2
src/Avalonia.Native/avn.idl

@ -397,7 +397,7 @@ enum AvnExtendClientAreaChromeHints
AvnSystemChrome = 0x01, AvnSystemChrome = 0x01,
AvnPreferSystemChrome = 0x02, AvnPreferSystemChrome = 0x02,
AvnOSXThickTitleBar = 0x08, AvnOSXThickTitleBar = 0x08,
AvnDefaultChrome = AvnSystemChrome, AvnDefaultChrome = AvnPreferSystemChrome,
} }
[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)]

18
src/Avalonia.ReactiveUI/AppBuilderExtensions.cs

@ -9,18 +9,22 @@ namespace Avalonia.ReactiveUI
{ {
/// <summary> /// <summary>
/// Initializes ReactiveUI framework to use with Avalonia. Registers Avalonia /// Initializes ReactiveUI framework to use with Avalonia. Registers Avalonia
/// scheduler and Avalonia activation for view fetcher. Always remember to /// scheduler, an activation for view fetcher, a template binding hook. Remember
/// call this method if you are using ReactiveUI in your application. /// to call this method if you are using ReactiveUI in your application.
/// </summary> /// </summary>
public static TAppBuilder UseReactiveUI<TAppBuilder>(this TAppBuilder builder) public static TAppBuilder UseReactiveUI<TAppBuilder>(this TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new() where TAppBuilder : AppBuilderBase<TAppBuilder>, new() =>
{ builder.AfterPlatformServicesSetup(_ => Locator.RegisterResolverCallbackChanged(() =>
return builder.AfterPlatformServicesSetup(_ =>
{ {
if (Locator.CurrentMutable is null)
{
return;
}
PlatformRegistrationManager.SetRegistrationNamespaces(RegistrationNamespace.Avalonia);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
}); }));
}
} }
} }

31
src/Avalonia.Styling/ClassBindingManager.cs

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using Avalonia.Data;
namespace Avalonia
{
internal static class ClassBindingManager
{
private static readonly Dictionary<string, AvaloniaProperty> s_RegisteredProperties =
new Dictionary<string, AvaloniaProperty>();
public static IDisposable Bind(IStyledElement target, string className, IBinding source, object anchor)
{
if (!s_RegisteredProperties.TryGetValue(className, out var prop))
s_RegisteredProperties[className] = prop = RegisterClassProxyProperty(className);
return target.Bind(prop, source, anchor);
}
private static AvaloniaProperty RegisterClassProxyProperty(string className)
{
var prop = AvaloniaProperty.Register<StyledElement, bool>("__AvaloniaReserved::Classes::" + className);
prop.Changed.Subscribe(args =>
{
var classes = ((IStyledElement)args.Sender).Classes;
classes.Set(className, args.NewValue.GetValueOrDefault());
});
return prop;
}
}
}

21
src/Avalonia.Styling/Controls/Classes.cs

@ -265,5 +265,26 @@ namespace Avalonia.Controls
$"The pseudoclass '{name}' may only be {operation} by the control itself."); $"The pseudoclass '{name}' may only be {operation} by the control itself.");
} }
} }
/// <summary>
/// Adds a or removes a style class to/from the collection.
/// </summary>
/// <param name="name">The class names.</param>
/// <param name="value">If true adds the class, if false, removes it.</param>
/// <remarks>
/// Only standard classes may be added or removed via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public void Set(string name, bool value)
{
if (value)
{
if (!Contains(name))
Add(name);
}
else
Remove(name);
}
} }
} }

11
src/Avalonia.Styling/StyledElementExtensions.cs

@ -0,0 +1,11 @@
using System;
using Avalonia.Data;
namespace Avalonia
{
public static class StyledElementExtensions
{
public static IDisposable BindClass(this IStyledElement target, string className, IBinding source, object anchor) =>
ClassBindingManager.Bind(target, className, source, anchor);
}
}

2
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -60,4 +60,6 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.SplitView.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.SplitView.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.DatePicker.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.DatePicker.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TimePicker.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.TimePicker.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.FlyoutPresenter.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.MenuFlyoutPresenter.xaml?assembly=Avalonia.Themes.Default"/>
</Styles> </Styles>

32
src/Avalonia.Themes.Default/FlyoutPresenter.xaml

@ -0,0 +1,32 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="FlyoutPresenter">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="4" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="LayoutRoot"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Styles>

29
src/Avalonia.Themes.Default/MenuFlyoutPresenter.xaml

@ -0,0 +1,29 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="MenuFlyoutPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="LayoutRoot"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="{TemplateBinding Padding}"
KeyboardNavigation.TabNavigation="Continue"
Grid.IsSharedSizeScope="True" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Styles>

9
src/Avalonia.Themes.Default/TextBox.xaml

@ -3,6 +3,13 @@
<StreamGeometry x:Key="TextBoxClearButtonData">M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z</StreamGeometry> <StreamGeometry x:Key="TextBoxClearButtonData">M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry>
<MenuFlyout x:Key="DefaultTextBoxContextFlyout">
<MenuItem x:Name="TextBoxContextFlyoutCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextFlyoutCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
<MenuItem x:Name="TextBoxContextFlyoutPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>
</MenuFlyout>
<ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu"> <ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu">
<MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" /> <MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/> <MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
@ -17,7 +24,7 @@
<Setter Property="SelectionBrush" Value="{DynamicResource HighlightBrush}"/> <Setter Property="SelectionBrush" Value="{DynamicResource HighlightBrush}"/>
<Setter Property="SelectionForegroundBrush" Value="{DynamicResource HighlightForegroundBrush}"/> <Setter Property="SelectionForegroundBrush" Value="{DynamicResource HighlightForegroundBrush}"/>
<Setter Property="Padding" Value="4"/> <Setter Property="Padding" Value="4"/>
<Setter Property="ContextMenu" Value="{StaticResource DefaultTextBoxContextMenu}" /> <Setter Property="ContextFlyout" Value="{StaticResource DefaultTextBoxContextFlyout}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Name="border" <Border Name="border"

5
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -809,5 +809,10 @@
<StaticResource x:Key="CalendarDatePickerForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="CalendarDatePickerForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarDatePickerBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="CalendarDatePickerBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<Thickness x:Key="CalendarDatePickerBorderThemeThickness">1</Thickness> <Thickness x:Key="CalendarDatePickerBorderThemeThickness">1</Thickness>
<!-- Resources for FlyoutPresenter.xaml -->
<StaticResource x:Key="FlyoutPresenterBackground" ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="FlyoutBorderThemeBrush" ResourceKey="SystemControlForegroundChromeHighBrush" />
</Style.Resources> </Style.Resources>
</Style> </Style>

5
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@ -807,5 +807,10 @@
<StaticResource x:Key="CalendarDatePickerForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="CalendarDatePickerForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarDatePickerBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" /> <StaticResource x:Key="CalendarDatePickerBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<Thickness x:Key="CalendarDatePickerBorderThemeThickness">1</Thickness> <Thickness x:Key="CalendarDatePickerBorderThemeThickness">1</Thickness>
<!-- Resources for FlyoutPresenter.xaml -->
<StaticResource x:Key="FlyoutPresenterBackground" ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="FlyoutBorderThemeBrush" ResourceKey="SystemControlForegroundChromeHighBrush" />
</Style.Resources> </Style.Resources>
</Style> </Style>

2
src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml

@ -35,7 +35,7 @@
<ItemsControl Items="{Binding}"> <ItemsControl Items="{Binding}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding Message}" Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}" TextWrapping="Wrap" /> <TextBlock Text="{Binding }" Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}" TextWrapping="Wrap" />
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>

2
src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml

@ -59,4 +59,6 @@
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/SplitView.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/SplitView.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/DatePicker.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/DatePicker.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TimePicker.xaml"/> <StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TimePicker.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml"/>
</Styles> </Styles>

43
src/Avalonia.Themes.Fluent/Controls/FlyoutPresenter.xaml

@ -0,0 +1,43 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<Thickness x:Key="FlyoutBorderThemeThickness">1</Thickness>
<Thickness x:Key="FlyoutBorderThemePadding">0</Thickness>
</Styles.Resources>
<Style Selector="FlyoutPresenter">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="{DynamicResource FlyoutPresenterBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource FlyoutBorderThemeBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource FlyoutBorderThemeThickness}" />
<Setter Property="Padding" Value="{DynamicResource FlyoutContentThemePadding}" />
<Setter Property="MinWidth" Value="{DynamicResource FlyoutThemeMinWidth}" />
<Setter Property="MaxWidth" Value="{DynamicResource FlyoutThemeMaxWidth}" />
<Setter Property="MinHeight" Value="{DynamicResource FlyoutThemeMinHeight}" />
<Setter Property="MaxHeight" Value="{DynamicResource FlyoutThemeMaxHeight}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="CornerRadius" Value="{DynamicResource OverlayCornerRadius}" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="LayoutRoot"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{DynamicResource FlyoutBorderThemePadding}"
CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Styles>

35
src/Avalonia.Themes.Fluent/Controls/MenuFlyoutPresenter.xaml

@ -0,0 +1,35 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="MenuFlyoutPresenter">
<Setter Property="Background" Value="{DynamicResource MenuFlyoutPresenterBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" />
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutPresenterThemePadding}" />
<Setter Property="MaxWidth" Value="{DynamicResource FlyoutThemeMaxWidth}" />
<Setter Property="MinHeight" Value="{DynamicResource MenuFlyoutThemeMinHeight}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="CornerRadius" Value="{DynamicResource OverlayCornerRadius}" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="LayoutRoot"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{DynamicResource FlyoutBorderThemePadding}"
CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="{DynamicResource MenuFlyoutScrollerMargin}"
KeyboardNavigation.TabNavigation="Continue"
Grid.IsSharedSizeScope="True" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Styles>

9
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -14,6 +14,13 @@
<StreamGeometry x:Key="TextBoxClearButtonData">M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z</StreamGeometry> <StreamGeometry x:Key="TextBoxClearButtonData">M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry>
<MenuFlyout x:Key="DefaultTextBoxContextFlyout">
<MenuItem x:Name="TextBoxContextFlyoutCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextFlyoutCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
<MenuItem x:Name="TextBoxContextFlyoutPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>
</MenuFlyout>
<ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu"> <ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu">
<MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" /> <MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/> <MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
@ -33,7 +40,7 @@
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" /> <Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" /> <Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="FocusAdorner" Value="{x:Null}" /> <Setter Property="FocusAdorner" Value="{x:Null}" />
<Setter Property="ContextMenu" Value="{StaticResource DefaultTextBoxContextMenu}" /> <Setter Property="ContextFlyout" Value="{StaticResource DefaultTextBoxContextFlyout}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>

9
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -0,0 +1,9 @@
Compat issues with assembly Avalonia.Visuals:
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PopBitmapBlendMode()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PushBitmapBlendMode(Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract.
Total Issues: 7

13
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -121,12 +121,23 @@ namespace Avalonia.Media
/// <param name="pen">The stroke pen.</param> /// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param> /// <param name="geometry">The geometry.</param>
public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry) public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry)
{
DrawGeometry(brush, pen, geometry.PlatformImpl);
}
/// <summary>
/// Draws a geometry.
/// </summary>
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{ {
Contract.Requires<ArgumentNullException>(geometry != null); Contract.Requires<ArgumentNullException>(geometry != null);
if (brush != null || PenIsVisible(pen)) if (brush != null || PenIsVisible(pen))
{ {
PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl); PlatformImpl.DrawGeometry(brush, pen, geometry);
} }
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save