Browse Source

Merge branch 'master' into fixes/hotkey-manager-leak-fix

pull/4823/head
Lighto 6 years ago
committed by GitHub
parent
commit
6a5b4ecce8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Avalonia.sln.DotSettings
  2. 2
      build.ps1
  3. 3
      build/SharedVersion.props
  4. 2
      build/XUnit.props
  5. 5
      nukebuild/BuildTasksPatcher.cs
  6. 1
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  7. 10
      samples/ControlCatalog/App.xaml
  8. 1
      samples/ControlCatalog/MainView.xaml
  9. 42
      samples/ControlCatalog/Pages/LabelsPage.axaml
  10. 43
      samples/ControlCatalog/Pages/LabelsPage.axaml.cs
  11. 16
      samples/ControlCatalog/Pages/RelativePanelPage.axaml
  12. 36
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  13. 5
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  14. 8
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  15. 2
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  16. 7
      src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
  17. 4
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  18. 75
      src/Avalonia.Controls/Label.cs
  19. 25
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  20. 20
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  21. 7
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  22. 12
      src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
  23. 6
      src/Avalonia.Controls/ResolveByNameAttribute.cs
  24. 3
      src/Avalonia.Controls/Selection/SelectionModel.cs
  25. 28
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  26. 23
      src/Avalonia.Controls/TextBlock.cs
  27. 7
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  28. 6
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  29. 8
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  30. 3
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
  31. 6
      src/Avalonia.Layout/Properties/AssemblyInfo.cs
  32. 3
      src/Avalonia.Native/WindowImplBase.cs
  33. 1
      src/Avalonia.ReactiveUI.Events/Avalonia.ReactiveUI.Events.csproj
  34. 2
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  35. 18
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  36. 18
      src/Avalonia.ReactiveUI/ReactiveWindow.cs
  37. 4
      src/Avalonia.Styling/Properties/AssemblyInfo.cs
  38. 3
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  39. 17
      src/Avalonia.Themes.Default/Label.xaml
  40. 3
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  41. 17
      src/Avalonia.Themes.Fluent/Label.xaml
  42. 55
      src/Avalonia.Visuals/Media/ConicGradientBrush.cs
  43. 19
      src/Avalonia.Visuals/Media/IConicGradientBrush.cs
  44. 47
      src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs
  45. 9
      src/Avalonia.Visuals/Properties/AssemblyInfo.cs
  46. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  47. 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  48. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs
  49. 5
      src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs
  50. 4
      src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs
  51. 17
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  52. 6
      src/Skia/Avalonia.Skia/Properties/AssemblyInfo.cs
  53. 6
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  54. 17
      src/Windows/Avalonia.Direct2D1/Media/SolidColorBrushImpl.cs
  55. 6
      src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs
  56. 32
      src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs
  57. 176
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  58. 6
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  59. 10
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  60. 100
      src/Windows/Avalonia.Win32/WindowFramebuffer.cs
  61. 18
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  62. 45
      tests/Avalonia.Controls.UnitTests/Selection/InternalSelectionModelTests.cs
  63. 1
      tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj
  64. 34
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  65. 79
      tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
  66. 87
      tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs
  67. 54
      tests/Avalonia.RenderTests/Controls/TextBlockTests.cs
  68. 178
      tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
  69. 2
      tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj
  70. BIN
      tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png
  71. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png
  72. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png
  73. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png
  74. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png
  75. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png
  76. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png
  77. BIN
      tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png
  78. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png
  79. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png
  80. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png
  81. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png
  82. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png
  83. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png

1
Avalonia.sln.DotSettings

@ -37,4 +37,5 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

2
build.ps1

@ -62,6 +62,8 @@ else {
} else {
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
}
$env:PATH="$DotNetDirectory;$env:PATH"
}
Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"

3
build/SharedVersion.props

@ -16,7 +16,8 @@
<PackageReleaseNotes>https://github.com/AvaloniaUI/Avalonia/releases</PackageReleaseNotes>
<RepositoryType>git</RepositoryType>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\avalonia.snk</AssemblyOriginatorKeyFile>
<SignAssembly>True</SignAssembly>
<SignAssembly>false</SignAssembly>
<DefineConstants Condition="$(SignAssembly) == true">$(DefineConstants);SIGNED_BUILD</DefineConstants>
</PropertyGroup>
<ItemGroup Label="PackageIcon">

2
build/XUnit.props

@ -13,6 +13,6 @@
</ItemGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\avalonia.snk</AssemblyOriginatorKeyFile>
<SignAssembly>True</SignAssembly>
<SignAssembly>False</SignAssembly>
</PropertyGroup>
</Project>

5
nukebuild/BuildTasksPatcher.cs

@ -30,7 +30,10 @@ public class BuildTasksPatcher
{
temp, typeof(Mono.Cecil.AssemblyDefinition).Assembly.GetModules()[0]
.FullyQualifiedName,
typeof(Mono.Cecil.Rocks.MethodBodyRocks).Assembly.GetModules()[0].FullyQualifiedName
typeof(Mono.Cecil.Rocks.MethodBodyRocks).Assembly.GetModules()[0].FullyQualifiedName,
typeof(Mono.Cecil.Pdb.PdbReaderProvider).Assembly.GetModules()[0].FullyQualifiedName,
typeof(Mono.Cecil.Mdb.MdbReaderProvider).Assembly.GetModules()[0].FullyQualifiedName
},
SearchDirectories = new string[0],
OutputFile = output

1
samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net461</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>

10
samples/ControlCatalog/App.xaml

@ -12,6 +12,16 @@
<Style Selector="TextBlock.h3">
<Setter Property="FontSize" Value="12" />
</Style>
<Style Selector="Label.h1">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Medium" />
</Style>
<Style Selector="Label.h2">
<Setter Property="FontSize" Value="14" />
</Style>
<Style Selector="Label.h3">
<Setter Property="FontSize" Value="12" />
</Style>
<StyleInclude Source="/SideBar.xaml"/>
</Application.Styles>
</Application>

1
samples/ControlCatalog/MainView.xaml

@ -44,6 +44,7 @@
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:ItemsRepeaterPage/>
</TabItem>
<TabItem Header="Label"><pages:LabelsPage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="ListBox"
ScrollViewer.VerticalScrollBarVisibility="Disabled">

42
samples/ControlCatalog/Pages/LabelsPage.axaml

@ -0,0 +1,42 @@
<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="400" d:DesignHeight="250"
x:Class="ControlCatalog.Pages.LabelsPage"
x:Name="_labelsPage">
<UserControl.Styles>
<Style Selector="Label">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="6,3,0,3"/>
</Style>
<Style Selector="TextBox">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="0,3,6,3"/>
</Style>
<Style Selector="CheckBox">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="0,3,6,3"/>
</Style>
<Style Selector="Button[IsDefault=true]">
<Setter Property="Background" Value="{DynamicResource HighlightBrush}"/>
</Style>
</UserControl.Styles>
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Hidden">
<Grid HorizontalAlignment="Left" VerticalAlignment="Top" RowDefinitions="Auto,Auto,Auto,Auto,Auto,*" ColumnDefinitions="Auto,6,*" Width="246">
<Label Target="firstNameEdit" Grid.Row="0" Grid.Column="0">_First name</Label>
<TextBox Name="firstNameEdit" Grid.Column="2" Grid.Row="0" Text="{Binding FirstName}"></TextBox>
<Label Target="lastNameEdit" Grid.Row="1" Grid.Column="0">_Last name</Label>
<TextBox Name="lastNameEdit" Grid.Column="2" Grid.Row="1" Text="{Binding LastName}"></TextBox>
<Label Target="bannedCheck" Grid.Row="2" Grid.Column="0">_Banned</Label>
<CheckBox Name="bannedCheck" Grid.Column="2" Grid.Row="2" IsChecked="{Binding IsBanned}"></CheckBox>
<GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.RowSpan="3" >
</GridSplitter>
<StackPanel Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button IsCancel="True" Command="{Binding #_labelsPage.DoCancel}">Cancel</Button>
<Button IsDefault="True" Command="{Binding #_labelsPage.DoSave}">Save</Button>
</StackPanel>
</Grid>
</ScrollViewer>
</UserControl>

43
samples/ControlCatalog/Pages/LabelsPage.axaml.cs

@ -0,0 +1,43 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.Models;
using ReactiveUI;
namespace ControlCatalog.Pages
{
public class LabelsPage : UserControl
{
private Person _person;
public LabelsPage()
{
CreateDefaultPerson();
this.InitializeComponent();
}
private void CreateDefaultPerson()
{
DataContext = _person = new Person
{
FirstName = "John",
LastName = "Doe",
IsBanned = true,
};
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void DoSave()
{
}
public void DoCancel()
{
CreateDefaultPerson();
}
}
}

16
samples/ControlCatalog/Pages/RelativePanelPage.axaml

@ -23,16 +23,16 @@
<Border Name="Rect6" Background="Green" Height="50" Width="50" RelativePanel.AlignBottomWithPanel="True" RelativePanel.AlignRightWithPanel="True">
<TextBlock Text="Rect6" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect7" Background="Blue" Height="50" RelativePanel.RightOf="{Binding ElementName=Rect1}">
<Border Name="Rect7" Background="Blue" Height="50" RelativePanel.RightOf="Rect1">
<TextBlock Text="Rect7 (RightOf Rect1)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect8" Background="Green" Height="50" RelativePanel.Below="{Binding ElementName=Rect7}">
<Border Name="Rect8" Background="Green" Height="50" RelativePanel.Below="Rect7">
<TextBlock Text="Rect8 (Below Rect7)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect9" Background="Blue" Height="140" Width="460" RelativePanel.AlignHorizontalCenterWithPanel="True" RelativePanel.AlignVerticalCenterWithPanel="True">
<TextBlock Text="Rect9" Padding="10" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Top"/>
</Border>
<Border Name="Rect10" Background="Red" Width="50" RelativePanel.RightOf="{Binding ElementName=Rect9}" RelativePanel.AlignVerticalCenterWith="{Binding ElementName=Rect9}">
<Border Name="Rect10" Background="Red" Width="50" RelativePanel.RightOf="Rect9" RelativePanel.AlignVerticalCenterWith="Rect9">
<TextBlock Text="Rect14 (RightOf Rect9, AlignVerticalCenterWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.RenderTransform>
<TransformGroup>
@ -41,19 +41,19 @@
</TextBlock.RenderTransform>
</TextBlock>
</Border>
<Border Name="Rect11" Background="Red" Height="50" RelativePanel.AlignBottomWith="{Binding ElementName=Rect9}" RelativePanel.AlignHorizontalCenterWith="{Binding ElementName=Rect9}">
<Border Name="Rect11" Background="Red" Height="50" RelativePanel.AlignBottomWith="Rect9" RelativePanel.AlignHorizontalCenterWith="Rect9">
<TextBlock Text="Rect11 (AlignBottomWith Rect9, AlignHorizontalCenterWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect12" Background="Red" Height="50" RelativePanel.Below="{Binding ElementName=Rect8}" RelativePanel.AlignLeftWith="{Binding ElementName=Rect7}">
<Border Name="Rect12" Background="Red" Height="50" RelativePanel.Below="Rect8" RelativePanel.AlignLeftWith="Rect7">
<TextBlock Text="Rect12 (Below Rect8, AlignLeftWith Rect7)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect13" Background="Blue" Height="50" RelativePanel.Below="{Binding ElementName=Rect12}" RelativePanel.AlignRightWith="{Binding ElementName=Rect12}">
<Border Name="Rect13" Background="Blue" Height="50" RelativePanel.Below="Rect12" RelativePanel.AlignRightWith="Rect12">
<TextBlock Text="Rect13 (Below Rect12, AlignRightWith Rect12)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect14" Background="Green" Height="50" RelativePanel.Above="{Binding ElementName=Rect9}" RelativePanel.AlignRightWith="{Binding ElementName=Rect9}">
<Border Name="Rect14" Background="Green" Height="50" RelativePanel.Above="Rect9" RelativePanel.AlignRightWith="Rect9">
<TextBlock Text="Rect14 (Above Rect9, AlignRightWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border Name="Rect15" Background="Red" Height="50" RelativePanel.LeftOf="{Binding ElementName=Rect2}" RelativePanel.AlignTopWith="{Binding ElementName=Rect9}">
<Border Name="Rect15" Background="Red" Height="50" RelativePanel.LeftOf="Rect2" RelativePanel.AlignTopWith="Rect9">
<TextBlock Text="Rect15 (LeftOf Rect2, AlignTopWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</RelativePanel>

36
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.TextBoxPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">TextBox</TextBlock>
<TextBlock Classes="h2">A control into which the user can input text</TextBlock>
<Label Classes="h1">TextBox</Label>
<Label Classes="h2">A control into which the user can input text</Label>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
@ -38,24 +38,24 @@
Text="Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox AcceptsReturn="True" Width="200" Height="125"
Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox Classes="clearButton" Text="Clear Content" Width="200" FontWeight="Normal" FontStyle="Normal" Watermark="Watermark" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8">
<TextBlock Classes="h2">resm fonts</TextBlock>
<TextBox Width="200" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-Italic.ttf?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-*.ttf?assembly=ControlCatalog#Source Sans Pro"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8">
<TextBlock Classes="h2">res fonts</TextBlock>
<TextBox Width="200" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8">
<Label Classes="h2" Target="{Binding #firstResMFont}">res_m fonts</Label>
<TextBox Width="200" x:Name="firstResMFont" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-Italic.ttf?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-*.ttf?assembly=ControlCatalog#Source Sans Pro"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8">
<Label Classes="h2" Target="{Binding #firstResFont}">_res fonts</Label>
<TextBox Width="200" x:Name="firstResFont" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

5
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@ -6,5 +6,10 @@ using System.Runtime.CompilerServices;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.LeakTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Animation.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.LeakTests")]
[assembly: InternalsVisibleTo("Avalonia.Animation.UnitTests")]
#endif

8
src/Avalonia.Base/Properties/AssemblyInfo.cs

@ -5,8 +5,16 @@ using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid")]
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests")]
#endif

2
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@ -438,7 +438,7 @@ namespace Avalonia.Controls
_lastMousePositionHeaders = mousePositionHeaders;
if (args.Pointer.Captured != this)
if (args.Pointer.Captured != this && _dragMode == DragMode.Drag)
args.Pointer.Capture(this);
SetDragCursor(mousePosition);

7
src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs

@ -1,10 +1,13 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]
#endif
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Collections")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")]

4
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@ -486,9 +486,9 @@ namespace Avalonia.Controls.Primitives
switch (panelType)
{
case DateTimePickerPanelType.Year:
return new DateTime(value, FormatDate.Month, FormatDate.Day).ToString(ItemFormat);
return new DateTime(value, 1, 1).ToString(ItemFormat);
case DateTimePickerPanelType.Month:
return new DateTime(FormatDate.Year, value, FormatDate.Day).ToString(ItemFormat);
return new DateTime(FormatDate.Year, value, 1).ToString(ItemFormat);
case DateTimePickerPanelType.Day:
return new DateTime(FormatDate.Year, FormatDate.Month, value).ToString(ItemFormat);
case DateTimePickerPanelType.Hour:

75
src/Avalonia.Controls/Label.cs

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Label control. Focuses <see cref="Target"/> on pointer click or access key press (Alt + accessKey)
/// </summary>
public class Label : ContentControl
{
/// <summary>
/// Defines the <see cref="Target"/> Direct property
/// </summary>
public static readonly DirectProperty<Label, IInputElement> TargetProperty =
AvaloniaProperty.RegisterDirect<Label, IInputElement>(nameof(Target), lbl => lbl.Target, (lbl, inp) => lbl.Target = inp);
/// <summary>
/// Label focus target storage field
/// </summary>
private IInputElement _target;
/// <summary>
/// Label focus Target
/// </summary>
[ResolveByName]
public IInputElement Target
{
get => _target;
set => SetAndRaise(TargetProperty, ref _target, value);
}
static Label()
{
AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<Label>((lbl, args) => lbl.LabelActivated(args));
// IsTabStopProperty.OverrideDefaultValue<Label>(false)
FocusableProperty.OverrideDefaultValue<Label>(false);
}
/// <summary>
/// Initializes instance of <see cref="Label"/> control
/// </summary>
public Label()
{
}
/// <summary>
/// Method which focuses <see cref="Target"/> input element
/// </summary>
private void LabelActivated(RoutedEventArgs args)
{
Target?.Focus();
args.Handled = Target != null;
}
/// <summary>
/// Handler of <see cref="IInputElement.PointerPressed"/> event
/// </summary>
/// <param name="e">Event Arguments</param>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonPressed)
{
LabelActivated(e);
}
base.OnPointerPressed(e);
}
}
}

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

@ -85,10 +85,19 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<Thickness> PaddingProperty =
Decorator.PaddingProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="RecognizesAccessKey"/> property
/// </summary>
public static readonly DirectProperty<ContentPresenter, bool> RecognizesAccessKeyProperty =
AvaloniaProperty.RegisterDirect<ContentPresenter, bool>(
nameof(RecognizesAccessKey),
cp => cp.RecognizesAccessKey, (cp, value) => cp.RecognizesAccessKey = value);
private IControl _child;
private bool _createdChild;
private IRecyclingDataTemplate _recyclingDataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
private bool _recognizesAccessKey;
/// <summary>
/// Initializes static members of the <see cref="ContentPresenter"/> class.
@ -208,6 +217,15 @@ namespace Avalonia.Controls.Presenters
set { SetValue(PaddingProperty, value); }
}
/// <summary>
/// Determine if <see cref="ContentPresenter"/> should use <see cref="AccessText"/> in its style
/// </summary>
public bool RecognizesAccessKey
{
get => _recognizesAccessKey;
set => SetAndRaise(RecognizesAccessKeyProperty, ref _recognizesAccessKey, value);
}
/// <summary>
/// Gets the host content control.
/// </summary>
@ -311,7 +329,12 @@ namespace Avalonia.Controls.Presenters
if (content != null && newChild == null)
{
var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ??
(
RecognizesAccessKey
? FuncDataTemplate.Access
: FuncDataTemplate.Default
);
if (dataTemplate is IRecyclingDataTemplate rdt)
{

20
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -4,6 +4,7 @@ using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Avalonia.Layout;
namespace Avalonia.Controls.Presenters
{
@ -312,7 +313,24 @@ namespace Avalonia.Controls.Presenters
context.FillRectangle(background, new Rect(Bounds.Size));
}
context.DrawText(Foreground, new Point(), FormattedText);
double top = 0;
var textSize = FormattedText.Bounds.Size;
if (Bounds.Height < textSize.Height)
{
switch (VerticalAlignment)
{
case VerticalAlignment.Center:
top += (Bounds.Height - textSize.Height) / 2;
break;
case VerticalAlignment.Bottom:
top += (Bounds.Height - textSize.Height);
break;
}
}
context.DrawText(Foreground, new Point(0, top), FormattedText);
}
public override void Render(DrawingContext context)

7
src/Avalonia.Controls/Properties/AssemblyInfo.cs

@ -1,10 +1,13 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]
#endif
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Embedding")]

12
src/Avalonia.Controls/RelativePanel.AttachedProperties.cs

@ -55,6 +55,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to position this element above.)</param>
[ResolveByName]
public static void SetAbove(AvaloniaObject obj, object value)
{
obj.SetValue(AboveProperty, value);
@ -121,6 +122,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's bottom edge with.)</param>
[ResolveByName]
public static void SetAlignBottomWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignBottomWithProperty, value);
@ -185,6 +187,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's horizontal center with.)</param>
[ResolveByName]
public static void SetAlignHorizontalCenterWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignHorizontalCenterWithProperty, value);
@ -250,6 +253,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's left edge with.)</param>
[ResolveByName]
public static void SetAlignLeftWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignLeftWithProperty, value);
@ -315,6 +319,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's right edge with.)</param>
[ResolveByName]
public static void SetAlignRightWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignRightWithProperty, value);
@ -376,6 +381,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's top edge with.)</param>
[ResolveByName]
public static void SetAlignTopWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignTopWithProperty, value);
@ -437,7 +443,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's horizontal center with.)</param>
[ResolveByName]
public static void SetAlignVerticalCenterWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignVerticalCenterWithProperty, value);
@ -468,7 +474,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to position this element below.)</param>
[ResolveByName]
public static void SetBelow(AvaloniaObject obj, object value)
{
obj.SetValue(BelowProperty, value);
@ -500,6 +506,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to position this element to the left of.)</param>
[ResolveByName]
public static void SetLeftOf(AvaloniaObject obj, object value)
{
obj.SetValue(LeftOfProperty, value);
@ -531,6 +538,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to position this element to the right of.)</param>
[ResolveByName]
public static void SetRightOf(AvaloniaObject obj, object value)
{
obj.SetValue(RightOfProperty, value);

6
src/Avalonia.Controls/ResolveByNameAttribute.cs

@ -2,8 +2,12 @@
namespace Avalonia.Controls
{
/// <summary>
/// Indicates that the property resolves an element by Name or x:Name.
/// When applying this to attached properties, ensure to put on both
/// the Getter and Setter methods.
/// </summary>
public class ResolveByNameAttribute : Attribute
{
}
}

3
src/Avalonia.Controls/Selection/SelectionModel.cs

@ -442,7 +442,8 @@ namespace Avalonia.Controls.Selection
RaisePropertyChanged(nameof(SelectedIndex));
}
if (e.Action == NotifyCollectionChangedAction.Remove && e.OldStartingIndex <= oldSelectedIndex)
if ((e.Action == NotifyCollectionChangedAction.Remove && e.OldStartingIndex <= oldSelectedIndex) ||
e.Action == NotifyCollectionChangedAction.Reset)
{
RaisePropertyChanged(nameof(SelectedItem));
}

28
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@ -1,5 +1,6 @@
using System;
using System.Reactive.Linq;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls.Templates
{
@ -30,6 +31,31 @@ namespace Avalonia.Controls.Templates
},
true);
/// <summary>
/// The default data template used in the case where no matching data template is found
/// but <see cref="AccessText"/> should be used.
/// </summary>
public static readonly FuncDataTemplate Access =
new FuncDataTemplate<object>(
(data, s) =>
{
if (data != null)
{
var result = new AccessText();
result.Bind(TextBlock.TextProperty,
result.GetObservable(Control.DataContextProperty).Select(x => x?.ToString()));
return result;
}
else
{
return null;
}
},
true);
/// <summary>
/// The implementation of the <see cref="Match"/> method.
/// </summary>
private readonly Func<object, bool> _match;
private readonly bool _supportsRecycling;
@ -42,7 +68,7 @@ namespace Avalonia.Controls.Templates
/// </param>
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(
Type type,
Type type,
Func<object, INameScope, IControl> build,
bool supportsRecycling = false)
: this(o => IsInstance(o, type), build, supportsRecycling)

23
src/Avalonia.Controls/TextBlock.cs

@ -3,6 +3,7 @@ using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
using Avalonia.Layout;
namespace Avalonia.Controls
{
@ -427,14 +428,32 @@ namespace Avalonia.Controls
case TextAlignment.Center:
offsetX = (width - TextLayout.Size.Width) / 2;
break;
case TextAlignment.Right:
offsetX = width - TextLayout.Size.Width;
offsetX = width - TextLayout.Size.Width;
break;
}
var padding = Padding;
using (context.PushPostTransform(Matrix.CreateTranslation(padding.Left + offsetX, padding.Top)))
var top = padding.Top;
var textSize = TextLayout.Size;
if (Bounds.Height < textSize.Height)
{
switch (VerticalAlignment)
{
case VerticalAlignment.Center:
top += (Bounds.Height - textSize.Height) / 2;
break;
case VerticalAlignment.Bottom:
top += (Bounds.Height - textSize.Height);
break;
}
}
using (context.PushPostTransform(Matrix.CreateTranslation(padding.Left + offsetX, top)))
{
TextLayout.Draw(context);
}

7
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@ -23,6 +23,8 @@ namespace Avalonia.Diagnostics.ViewModels
if (visual is IControl control)
{
ElementName = control.Name;
var removed = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => control.DetachedFromLogicalTree += x,
x => control.DetachedFromLogicalTree -= x);
@ -61,6 +63,11 @@ namespace Avalonia.Diagnostics.ViewModels
private set { RaiseAndSetIfChanged(ref _classes, value); }
}
public string ElementName
{
get;
}
public IVisual Visual
{
get;

6
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -26,6 +26,12 @@
<DataGridTextColumn Header="Type" Binding="{Binding Type}" />
<DataGridTextColumn Header="Priority" Binding="{Binding Priority}" IsReadOnly="True" />
</DataGrid.Columns>
<DataGrid.Styles>
<Style Selector="DataGridRow TextBox">
<Setter Property="SelectionBrush" Value="LightBlue"/>
</Style>
</DataGrid.Styles>
</DataGrid>
</Grid>

8
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -76,7 +76,13 @@ namespace Avalonia.Diagnostics.Views
if (e.Modifiers == modifiers)
{
var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default;
var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible))
var control = Root.GetVisualsAt(point, x =>
{
if (x is AdornerLayer || !x.IsVisible) return false;
if (!(x is IInputElement ie)) return true;
return ie.IsHitTestVisible;
})
.FirstOrDefault();
if (control != null)

3
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml

@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
x:Class="Avalonia.Diagnostics.Views.TreePageView">
<Grid ColumnDefinitions="*,4,3*">
<Grid ColumnDefinitions="0.8*,4,*">
<TreeView Name="tree"
BorderThickness="0"
Items="{Binding Nodes}"
@ -13,6 +13,7 @@
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="{Binding Type}"/>
<TextBlock Text="{Binding Classes}"/>
<TextBlock Foreground="Gray" Text="{Binding ElementName}"/>
</StackPanel>
</TreeDataTemplate>
</TreeView.DataTemplates>

6
src/Avalonia.Layout/Properties/AssemblyInfo.cs

@ -1,7 +1,9 @@
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests")]
#endif
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")]

3
src/Avalonia.Native/WindowImplBase.cs

@ -341,8 +341,7 @@ namespace Avalonia.Native
public void Invalidate(Rect rect)
{
if (!_deferredRendering && _native != null)
_native.Invalidate(new AvnRect { Height = rect.Height, Width = rect.Width, X = rect.X, Y = rect.Y });
_native?.Invalidate(new AvnRect { Height = rect.Height, Width = rect.Width, X = rect.X, Y = rect.Y });
}
public void SetInputRoot(IInputRoot inputRoot)

1
src/Avalonia.ReactiveUI.Events/Avalonia.ReactiveUI.Events.csproj

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Avalonia.ReactiveUI.Events</PackageId>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />

2
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Avalonia.ReactiveUI</PackageId>
<SignAssembly>True</SignAssembly>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />

18
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -1,3 +1,6 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
@ -6,8 +9,10 @@ using ReactiveUI;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ReactiveUI UserControl that implements <see cref="IViewFor{TViewModel}"/>
/// and will activate your ViewModel automatically if it supports activation.
/// A ReactiveUI <see cref="UserControl"/> that implements the <see cref="IViewFor{TViewModel}"/> interface and
/// will activate your ViewModel automatically if the view model implements <see cref="IActivatableViewModel"/>.
/// When the DataContext property changes, this class will update the ViewModel property with the new DataContext
/// value, and vice versa.
/// </summary>
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
public class ReactiveUserControl<TViewModel> : UserControl, IViewFor<TViewModel> where TViewModel : class
@ -20,7 +25,14 @@ namespace Avalonia.ReactiveUI
/// </summary>
public ReactiveUserControl()
{
DataContextChanged += (sender, args) => ViewModel = DataContext as TViewModel;
// This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel.
this.WhenActivated(disposables => { });
this.ObservableForProperty(x => x.ViewModel, false, true)
.Subscribe(args => DataContext = args.Value);
this.ObservableForProperty(x => x.DataContext, false, true)
.Subscribe(args => ViewModel = args.Value as TViewModel);
}
/// <summary>

18
src/Avalonia.ReactiveUI/ReactiveWindow.cs

@ -1,3 +1,6 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
@ -6,8 +9,10 @@ using ReactiveUI;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ReactiveUI Window that implements <see cref="IViewFor{TViewModel}"/>
/// and will activate your ViewModel automatically if it supports activation.
/// A ReactiveUI <see cref="Window"/> that implements the <see cref="IViewFor{TViewModel}"/> interface and will
/// activate your ViewModel automatically if the view model implements <see cref="IActivatableViewModel"/>. When
/// the DataContext property changes, this class will update the ViewModel property with the new DataContext value,
/// and vice versa.
/// </summary>
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
public class ReactiveWindow<TViewModel> : Window, IViewFor<TViewModel> where TViewModel : class
@ -20,7 +25,14 @@ namespace Avalonia.ReactiveUI
/// </summary>
public ReactiveWindow()
{
DataContextChanged += (sender, args) => ViewModel = DataContext as TViewModel;
// This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel.
this.WhenActivated(disposables => { });
this.ObservableForProperty(x => x.ViewModel, false, true)
.Subscribe(args => DataContext = args.Value);
this.ObservableForProperty(x => x.DataContext, false, true)
.Subscribe(args => ViewModel = args.Value as TViewModel);
}
/// <summary>

4
src/Avalonia.Styling/Properties/AssemblyInfo.cs

@ -5,4 +5,8 @@ using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.LogicalTree")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Styling")]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Styling.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Styling.UnitTests")]
#endif

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

@ -12,7 +12,8 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.CaptionButtons.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ComboBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ComboBoxItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ContentControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ContentControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Label.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.GridSplitter.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ItemsControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ListBox.xaml?assembly=Avalonia.Themes.Default"/>

17
src/Avalonia.Themes.Default/Label.xaml

@ -0,0 +1,17 @@
<Style xmlns="https://github.com/avaloniaui" Selector="Label">
<Setter Property="Padding" Value="3"/>
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</ControlTemplate>
</Setter>
</Style>

3
src/Avalonia.Themes.Fluent/FluentTheme.xaml

@ -11,7 +11,8 @@
<StyleInclude Source="avares://Avalonia.Themes.Fluent/CheckBox.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/ComboBox.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/ComboBoxItem.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/ContentControl.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/ContentControl.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Label.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/GridSplitter.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/ItemsControl.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Fluent/ListBox.xaml"/>

17
src/Avalonia.Themes.Fluent/Label.xaml

@ -0,0 +1,17 @@
<Style xmlns="https://github.com/avaloniaui" Selector="Label">
<Setter Property="Padding" Value="3"/>
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</ControlTemplate>
</Setter>
</Style>

55
src/Avalonia.Visuals/Media/ConicGradientBrush.cs

@ -0,0 +1,55 @@
using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// Paints an area with a swept circular gradient.
/// </summary>
public sealed class ConicGradientBrush : GradientBrush, IConicGradientBrush
{
/// <summary>
/// Defines the <see cref="Center"/> property.
/// </summary>
public static readonly StyledProperty<RelativePoint> CenterProperty =
AvaloniaProperty.Register<RadialGradientBrush, RelativePoint>(
nameof(Center),
RelativePoint.Center);
/// <summary>
/// Defines the <see cref="Angle"/> property.
/// </summary>
public static readonly StyledProperty<double> AngleProperty =
AvaloniaProperty.Register<RadialGradientBrush, double>(
nameof(Angle),
0);
static ConicGradientBrush()
{
AffectsRender<ConicGradientBrush>(CenterProperty, AngleProperty);
}
/// <summary>
/// Gets or sets the center point of the gradient.
/// </summary>
public RelativePoint Center
{
get { return GetValue(CenterProperty); }
set { SetValue(CenterProperty, value); }
}
/// <summary>
/// Gets or sets the angle of the start and end of the sweep, measured from above the center point.
/// </summary>
public double Angle
{
get { return GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
/// <inheritdoc/>
public override IBrush ToImmutable()
{
return new ImmutableConicGradientBrush(this);
}
}
}

19
src/Avalonia.Visuals/Media/IConicGradientBrush.cs

@ -0,0 +1,19 @@
namespace Avalonia.Media
{
/// <summary>
/// Paints an area with a conic gradient.
/// </summary>
public interface IConicGradientBrush : IGradientBrush
{
/// <summary>
/// Gets the center point for the gradient.
/// </summary>
RelativePoint Center { get; }
/// <summary>
/// Gets the starting angle for the gradient in degrees, measured from
/// the point above the center point.
/// </summary>
double Angle { get; }
}
}

47
src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs

@ -0,0 +1,47 @@
using System.Collections.Generic;
namespace Avalonia.Media.Immutable
{
/// <summary>
/// A brush that draws with a sweep gradient.
/// </summary>
public class ImmutableConicGradientBrush : ImmutableGradientBrush, IConicGradientBrush
{
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableConicGradientBrush"/> class.
/// </summary>
/// <param name="gradientStops">The gradient stops.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
/// <param name="center">The center point for the gradient.</param>
/// <param name="angle">The starting angle for the gradient.</param>
public ImmutableConicGradientBrush(
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity = 1,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? center = null,
double angle = 0)
: base(gradientStops, opacity, spreadMethod)
{
Center = center ?? RelativePoint.Center;
Angle = angle;
}
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableConicGradientBrush"/> class.
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
public ImmutableConicGradientBrush(ConicGradientBrush source)
: base(source)
{
Center = source.Center;
Angle = source.Angle;
}
/// <inheritdoc/>
public RelativePoint Center { get; }
/// <inheritdoc/>
public double Angle { get; }
}
}

9
src/Avalonia.Visuals/Properties/AssemblyInfo.cs

@ -2,13 +2,20 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media.Imaging")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media.Transformation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests")]
#endif

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit 7d15a9227461cf4d62bc2ed251fcc25c96b28aaf
Subproject commit 5420df861ce6f2be5ead9efa078fe7242ce88f18

2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLX_INTERNAL</DefineConstants>
<DefineConstants>$(DefineConstants);PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLX_INTERNAL</DefineConstants>
<UseCecil>false</UseCecil>
<DefineConstants Condition="$(UseCecil) == true">$(DefineConstants);RUNTIME_XAML_CECIL</DefineConstants>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs

@ -30,7 +30,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
if (provideValueTarget.TargetProperty is IPropertyInfo property)
value.OnCompleted(() => property.Set(target, value.GetResult()));
return null;
return AvaloniaProperty.UnsetValue;
}
}
}

5
src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs

@ -5,5 +5,8 @@ using System.Runtime.CompilerServices;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.MarkupExtensions")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Styling")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Templates")]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests")]
#endif

4
src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs

@ -4,4 +4,8 @@ using System.Runtime.CompilerServices;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Data")]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests")]
#endif

17
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -641,6 +641,23 @@ namespace Avalonia.Skia
}
}
break;
}
case IConicGradientBrush conicGradient:
{
var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint();
// Skia's default is that angle 0 is from the right hand side of the center point
// but we are matching CSS where the vertical point above the center is 0.
var angle = (float)(conicGradient.Angle - 90);
var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y);
using (var shader =
SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation))
{
paintWrapper.Paint.Shader = shader;
}
break;
}
}

6
src/Skia/Avalonia.Skia/Properties/AssemblyInfo.cs

@ -1,4 +1,8 @@
using System.Runtime.CompilerServices;
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests")]
#endif

6
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -423,6 +423,7 @@ namespace Avalonia.Direct2D1.Media
var solidColorBrush = brush as ISolidColorBrush;
var linearGradientBrush = brush as ILinearGradientBrush;
var radialGradientBrush = brush as IRadialGradientBrush;
var conicGradientBrush = brush as IConicGradientBrush;
var imageBrush = brush as IImageBrush;
var visualBrush = brush as IVisualBrush;
@ -438,6 +439,11 @@ namespace Avalonia.Direct2D1.Media
{
return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize);
}
else if (conicGradientBrush != null)
{
// there is no Direct2D implementation of Conic Gradients so use Radial as a stand-in
return new SolidColorBrushImpl(conicGradientBrush, _deviceContext);
}
else if (imageBrush?.Source != null)
{
return new ImageBrushImpl(

17
src/Windows/Avalonia.Direct2D1/Media/SolidColorBrushImpl.cs

@ -16,5 +16,22 @@ namespace Avalonia.Direct2D1.Media
}
);
}
/// <summary>
/// Direct2D has no ConicGradient implementation so fall back to a solid colour brush based on
/// the first gradient stop.
/// </summary>
public SolidColorBrushImpl(IConicGradientBrush brush, SharpDX.Direct2D1.DeviceContext target)
{
PlatformBrush = new SharpDX.Direct2D1.SolidColorBrush(
target,
brush?.GradientStops[0].Color.ToDirect2D() ?? new SharpDX.Mathematics.Interop.RawColor4(),
new SharpDX.Direct2D1.BrushProperties
{
Opacity = brush != null ? (float)brush.Opacity : 1.0f,
Transform = target.Transform
}
);
}
}
}

6
src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs

@ -6,6 +6,10 @@ using Avalonia.Direct2D1;
[assembly: ExportRenderingSubsystem(OperatingSystemType.WinNT, 1, "Direct2D1", typeof(Direct2D1Platform), nameof(Direct2D1Platform.Initialize),
typeof(Direct2DChecker))]
#if SIGNED_BUILD
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
#else
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.UnitTests")]
#endif

32
src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Logging;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
@ -43,6 +44,37 @@ namespace Avalonia.Win32
[DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options, out IntPtr dispatcherQueueController);
public static CompositionConnector TryCreate(EglPlatformOpenGlInterface egl)
{
const int majorRequired = 10;
const int buildRequired = 16299;
var majorInstalled = Win32Platform.WindowsVersion.Major;
var buildInstalled = Win32Platform.WindowsVersion.Build;
if (majorInstalled >= majorRequired &&
buildInstalled >= buildRequired)
{
try
{
return new CompositionConnector(egl);
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "WinUIComposition")?.Log(null, "Unable to initialize WinUI compositor: {0}", e);
return null;
}
}
var osVersionNotice = $"Windows {majorRequired} Build {buildRequired} is required. Your machine has Windows {majorInstalled} Build {buildInstalled} installed.";
Logger.TryGet(LogEventLevel.Warning, "WinUIComposition")?.Log(null,
$"Unable to initialize WinUI compositor: {osVersionNotice}");
return null;
}
public CompositionConnector(EglPlatformOpenGlInterface egl)
{
EnsureDispatcherQueue();

176
src/Windows/Avalonia.Win32/FramebufferManager.cs

@ -1,41 +1,191 @@
using System;
using System.Threading;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
#nullable enable
namespace Avalonia.Win32
{
class FramebufferManager : IFramebufferPlatformSurface, IDisposable
internal class FramebufferManager : IFramebufferPlatformSurface, IDisposable
{
private const int _bytesPerPixel = 4;
private const PixelFormat _format = PixelFormat.Bgra8888;
private readonly IntPtr _hwnd;
private WindowFramebuffer _fb;
private readonly object _lock;
private readonly Action _onDisposeAction;
private FramebufferData? _framebufferData;
public FramebufferManager(IntPtr hwnd)
{
_hwnd = hwnd;
_lock = new object();
_onDisposeAction = DrawAndUnlock;
}
public ILockedFramebuffer Lock()
{
UnmanagedMethods.GetClientRect(_hwnd, out var rc);
Monitor.Enter(_lock);
var width = Math.Max(1, rc.right - rc.left);
var height = Math.Max(1, rc.bottom - rc.top);
LockedFramebuffer? fb = null;
if ((_fb == null || _fb.Size.Width != width || _fb.Size.Height != height))
try
{
_fb?.Deallocate();
_fb = null;
_fb = new WindowFramebuffer(_hwnd, new PixelSize(width, height));
}
UnmanagedMethods.GetClientRect(_hwnd, out var rc);
var width = Math.Max(1, rc.right - rc.left);
var height = Math.Max(1, rc.bottom - rc.top);
if (_framebufferData is null || _framebufferData?.Size.Width != width || _framebufferData?.Size.Height != height)
{
_framebufferData?.Dispose();
_framebufferData = AllocateFramebufferData(width, height);
}
var framebufferData = _framebufferData.Value;
return _fb;
return fb = new LockedFramebuffer(
framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes,
GetCurrentDpi(), _format, _onDisposeAction);
}
finally
{
// We free the lock when for whatever reason framebuffer was not created.
// This allows for a potential retry later.
if (fb is null)
{
Monitor.Exit(_lock);
}
}
}
public void Dispose()
{
_fb?.Deallocate();
_fb = null;
lock (_lock)
{
_framebufferData?.Dispose();
_framebufferData = null;
}
}
private void DrawAndUnlock()
{
try
{
if (_framebufferData.HasValue)
DrawToWindow(_hwnd, _framebufferData.Value);
}
finally
{
Monitor.Exit(_lock);
}
}
private Vector GetCurrentDpi()
{
if (UnmanagedMethods.ShCoreAvailable)
{
var monitor =
UnmanagedMethods.MonitorFromWindow(_hwnd, UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST);
if (UnmanagedMethods.GetDpiForMonitor(
monitor,
UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI,
out var dpix,
out var dpiy) == 0)
{
return new Vector(dpix, dpiy);
}
}
return new Vector(96, 96);
}
private static FramebufferData AllocateFramebufferData(int width, int height)
{
var bitmapBlob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(width * height * _bytesPerPixel);
return new FramebufferData(bitmapBlob, width, height);
}
private static void DrawToDevice(FramebufferData framebufferData, IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0,
int srcY = 0, int width = -1,
int height = -1)
{
if (width == -1)
width = framebufferData.Size.Width;
if (height == -1)
height = framebufferData.Size.Height;
var bmpInfo = framebufferData.Header;
UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint)width, (uint)height, srcX, srcY,
0, (uint)framebufferData.Size.Height, framebufferData.Data.Address, ref bmpInfo, 0);
}
private static bool DrawToWindow(IntPtr hWnd, FramebufferData framebufferData, int destX = 0, int destY = 0, int srcX = 0,
int srcY = 0, int width = -1,
int height = -1)
{
if (framebufferData.Data.IsDisposed)
throw new ObjectDisposedException("Framebuffer");
if (hWnd == IntPtr.Zero)
return false;
var hDC = UnmanagedMethods.GetDC(hWnd);
if (hDC == IntPtr.Zero)
return false;
try
{
DrawToDevice(framebufferData, hDC, destX, destY, srcX, srcY, width, height);
}
finally
{
UnmanagedMethods.ReleaseDC(hWnd, hDC);
}
return true;
}
private readonly struct FramebufferData
{
public IUnmanagedBlob Data { get; }
public PixelSize Size { get; }
public int RowBytes => Size.Width * _bytesPerPixel;
public UnmanagedMethods.BITMAPINFOHEADER Header { get; }
public FramebufferData(IUnmanagedBlob data, int width, int height)
{
Data = data;
Size = new PixelSize(width, height);
var header = new UnmanagedMethods.BITMAPINFOHEADER();
header.Init();
header.biPlanes = 1;
header.biBitCount = _bytesPerPixel * 8;
header.Init();
header.biWidth = width;
header.biHeight = -height;
Header = header;
}
public void Dispose()
{
Data.Dispose();
}
}
}
}

6
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1017,6 +1017,9 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool ValidateRect(IntPtr hWnd, IntPtr lpRect);
[DllImport("user32.dll")]
public static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool IsWindowEnabled(IntPtr hWnd);
@ -1050,6 +1053,9 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetActiveWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetActiveWindow(IntPtr hWnd);

10
src/Windows/Avalonia.Win32/Win32GlManager.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
@ -23,11 +24,12 @@ namespace Avalonia.Win32
var egl = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay());
if (egl is { } &&
opts?.UseWindowsUIComposition == true &&
Win32Platform.WindowsVersion.Major >= 10 &&
Win32Platform.WindowsVersion.Build >= 16299)
opts?.UseWindowsUIComposition == true)
{
AvaloniaLocator.CurrentMutable.BindToSelf(new CompositionConnector(egl));
var compositionConnector = CompositionConnector.TryCreate(egl);
if (compositionConnector != null)
AvaloniaLocator.CurrentMutable.BindToSelf(compositionConnector);
}
return egl;

100
src/Windows/Avalonia.Win32/WindowFramebuffer.cs

@ -1,100 +0,0 @@
using System;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using PixelFormat = Avalonia.Platform.PixelFormat;
namespace Avalonia.Win32
{
public class WindowFramebuffer : ILockedFramebuffer
{
private readonly IntPtr _handle;
private IUnmanagedBlob _bitmapBlob;
private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo;
public WindowFramebuffer(IntPtr handle, PixelSize size)
{
if (size.Width <= 0)
throw new ArgumentException("Width is less than zero");
if (size.Height <= 0)
throw new ArgumentException("Height is less than zero");
_handle = handle;
_bmpInfo.Init();
_bmpInfo.biPlanes = 1;
_bmpInfo.biBitCount = 32;
_bmpInfo.Init();
_bmpInfo.biWidth = size.Width;
_bmpInfo.biHeight = -size.Height;
_bitmapBlob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size.Width * size.Height * 4);
}
~WindowFramebuffer()
{
Deallocate();
}
public IntPtr Address => _bitmapBlob.Address;
public int RowBytes => Size.Width * 4;
public PixelFormat Format => PixelFormat.Bgra8888;
public Vector Dpi
{
get
{
if (UnmanagedMethods.ShCoreAvailable)
{
uint dpix, dpiy;
var monitor = UnmanagedMethods.MonitorFromWindow(_handle,
UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST);
if (UnmanagedMethods.GetDpiForMonitor(
monitor,
UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI,
out dpix,
out dpiy) == 0)
{
return new Vector(dpix, dpiy);
}
}
return new Vector(96, 96);
}
}
public PixelSize Size => new PixelSize(_bmpInfo.biWidth, -_bmpInfo.biHeight);
public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1,
int height = -1)
{
if (width == -1)
width = Size.Width;
if (height == -1)
height = Size.Height;
UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY,
0, (uint)Size.Height, _bitmapBlob.Address, ref _bmpInfo, 0);
}
public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1,
int height = -1)
{
if (_bitmapBlob.IsDisposed)
throw new ObjectDisposedException("Framebuffer");
if (hWnd == IntPtr.Zero)
return false;
IntPtr hDC = UnmanagedMethods.GetDC(hWnd);
if (hDC == IntPtr.Zero)
return false;
DrawToDevice(hDC, destX, destY, srcX, srcY, width, height);
UnmanagedMethods.ReleaseDC(hWnd, hDC);
return true;
}
public void Dispose()
{
//It's not an *actual* dispose. This call means "We are done drawing"
DrawToWindow(_handle);
}
public void Deallocate() => _bitmapBlob.Dispose();
}
}

18
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -65,6 +65,24 @@ namespace Avalonia.Win32
return IntPtr.Zero;
}
// Based on https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Window.cs#L4270-L4337
// We need to enable parent window before destroying child window to prevent OS from activating a random window behind us.
// This is described here: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks
// Our window closed callback will set enabled state to a correct value after child window gets destroyed.
// We need to verify if parent is still alive (perhaps it got destroyed somehow).
if (_parent != null && IsWindow(_parent._hwnd))
{
var wasActive = GetActiveWindow() == _hwnd;
_parent.SetEnabled(true);
// We also need to activate our parent window since again OS might try to activate a window behind if it is not set.
if (wasActive)
{
SetActiveWindow(_parent._hwnd);
}
}
break;
}

45
tests/Avalonia.Controls.UnitTests/Selection/InternalSelectionModelTests.cs

@ -205,6 +205,49 @@ namespace Avalonia.Controls.UnitTests.Selection
Assert.Equal(2, target.SelectedIndex);
}
[Fact]
public void Raises_Selection_Changed_On_Items_Reset()
{
var items = new ResettingCollection(new[] { "foo", "bar", "baz" });
var target = CreateTarget(source: items);
target.SelectedIndex = 1;
var changed = new List<string>();
target.PropertyChanged += (s, e) => changed.Add(e.PropertyName);
var oldSelectedIndex = target.SelectedIndex;
var oldSelectedItem = target.SelectedItem;
items.Reset(new string[0]);
Assert.NotEqual(oldSelectedIndex, target.SelectedIndex);
Assert.NotEqual(oldSelectedItem, target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex);
Assert.Equal(null, target.SelectedItem);
Assert.Contains(nameof(target.SelectedIndex), changed);
Assert.Contains(nameof(target.SelectedItem), changed);
}
[Fact]
public void Preserves_SelectedItem_On_Items_Reset()
{
var items = new ResettingCollection(new[] { "foo", "bar", "baz" });
var target = CreateTarget(source: items);
target.SelectedItem = "foo";
Assert.Equal(0, target.SelectedIndex);
items.Reset(new string[] { "baz", "foo", "bar" });
Assert.Equal("foo", target.SelectedItem);
Assert.Equal(1, target.SelectedIndex);
}
[Fact]
public void Preserves_Selection_On_Source_Changed()
{
@ -222,7 +265,7 @@ namespace Avalonia.Controls.UnitTests.Selection
bool nullSource = false)
{
source ??= !nullSource ? new[] { "foo", "bar", "baz" } : null;
var result = new InternalSelectionModel
{
SingleSelect = singleSelect,

1
tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

34
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@ -74,44 +74,26 @@ namespace Avalonia.ReactiveUI.UnitTests
{
public ActivatableWindow()
{
InitializeComponent();
Assert.IsType<Border>(Content);
Content = new Border();
this.WhenActivated(disposables => { });
}
private void InitializeComponent()
{
AvaloniaRuntimeXamlLoader.Load(@"
<Window xmlns='https://github.com/avaloniaui'>
<Border/>
</Window>", null, this);
}
}
public class ActivatableUserControl : ReactiveUserControl<ActivatableViewModel>
{
public ActivatableUserControl()
{
InitializeComponent();
Assert.IsType<Border>(Content);
Content = new Border();
this.WhenActivated(disposables => { });
}
private void InitializeComponent()
{
AvaloniaRuntimeXamlLoader.Load(@"
<UserControl xmlns='https://github.com/avaloniaui'>
<Border/>
</UserControl>", null, this);
}
}
public AvaloniaActivationForViewFetcherTest()
{
Locator.CurrentMutable.RegisterConstant(
new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
}
public AvaloniaActivationForViewFetcherTest() =>
Locator
.CurrentMutable
.RegisterConstant(
new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
[Fact]
public void Visual_Element_Is_Activated_And_Deactivated()

79
tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs

@ -1,4 +1,4 @@
using Avalonia.Controls;
using System.Reactive.Disposables;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
@ -8,14 +8,37 @@ namespace Avalonia.ReactiveUI.UnitTests
{
public class ReactiveUserControlTest
{
public class ExampleViewModel : ReactiveObject { }
public class ExampleViewModel : ReactiveObject, IActivatableViewModel
{
public bool IsActive { get; private set; }
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public ExampleViewModel() => this.WhenActivated(disposables =>
{
IsActive = true;
Disposable
.Create(() => IsActive = false)
.DisposeWith(disposables);
});
}
public class ExampleView : ReactiveUserControl<ExampleViewModel> { }
public ReactiveUserControlTest() =>
Locator
.CurrentMutable
.RegisterConstant(
new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
[Fact]
public void Data_Context_Should_Stay_In_Sync_With_Reactive_User_Control_View_Model()
{
var root = new TestRoot();
var view = new ExampleView();
root.Child = view;
var viewModel = new ExampleViewModel();
Assert.Null(view.ViewModel);
@ -26,6 +49,56 @@ namespace Avalonia.ReactiveUI.UnitTests
view.DataContext = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
view.ViewModel = viewModel;
Assert.Equal(viewModel, view.ViewModel);
Assert.Equal(viewModel, view.DataContext);
view.ViewModel = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
}
[Fact]
public void Should_Start_With_NotNull_Activated_ViewModel()
{
var root = new TestRoot();
var view = new ExampleView {ViewModel = new ExampleViewModel()};
Assert.False(view.ViewModel.IsActive);
root.Child = view;
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
root.Child = null;
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);
}
[Fact]
public void Should_Start_With_NotNull_Activated_DataContext()
{
var root = new TestRoot();
var view = new ExampleView {DataContext = new ExampleViewModel()};
Assert.False(view.ViewModel.IsActive);
root.Child = view;
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
root.Child = null;
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);
}
}
}
}

87
tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs

@ -1,4 +1,4 @@
using Avalonia.Controls;
using System.Reactive.Disposables;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
@ -8,10 +8,30 @@ namespace Avalonia.ReactiveUI.UnitTests
{
public class ReactiveWindowTest
{
public class ExampleViewModel : ReactiveObject { }
public class ExampleViewModel : ReactiveObject, IActivatableViewModel
{
public bool IsActive { get; private set; }
public ViewModelActivator Activator { get; } = new ViewModelActivator();
public ExampleViewModel() => this.WhenActivated(disposables =>
{
IsActive = true;
Disposable
.Create(() => IsActive = false)
.DisposeWith(disposables);
});
}
public class ExampleWindow : ReactiveWindow<ExampleViewModel> { }
public ReactiveWindowTest() =>
Locator
.CurrentMutable
.RegisterConstant(
new AvaloniaActivationForViewFetcher(),
typeof(IActivationForViewFetcher));
[Fact]
public void Data_Context_Should_Stay_In_Sync_With_Reactive_Window_View_Model()
{
@ -19,16 +39,73 @@ namespace Avalonia.ReactiveUI.UnitTests
{
var view = new ExampleWindow();
var viewModel = new ExampleViewModel();
view.Show();
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
view.DataContext = viewModel;
Assert.Equal(view.ViewModel, viewModel);
Assert.Equal(view.DataContext, viewModel);
Assert.Equal(viewModel, view.ViewModel);
Assert.Equal(viewModel, view.DataContext);
view.DataContext = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
view.ViewModel = viewModel;
Assert.Equal(viewModel, view.ViewModel);
Assert.Equal(viewModel, view.DataContext);
view.ViewModel = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
}
}
[Fact]
public void Should_Start_With_NotNull_Activated_ViewModel()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var view = new ExampleWindow { ViewModel = new ExampleViewModel() };
Assert.False(view.ViewModel.IsActive);
view.Show();
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
view.Close();
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);
}
}
[Fact]
public void Should_Start_With_NotNull_Activated_DataContext()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var view = new ExampleWindow { DataContext = new ExampleViewModel() };
Assert.False(view.ViewModel.IsActive);
view.Show();
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.True(view.ViewModel.IsActive);
view.Close();
Assert.NotNull(view.ViewModel);
Assert.NotNull(view.DataContext);
Assert.False(view.ViewModel.IsActive);
}
}
}
}
}

54
tests/Avalonia.RenderTests/Controls/TextBlockTests.cs

@ -40,5 +40,59 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
await RenderToFile(target);
CompareImages();
}
[Win32Fact("Has text")]
public async Task RestrictedHeight_VerticalAlign()
{
IControl text(VerticalAlignment verticalAlingnment, bool clip = true, bool restrictHeight = true)
{
return new Border()
{
BorderBrush = Brushes.Blue,
BorderThickness = new Thickness(1),
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center,
Height = restrictHeight ? 20 : double.NaN,
Margin = new Thickness(1),
Child = new TextBlock
{
FontFamily = new FontFamily("Courier New"),
Background = Brushes.Red,
FontSize = 24,
Foreground = Brushes.Black,
Text = "L",
VerticalAlignment = verticalAlingnment,
ClipToBounds = clip
}
};
}
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 180,
Height = 80,
Child = new StackPanel()
{
Orientation = Orientation.Horizontal,
Children =
{
text(VerticalAlignment.Stretch, restrictHeight: false),
text(VerticalAlignment.Center),
text(VerticalAlignment.Stretch),
text(VerticalAlignment.Top),
text(VerticalAlignment.Bottom),
text(VerticalAlignment.Center, clip:false),
text(VerticalAlignment.Stretch, clip:false),
text(VerticalAlignment.Top, clip:false),
text(VerticalAlignment.Bottom, clip:false),
}
}
};
await RenderToFile(target);
CompareImages();
}
}
}

178
tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs

@ -0,0 +1,178 @@
using Avalonia.Controls;
using Avalonia.Media;
using System.Threading.Tasks;
using Xunit;
#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
#else
namespace Avalonia.Direct2D1.RenderTests.Media
#endif
{
public class ConicGradientBrushTests : TestBase
{
public ConicGradientBrushTests() : base(@"Media\ConicGradientBrush")
{
}
[Fact]
public async Task ConicGradientBrush_RedBlue()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new ConicGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
}
}
}
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task ConicGradientBrush_RedBlue_Rotation()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new ConicGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
},
Angle = 90
}
}
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task ConicGradientBrush_RedBlue_Center()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new ConicGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
},
Center = new RelativePoint(0.25, 0.25, RelativeUnit.Relative)
}
}
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task ConicGradientBrush_RedBlue_Center_and_Rotation()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new ConicGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
},
Center = new RelativePoint(0.25, 0.25, RelativeUnit.Relative),
Angle = 90
}
}
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task ConicGradientBrush_RedBlue_SoftEdge()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new ConicGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 0.5 },
new GradientStop { Color = Colors.Red, Offset = 1 },
}
}
}
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task ConicGradientBrush_Umbrella()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new ConicGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Yellow, Offset = 0.1667 },
new GradientStop { Color = Colors.Lime, Offset = 0.3333 },
new GradientStop { Color = Colors.Aqua, Offset = 0.5000 },
new GradientStop { Color = Colors.Blue, Offset = 0.6667 },
new GradientStop { Color = Colors.Magenta, Offset = 0.8333 },
new GradientStop { Color = Colors.Red, Offset = 1 },
}
}
}
};
await RenderToFile(target);
CompareImages();
}
}
}

2
tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj

@ -7,7 +7,7 @@
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<AssemblyOriginatorKeyFile>..\..\build\avalonia.snk</AssemblyOriginatorKeyFile>
<SignAssembly>True</SignAssembly>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="..\Avalonia.UnitTests\Assets\*.ttf" />

BIN
tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

BIN
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

BIN
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

BIN
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

BIN
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

BIN
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

BIN
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

BIN
tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

BIN
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Loading…
Cancel
Save