Browse Source

Merge branch 'master' into fixes/custom-chrome-canresize-maximize

pull/10851/head
Max Katz 3 years ago
committed by GitHub
parent
commit
449062220e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/BindingDemo/MainWindow.xaml
  2. 2
      samples/ControlCatalog.Android/MainActivity.cs
  3. 4
      samples/ControlCatalog.Android/Resources/values-night/colors.xml
  4. 3
      samples/ControlCatalog/MainView.xaml
  5. 222
      samples/ControlCatalog/Pages/ScrollSnapPage.xaml
  6. 68
      samples/ControlCatalog/Pages/ScrollSnapPage.xaml.cs
  7. 290
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml
  8. 37
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs
  9. 2
      samples/ControlCatalog/Pages/TabControlPage.xaml
  10. 8
      samples/VirtualizationDemo/MainWindow.xaml
  11. 4
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  12. 43
      src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
  13. 3
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  14. 41
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  15. 6
      src/Avalonia.Base/AvaloniaProperty.cs
  16. 2
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  17. 5
      src/Avalonia.Base/DirectPropertyBase.cs
  18. 24
      src/Avalonia.Base/Layout/LayoutManager.cs
  19. 18
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  20. 2
      src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs
  21. 49
      src/Avalonia.Base/PropertyStore/EffectiveValue.cs
  22. 42
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  23. 52
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  24. 85
      src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
  25. 73
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  26. 5
      src/Avalonia.Base/StyledProperty.cs
  27. 0
      src/Avalonia.Controls.ColorPicker/AlphaComponentPosition.cs
  28. 2
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
  29. 6
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs
  30. 40
      src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs
  31. 2
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  32. 1
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
  33. 2
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  34. 2
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
  35. 1
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
  36. 2
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  37. 11
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  38. 2
      src/Avalonia.Controls/Flyouts/MenuFlyout.cs
  39. 14
      src/Avalonia.Controls/ItemCollection.cs
  40. 91
      src/Avalonia.Controls/ItemsControl.cs
  41. 8
      src/Avalonia.Controls/Platform/IInsetsManager.cs
  42. 2
      src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
  43. 1
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  44. 15
      src/Avalonia.Controls/SelectableTextBlock.cs
  45. 2
      src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs
  46. 2
      src/Avalonia.Controls/SystemDialog.cs
  47. 46
      src/Avalonia.Controls/TopLevel.cs
  48. 6
      src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.cs
  49. 8
      src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs
  50. 4
      src/Avalonia.Fonts.Inter/AppBuilderExtension.cs
  51. 2
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  52. 32
      src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs
  53. 1
      src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
  54. 1
      src/Avalonia.Themes.Fluent/Controls/Window.xaml
  55. 3
      src/Browser/Avalonia.Browser/BrowserInsetsManager.cs
  56. 43
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  57. 3
      src/iOS/Avalonia.iOS/InsetsManager.cs
  58. 162
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
  59. 5
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  60. 18
      tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs
  61. 2
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  62. 1
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  63. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs
  64. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs
  65. 27
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

4
samples/BindingDemo/MainWindow.xaml

@ -75,11 +75,11 @@
</StackPanel.DataTemplates>
<StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Multiple"/>
<ListBox Items="{Binding Items}" SelectionMode="Multiple" Selection="{Binding Selection}"/>
<ListBox ItemsSource="{Binding Items}" SelectionMode="Multiple" Selection="{Binding Selection}"/>
</StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Multiple"/>
<ListBox Items="{Binding Items}" SelectionMode="Multiple" Selection="{Binding Selection}"/>
<ListBox ItemsSource="{Binding Items}" SelectionMode="Multiple" Selection="{Binding Selection}"/>
</StackPanel>
<ContentControl Content="{ReflectionBinding Selection.SelectedItems[0]}">
<ContentControl.DataTemplates>

2
samples/ControlCatalog.Android/MainActivity.cs

@ -5,7 +5,7 @@ using Avalonia.Android;
namespace ControlCatalog.Android
{
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.Main", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.Main", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity
{
}

4
samples/ControlCatalog.Android/Resources/values-night/colors.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_background">#212121</color>
</resources>

3
samples/ControlCatalog/MainView.xaml

@ -147,9 +147,6 @@
<TabItem Header="ScrollViewer">
<pages:ScrollViewerPage />
</TabItem>
<TabItem Header="ScrollViewer Snapping">
<pages:ScrollSnapPage />
</TabItem>
<TabItem Header="Slider">
<pages:SliderPage />
</TabItem>

222
samples/ControlCatalog/Pages/ScrollSnapPage.xaml

@ -1,222 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DesignHeight="800"
d:DesignWidth="400"
x:Class="ControlCatalog.Pages.ScrollSnapPage"
xmlns:pages="using:ControlCatalog.Pages"
x:DataType="pages:ScrollSnapPageViewModel">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock TextWrapping="Wrap"
Classes="h2">Scrollviewer can snap supported content both vertically and horizontally. Snapping occurs from scrolling with touch or pen.</TextBlock>
<Grid RowDefinitions="Auto, Auto, Auto, Auto, Auto">
<StackPanel Orientation="Horizontal"
Spacing="4">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Type" />
<ComboBox ItemsSource="{Binding AvailableSnapPointsType}"
SelectedItem="{Binding SnapPointsType}" />
</StackPanel>
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Alignment" />
<ComboBox ItemsSource="{Binding AvailableSnapPointsAlignment}"
SelectedItem="{Binding SnapPointsAlignment}" />
</StackPanel>
<ToggleSwitch IsChecked="{Binding AreSnapPointsRegular}"
OffContent="No"
OnContent="Yes"
Content="Are Snap Points regular?" />
</StackPanel>
<TextBlock TextWrapping="Wrap"
Grid.Row="1"
Margin="0,10"
Classes="h2">Vertical Snapping</TextBlock>
<Border
BorderBrush="Green"
BorderThickness="1"
Padding="0"
Grid.Row="2"
Margin="10, 5">
<ScrollViewer x:Name="VerticalSnapsScrollViewer"
VerticalSnapPointsType="{Binding SnapPointsType}"
VerticalSnapPointsAlignment="{Binding SnapPointsAlignment}"
HorizontalAlignment="Stretch"
Height="350"
HorizontalScrollBarVisibility="Disabled">
<StackPanel AreVerticalSnapPointsRegular="{Binding AreSnapPointsRegular}"
Orientation="Vertical"
HorizontalAlignment="Stretch">
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 1"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 2"/>
</Border>
<Border Padding="5, 20"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 3"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 4"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 5"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 6"/>
</Border>
<Border Padding="5,8"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 7"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 8"/>
</Border>
<Border Padding="5,4"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 9"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 20"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 11"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 12"/>
</Border>
</StackPanel>
</ScrollViewer>
</Border>
<TextBlock TextWrapping="Wrap"
Grid.Row="3"
Margin="0,10"
Classes="h2">Horizontal Snapping</TextBlock>
<Border
BorderBrush="Green"
BorderThickness="1"
Padding="0"
Grid.Row="4"
Margin="10, 10">
<ScrollViewer x:Name="HorizontalSnapsScrollViewer"
HorizontalSnapPointsType="{Binding SnapPointsType}"
HorizontalSnapPointsAlignment="{Binding SnapPointsAlignment}"
HorizontalAlignment="Stretch"
Height="350"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled">
<StackPanel AreHorizontalSnapPointsRegular="{Binding AreSnapPointsRegular}"
Orientation="Horizontal"
HorizontalAlignment="Stretch">
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 1"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 2"/>
</Border>
<Border Padding="5, 20"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 3"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 4"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 5"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 6"/>
</Border>
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</StackPanel>
</UserControl>

68
samples/ControlCatalog/Pages/ScrollSnapPage.xaml.cs

@ -1,68 +0,0 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Xaml;
using MiniMvvm;
namespace ControlCatalog.Pages
{
public class ScrollSnapPageViewModel : ViewModelBase
{
private SnapPointsType _snapPointsType;
private SnapPointsAlignment _snapPointsAlignment;
private bool _areSnapPointsRegular;
public ScrollSnapPageViewModel()
{
AvailableSnapPointsType = new List<SnapPointsType>()
{
SnapPointsType.None,
SnapPointsType.Mandatory,
SnapPointsType.MandatorySingle
};
AvailableSnapPointsAlignment = new List<SnapPointsAlignment>()
{
SnapPointsAlignment.Near,
SnapPointsAlignment.Center,
SnapPointsAlignment.Far,
};
}
public bool AreSnapPointsRegular
{
get => _areSnapPointsRegular;
set => this.RaiseAndSetIfChanged(ref _areSnapPointsRegular, value);
}
public SnapPointsType SnapPointsType
{
get => _snapPointsType;
set => this.RaiseAndSetIfChanged(ref _snapPointsType, value);
}
public SnapPointsAlignment SnapPointsAlignment
{
get => _snapPointsAlignment;
set => this.RaiseAndSetIfChanged(ref _snapPointsAlignment, value);
}
public List<SnapPointsType> AvailableSnapPointsType { get; }
public List<SnapPointsAlignment> AvailableSnapPointsAlignment { get; }
}
public class ScrollSnapPage : UserControl
{
public ScrollSnapPage()
{
this.InitializeComponent();
DataContext = new ScrollSnapPageViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

290
samples/ControlCatalog/Pages/ScrollViewerPage.xaml

@ -3,35 +3,267 @@
xmlns:pages="using:ControlCatalog.Pages"
x:Class="ControlCatalog.Pages.ScrollViewerPage"
x:DataType="pages:ScrollViewerPageViewModel">
<StackPanel Orientation="Vertical" Spacing="20">
<TextBlock TextWrapping="Wrap" Classes="h2">Allows for horizontal and vertical content scrolling. Supports snapping on touch and pointer wheel scrolling.</TextBlock>
<Grid ColumnDefinitions="Auto, *">
<StackPanel Orientation="Vertical" Spacing="4">
<ToggleSwitch IsChecked="{Binding AllowAutoHide}" Content="Allow auto hide" />
<ToggleSwitch IsChecked="{Binding EnableInertia}" Content="Enable Inertia" />
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="Horizontal Scroll" />
<ComboBox ItemsSource="{Binding AvailableVisibility}" SelectedItem="{Binding HorizontalScrollVisibility}" />
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="Vertical Scroll" />
<ComboBox ItemsSource="{Binding AvailableVisibility}" SelectedItem="{Binding VerticalScrollVisibility}" />
</StackPanel>
<TabControl>
<TabItem Header="ScrollViewer">
<StackPanel Orientation="Vertical"
Spacing="20">
<TextBlock TextWrapping="Wrap"
Classes="h2">Allows for horizontal and vertical content scrolling. Supports snapping on touch and pointer wheel scrolling.</TextBlock>
<Grid ColumnDefinitions="Auto, *">
<StackPanel Orientation="Vertical"
Spacing="4">
<ToggleSwitch IsChecked="{Binding AllowAutoHide}"
Content="Allow auto hide" />
<ToggleSwitch IsChecked="{Binding EnableInertia}"
Content="Enable Inertia" />
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Horizontal Scroll" />
<ComboBox ItemsSource="{Binding AvailableVisibility}"
SelectedItem="{Binding HorizontalScrollVisibility}" />
</StackPanel>
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Vertical Scroll" />
<ComboBox ItemsSource="{Binding AvailableVisibility}"
SelectedItem="{Binding VerticalScrollVisibility}" />
</StackPanel>
</StackPanel>
<ScrollViewer x:Name="ScrollViewer"
Grid.Column="1"
Width="400"
Height="400"
IsScrollInertiaEnabled="{Binding EnableInertia}"
AllowAutoHide="{Binding AllowAutoHide}"
HorizontalScrollBarVisibility="{Binding HorizontalScrollVisibility}"
VerticalScrollBarVisibility="{Binding VerticalScrollVisibility}">
<Image Width="800"
Height="800"
Stretch="UniformToFill"
Source="/Assets/delicate-arch-896885_640.jpg" />
</ScrollViewer>
</Grid>
</StackPanel>
</TabItem>
<TabItem Header="Snapping">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock TextWrapping="Wrap"
Classes="h2">Scrollviewer can snap supported content both vertically and horizontally. Snapping occurs from scrolling with touch or pen.</TextBlock>
<Grid RowDefinitions="Auto, Auto, Auto, Auto, Auto">
<StackPanel Orientation="Horizontal"
Spacing="4">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Type" />
<ComboBox ItemsSource="{Binding AvailableSnapPointsType}"
SelectedItem="{Binding SnapPointsType}" />
</StackPanel>
<ScrollViewer x:Name="ScrollViewer"
Grid.Column="1"
Width="400" Height="400"
IsScrollInertiaEnabled="{Binding EnableInertia}"
AllowAutoHide="{Binding AllowAutoHide}"
HorizontalScrollBarVisibility="{Binding HorizontalScrollVisibility}"
VerticalScrollBarVisibility="{Binding VerticalScrollVisibility}">
<Image Width="800" Height="800" Stretch="UniformToFill"
Source="/Assets/delicate-arch-896885_640.jpg" />
</ScrollViewer>
</Grid>
</StackPanel>
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Alignment" />
<ComboBox ItemsSource="{Binding AvailableSnapPointsAlignment}"
SelectedItem="{Binding SnapPointsAlignment}" />
</StackPanel>
<ToggleSwitch IsChecked="{Binding AreSnapPointsRegular}"
OffContent="No"
OnContent="Yes"
Content="Are Snap Points regular?" />
</StackPanel>
<TextBlock TextWrapping="Wrap"
Grid.Row="1"
Margin="0,10"
Classes="h2">Vertical Snapping</TextBlock>
<Border
BorderBrush="Green"
BorderThickness="1"
Padding="0"
Grid.Row="2"
Margin="10, 5">
<ScrollViewer x:Name="VerticalSnapsScrollViewer"
VerticalSnapPointsType="{Binding SnapPointsType}"
VerticalSnapPointsAlignment="{Binding SnapPointsAlignment}"
HorizontalAlignment="Stretch"
Height="350"
HorizontalScrollBarVisibility="Disabled">
<StackPanel AreVerticalSnapPointsRegular="{Binding AreSnapPointsRegular}"
Orientation="Vertical"
HorizontalAlignment="Stretch">
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 1"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 2"/>
</Border>
<Border Padding="5, 20"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 3"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 4"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 5"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 6"/>
</Border>
<Border Padding="5,8"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 7"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 8"/>
</Border>
<Border Padding="5,4"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 9"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 20"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 11"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 12"/>
</Border>
</StackPanel>
</ScrollViewer>
</Border>
<TextBlock TextWrapping="Wrap"
Grid.Row="3"
Margin="0,10"
Classes="h2">Horizontal Snapping</TextBlock>
<Border
BorderBrush="Green"
BorderThickness="1"
Padding="0"
Grid.Row="4"
Margin="10, 10">
<ScrollViewer x:Name="HorizontalSnapsScrollViewer"
HorizontalSnapPointsType="{Binding SnapPointsType}"
HorizontalSnapPointsAlignment="{Binding SnapPointsAlignment}"
HorizontalAlignment="Stretch"
Height="350"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled">
<StackPanel AreHorizontalSnapPointsRegular="{Binding AreSnapPointsRegular}"
Orientation="Horizontal"
HorizontalAlignment="Stretch">
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 1"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 2"/>
</Border>
<Border Padding="5, 20"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 3"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 4"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 5"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 6"/>
</Border>
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</StackPanel>
</TabItem>
</TabControl>
</UserControl>

37
samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs

@ -12,6 +12,9 @@ namespace ControlCatalog.Pages
private bool _enableInertia;
private ScrollBarVisibility _horizontalScrollVisibility;
private ScrollBarVisibility _verticalScrollVisibility;
private SnapPointsType _snapPointsType;
private SnapPointsAlignment _snapPointsAlignment;
private bool _areSnapPointsRegular;
public ScrollViewerPageViewModel()
{
@ -23,6 +26,20 @@ namespace ControlCatalog.Pages
ScrollBarVisibility.Disabled,
};
AvailableSnapPointsType = new List<SnapPointsType>()
{
SnapPointsType.None,
SnapPointsType.Mandatory,
SnapPointsType.MandatorySingle
};
AvailableSnapPointsAlignment = new List<SnapPointsAlignment>()
{
SnapPointsAlignment.Near,
SnapPointsAlignment.Center,
SnapPointsAlignment.Far,
};
HorizontalScrollVisibility = ScrollBarVisibility.Auto;
VerticalScrollVisibility = ScrollBarVisibility.Auto;
AllowAutoHide = true;
@ -54,6 +71,26 @@ namespace ControlCatalog.Pages
}
public List<ScrollBarVisibility> AvailableVisibility { get; }
public bool AreSnapPointsRegular
{
get => _areSnapPointsRegular;
set => this.RaiseAndSetIfChanged(ref _areSnapPointsRegular, value);
}
public SnapPointsType SnapPointsType
{
get => _snapPointsType;
set => this.RaiseAndSetIfChanged(ref _snapPointsType, value);
}
public SnapPointsAlignment SnapPointsAlignment
{
get => _snapPointsAlignment;
set => this.RaiseAndSetIfChanged(ref _snapPointsAlignment, value);
}
public List<SnapPointsType> AvailableSnapPointsType { get; }
public List<SnapPointsAlignment> AvailableSnapPointsAlignment { get; }
}
public class ScrollViewerPage : UserControl

2
samples/ControlCatalog/Pages/TabControlPage.xaml

@ -51,7 +51,7 @@
Text="From DataTemplate">
</TextBlock>
<TabControl
Items="{Binding Tabs}"
ItemsSource="{Binding Tabs}"
Margin="0 16"
DisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}"
TabStripPlacement="{Binding TabPlacement}">

8
samples/VirtualizationDemo/MainWindow.xaml

@ -11,7 +11,7 @@
Margin="16 0 0 0"
Width="150"
Spacing="4">
<ComboBox Items="{Binding Orientations}"
<ComboBox ItemsSource="{Binding Orientations}"
SelectedItem="{Binding Orientation}"/>
<TextBox Watermark="Item Count"
UseFloatingWatermark="True"
@ -26,10 +26,10 @@
UseFloatingWatermark="True"
Text="{Binding #listBox.Scroll.Viewport, Mode=OneWay}"/>
<TextBlock>Horiz. ScrollBar</TextBlock>
<ComboBox Items="{Binding ScrollBarVisibilities}"
<ComboBox ItemsSource="{Binding ScrollBarVisibilities}"
SelectedItem="{Binding HorizontalScrollBarVisibility}"/>
<TextBlock>Vert. ScrollBar</TextBlock>
<ComboBox Items="{Binding ScrollBarVisibilities}"
<ComboBox ItemsSource="{Binding ScrollBarVisibilities}"
SelectedItem="{Binding VerticalScrollBarVisibility}"/>
<TextBox Watermark="Item to Create"
UseFloatingWatermark="True"
@ -44,7 +44,7 @@
</StackPanel>
<ListBox Name="listBox"
Items="{Binding Items}"
ItemsSource="{Binding Items}"
Selection="{Binding Selection}"
SelectionMode="Multiple"
ScrollViewer.HorizontalScrollBarVisibility="{Binding HorizontalScrollBarVisibility, Mode=TwoWay}"

4
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -32,10 +32,6 @@ namespace Avalonia.Android
{
lifetime.View = View;
}
Window?.ClearFlags(WindowManagerFlags.TranslucentStatus);
Window?.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
base.OnCreate(savedInstanceState);
SetContentView(View);

43
src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs

@ -2,11 +2,10 @@
using System.Collections.Generic;
using Android.OS;
using Android.Views;
using AndroidX.AppCompat.App;
using AndroidX.Core.View;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls.Platform;
using static Avalonia.Controls.Platform.IInsetsManager;
using Avalonia.Media;
namespace Avalonia.Android.Platform
{
@ -20,6 +19,7 @@ namespace Avalonia.Android.Platform
private bool? _systemUiVisibility;
private SystemBarTheme? _statusBarTheme;
private bool? _isDefaultSystemBarLightTheme;
private Color? _systemBarColor;
public event EventHandler<SafeAreaChangedArgs> SafeAreaChanged;
@ -36,6 +36,16 @@ namespace Avalonia.Android.Platform
}
WindowCompat.SetDecorFitsSystemWindows(_activity.Window, !value);
if(value)
{
_activity.Window.AddFlags(WindowManagerFlags.TranslucentStatus);
_activity.Window.AddFlags(WindowManagerFlags.TranslucentNavigation);
}
else
{
SystemBarColor = _systemBarColor;
}
}
}
@ -93,6 +103,7 @@ namespace Avalonia.Android.Platform
public WindowInsetsCompat OnApplyWindowInsets(View v, WindowInsetsCompat insets)
{
NotifySafeAreaChanged(SafeAreaPadding);
insets = ViewCompat.OnApplyWindowInsets(v, insets);
return insets;
}
@ -146,8 +157,6 @@ namespace Avalonia.Android.Platform
compat.AppearanceLightStatusBars = value == Controls.Platform.SystemBarTheme.Light;
compat.AppearanceLightNavigationBars = value == Controls.Platform.SystemBarTheme.Light;
AppCompatDelegate.DefaultNightMode = isDefault ? AppCompatDelegate.ModeNightFollowSystem : compat.AppearanceLightStatusBars ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes;
}
}
@ -190,10 +199,36 @@ namespace Avalonia.Android.Platform
}
}
public Color? SystemBarColor
{
get => _systemBarColor;
set
{
_systemBarColor = value;
if (_systemBarColor is { } color && !_displayEdgeToEdge && _activity.Window != null)
{
_activity.Window.ClearFlags(WindowManagerFlags.TranslucentStatus);
_activity.Window.ClearFlags(WindowManagerFlags.TranslucentNavigation);
_activity.Window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
var androidColor = global::Android.Graphics.Color.Argb(color.A, color.R, color.G, color.B);
_activity.Window.SetStatusBarColor(androidColor);
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
// As we can only change the navigation bar's foreground api 26 and newer, we only change the background color if running on those versions
_activity.Window.SetNavigationBarColor(androidColor);
}
}
}
}
internal void ApplyStatusBarState()
{
IsSystemBarVisible = _systemUiVisibility;
SystemBarTheme = _statusBarTheme;
SystemBarColor = _systemBarColor;
}
private class InsetsAnimationCallback : WindowInsetsAnimationCompat.Callback

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

@ -9,6 +9,7 @@ using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using AndroidX.AppCompat.App;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage;
@ -286,6 +287,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_ => null,
};
}
AppCompatDelegate.DefaultNightMode = themeVariant == PlatformThemeVariant.Light ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes;
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);

41
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -1,6 +1,6 @@
using System;
using Avalonia.Reactive;
using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia
{
@ -34,8 +34,8 @@ namespace Avalonia
/// </remarks>
public static IObservable<object?> GetObservable(this AvaloniaObject o, AvaloniaProperty property)
{
return new AvaloniaPropertyObservable<object?>(
o ?? throw new ArgumentNullException(nameof(o)),
return new AvaloniaPropertyObservable<object?, object?>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)));
}
@ -54,11 +54,23 @@ namespace Avalonia
/// </remarks>
public static IObservable<T> GetObservable<T>(this AvaloniaObject o, AvaloniaProperty<T> property)
{
return new AvaloniaPropertyObservable<T>(
return new AvaloniaPropertyObservable<T, T>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)));
}
/// <inheritdoc cref="GetObservable{T}(AvaloniaObject, AvaloniaProperty{T})"/>
/// <param name="o"/>
/// <param name="property"/>
/// <param name="converter">A method which is executed to convert each property value to <typeparamref name="TResult"/>.</param>
public static IObservable<TResult> GetObservable<TSource, TResult>(this AvaloniaObject o, AvaloniaProperty<TSource> property, Func<TSource, TResult> converter)
{
return new AvaloniaPropertyObservable<TSource, TResult>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)),
converter ?? throw new ArgumentNullException(nameof(converter)));
}
/// <summary>
/// Gets an observable for an <see cref="AvaloniaProperty"/>.
/// </summary>
@ -75,7 +87,7 @@ namespace Avalonia
this AvaloniaObject o,
AvaloniaProperty property)
{
return new AvaloniaPropertyBindingObservable<object?>(
return new AvaloniaPropertyBindingObservable<object?, object?>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)));
}
@ -97,12 +109,27 @@ namespace Avalonia
this AvaloniaObject o,
AvaloniaProperty<T> property)
{
return new AvaloniaPropertyBindingObservable<T>(
return new AvaloniaPropertyBindingObservable<T, T>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)));
}
/// <inheritdoc cref="GetBindingObservable{T}(AvaloniaObject, AvaloniaProperty{T})"/>
/// <param name="o"/>
/// <param name="property"/>
/// <param name="converter">A method which is executed to convert each property value to <typeparamref name="TResult"/>.</param>
public static IObservable<BindingValue<TResult>> GetBindingObservable<TSource, TResult>(
this AvaloniaObject o,
AvaloniaProperty<TSource> property,
Func<TSource, TResult> converter)
{
return new AvaloniaPropertyBindingObservable<TSource, TResult>(
o ?? throw new ArgumentNullException(nameof(o)),
property ?? throw new ArgumentNullException(nameof(property)),
converter ?? throw new ArgumentNullException(nameof(converter)));
}
/// <summary>
/// Gets an observable that listens for property changed events for an
/// <see cref="AvaloniaProperty"/>.
@ -338,7 +365,7 @@ namespace Avalonia
return InstancedBinding.OneWay(_source);
}
}
private class ClassHandlerObserver<TTarget, TValue> : IObserver<AvaloniaPropertyChangedEventArgs<TValue>>
{
private readonly Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> _action;

6
src/Avalonia.Base/AvaloniaProperty.cs

@ -499,6 +499,12 @@ namespace Avalonia
/// <param name="o">The object instance.</param>
internal abstract void RouteClearValue(AvaloniaObject o);
/// <summary>
/// Routes an untyped CoerceValue call on a property with its default value to a typed call.
/// </summary>
/// <param name="o">The object instance.</param>
internal abstract void RouteCoerceDefaultValue(AvaloniaObject o);
/// <summary>
/// Routes an untyped GetValue call to a typed call.
/// </summary>

2
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

@ -40,7 +40,7 @@ namespace Avalonia.Data.Core
{
if (reference.TryGetTarget(out var target) && target is AvaloniaObject obj)
{
_subscription = new AvaloniaPropertyObservable<object?>(obj, _property).Subscribe(ValueChanged);
_subscription = new AvaloniaPropertyObservable<object?,object?>(obj, _property).Subscribe(ValueChanged);
}
else
{

5
src/Avalonia.Base/DirectPropertyBase.cs

@ -117,6 +117,11 @@ namespace Avalonia
o.ClearValue<TValue>(this);
}
internal override void RouteCoerceDefaultValue(AvaloniaObject o)
{
// Do nothing.
}
/// <inheritdoc/>
internal override object? RouteGetValue(AvaloniaObject o)
{

24
src/Avalonia.Base/Layout/LayoutManager.cs

@ -269,21 +269,25 @@ namespace Avalonia.Layout
}
}
private void Measure(Layoutable control)
private bool Measure(Layoutable control)
{
if (!control.IsVisible || !control.IsAttachedToVisualTree)
return false;
// Controls closest to the visual root need to be arranged first. We don't try to store
// ordered invalidation lists, instead we traverse the tree upwards, measuring the
// controls closest to the root first. This has been shown by benchmarks to be the
// fastest and most memory-efficient algorithm.
if (control.VisualParent is Layoutable parent)
{
Measure(parent);
if (!Measure(parent))
return false;
}
// If the control being measured has IsMeasureValid == true here then its measure was
// handed by an ancestor and can be ignored. The measure may have also caused the
// control to be removed.
if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
if (!control.IsMeasureValid)
{
if (control is ILayoutRoot root)
{
@ -294,16 +298,22 @@ namespace Avalonia.Layout
control.Measure(control.PreviousMeasure.Value);
}
}
return true;
}
private void Arrange(Layoutable control)
private bool Arrange(Layoutable control)
{
if (!control.IsVisible || !control.IsAttachedToVisualTree)
return false;
if (control.VisualParent is Layoutable parent)
{
Arrange(parent);
if (!Arrange(parent))
return false;
}
if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
if (control.IsMeasureValid && !control.IsArrangeValid)
{
if (control is IEmbeddedLayoutRoot embeddedRoot)
control.Arrange(new Rect(embeddedRoot.AllocatedSize));
@ -316,6 +326,8 @@ namespace Avalonia.Layout
control.Arrange(control.PreviousArrange.Value);
}
}
return true;
}
private void QueueLayoutPass()

18
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -701,7 +701,14 @@ namespace Avalonia.Media.TextFormatting
if (directionalWidth == 0)
{
//In case a run only contains a linebreak we don't want to skip it.
if (currentRun is ShapedTextRun shaped && currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
if (currentRun is ShapedTextRun shaped)
{
if(currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
{
continue;
}
}
else
{
continue;
}
@ -840,7 +847,14 @@ namespace Avalonia.Media.TextFormatting
if (directionalWidth == 0)
{
//In case a run only contains a linebreak we don't want to skip it.
if (currentRun is ShapedTextRun shaped && currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
if (currentRun is ShapedTextRun shaped)
{
if (currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0)
{
continue;
}
}
else
{
continue;
}

2
src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs

@ -9,7 +9,7 @@ namespace Avalonia.Metadata;
/// A typical usage example is a ListBox control, where <see cref="InheritDataTypeFromItemsAttribute"/> is defined on the ItemTemplate property,
/// allowing the template to inherit the data type from the Items collection binding.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class InheritDataTypeFromItemsAttribute : Attribute
{
/// <summary>

49
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@ -36,12 +36,23 @@ namespace Avalonia.PropertyStore
/// </summary>
public IValueEntry? BaseValueEntry { get; private set; }
/// <summary>
/// Gets a value indicating whether the property has a coercion function.
/// </summary>
public bool HasCoercion { get; protected set; }
/// <summary>
/// Gets a value indicating whether the <see cref="Value"/> was overridden by a call to
/// <see cref="AvaloniaObject.SetCurrentValue{T}"/>.
/// </summary>
public bool IsOverridenCurrentValue { get; set; }
/// <summary>
/// Gets a value indicating whether the <see cref="Value"/> is the result of the
///
/// </summary>
public bool IsCoercedDefaultValue { get; set; }
/// <summary>
/// Begins a reevaluation pass on the effective value.
/// </summary>
@ -63,10 +74,33 @@ namespace Avalonia.PropertyStore
/// <summary>
/// Ends a reevaluation pass on the effective value.
/// </summary>
/// <param name="owner">The associated value store.</param>
/// <param name="property">The property being reevaluated.</param>
/// <remarks>
/// This method unsubscribes from any unused value entries.
/// Handles coercing the default value if necessary.
/// </remarks>
public void EndReevaluation()
public void EndReevaluation(ValueStore owner, AvaloniaProperty property)
{
if (Priority == BindingPriority.Unset && HasCoercion)
CoerceDefaultValueAndRaise(owner, property);
}
/// <summary>
/// Gets a value indicating whether the effective value represents the default value of the
/// property and can be removed.
/// </summary>
/// <returns>True if the effective value van be removed; otherwise false.</returns>
public bool CanRemove()
{
return Priority == BindingPriority.Unset &&
!IsOverridenCurrentValue &&
!IsCoercedDefaultValue;
}
/// <summary>
/// Unsubscribes from any unused value entries.
/// </summary>
public void UnsubscribeIfNecessary()
{
if (Priority == BindingPriority.Unset)
{
@ -130,6 +164,17 @@ namespace Avalonia.PropertyStore
/// <param name="property">The property being cleared.</param>
public abstract void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property);
/// <summary>
/// Coerces the default value, raising <see cref="AvaloniaObject.PropertyChanged"/>
/// where necessary.
/// </summary>
/// <param name="owner">The associated value store.</param>
/// <param name="property">The property being coerced.</param>
protected abstract void CoerceDefaultValueAndRaise(ValueStore owner, AvaloniaProperty property);
/// <summary>
/// Gets the current effective value as a boxed value.
/// </summary>
protected abstract object? GetBoxedValue();
protected void UpdateValueEntry(IValueEntry? entry, BindingPriority priority)

42
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
@ -33,19 +32,16 @@ namespace Avalonia.PropertyStore
if (_metadata.CoerceValue is { } coerce)
{
HasCoercion = true;
_uncommon = new()
{
_coerce = coerce,
_uncoercedValue = value,
_uncoercedBaseValue = value,
};
Value = coerce(owner, value);
}
else
{
Value = value;
}
Value = value;
}
/// <summary>
@ -61,7 +57,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(priority != BindingPriority.LocalValue);
UpdateValueEntry(value, priority);
SetAndRaiseCore(owner, (StyledProperty<T>)value.Property, GetValue(value), priority, false);
SetAndRaiseCore(owner, (StyledProperty<T>)value.Property, GetValue(value), priority);
if (priority > BindingPriority.LocalValue &&
value.GetDataValidationState(out var state, out var error))
@ -75,7 +71,7 @@ namespace Avalonia.PropertyStore
StyledProperty<T> property,
T value)
{
SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue, false);
SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue);
}
public void SetCurrentValueAndRaise(
@ -83,8 +79,15 @@ namespace Avalonia.PropertyStore
StyledProperty<T> property,
T value)
{
IsOverridenCurrentValue = true;
SetAndRaiseCore(owner, property, value, Priority, true);
SetAndRaiseCore(owner, property, value, Priority, isOverriddenCurrentValue: true);
}
public void SetCoercedDefaultValueAndRaise(
ValueStore owner,
StyledProperty<T> property,
T value)
{
SetAndRaiseCore(owner, property, value, Priority, isCoercedDefaultValue: true);
}
public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
@ -117,7 +120,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(Priority != BindingPriority.Animation);
Debug.Assert(BasePriority != BindingPriority.Unset);
UpdateValueEntry(null, BindingPriority.Animation);
SetAndRaiseCore(owner, (StyledProperty<T>)property, _baseValue!, BasePriority, false);
SetAndRaiseCore(owner, (StyledProperty<T>)property, _baseValue!, BasePriority);
}
public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
@ -168,6 +171,17 @@ namespace Avalonia.PropertyStore
}
}
protected override void CoerceDefaultValueAndRaise(ValueStore owner, AvaloniaProperty property)
{
Debug.Assert(_uncommon?._coerce is not null);
Debug.Assert(Priority == BindingPriority.Unset);
var coercedDefaultValue = _uncommon!._coerce!(owner.Owner, _metadata.DefaultValue);
if (!EqualityComparer<T>.Default.Equals(_metadata.DefaultValue, coercedDefaultValue))
SetCoercedDefaultValueAndRaise(owner, (StyledProperty<T>)property, coercedDefaultValue);
}
protected override object? GetBoxedValue() => Value;
private static T GetValue(IValueEntry entry)
@ -183,7 +197,8 @@ namespace Avalonia.PropertyStore
StyledProperty<T> property,
T value,
BindingPriority priority,
bool isOverriddenCurrentValue)
bool isOverriddenCurrentValue = false,
bool isCoercedDefaultValue = false)
{
var oldValue = Value;
var valueChanged = false;
@ -191,6 +206,7 @@ namespace Avalonia.PropertyStore
var v = value;
IsOverridenCurrentValue = isOverriddenCurrentValue;
IsCoercedDefaultValue = isCoercedDefaultValue;
if (_uncommon?._coerce is { } coerce)
v = coerce(owner.Owner, value);

52
src/Avalonia.Base/PropertyStore/ValueStore.cs

@ -259,6 +259,27 @@ namespace Avalonia.PropertyStore
{
if (_effectiveValues.TryGetValue(property, out var v))
v.CoerceValue(this, property);
else
property.RouteCoerceDefaultValue(Owner);
}
public void CoerceDefaultValue<T>(StyledProperty<T> property)
{
var metadata = property.GetMetadata(Owner.GetType());
if (metadata.CoerceValue is null)
return;
var coercedDefaultValue = metadata.CoerceValue(Owner, metadata.DefaultValue);
if (EqualityComparer<T>.Default.Equals(metadata.DefaultValue, coercedDefaultValue))
return;
// We have a situation where the default value isn't valid according to the coerce
// function. In this case, we need to create an EffectiveValue entry.
var effectiveValue = CreateEffectiveValue(property);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetCoercedDefaultValueAndRaise(this, property, coercedDefaultValue);
}
public Optional<T> GetBaseValue<T>(StyledProperty<T> property)
@ -840,20 +861,25 @@ namespace Avalonia.PropertyStore
goto restart;
}
if (current?.Priority == BindingPriority.Unset)
if (current is not null)
{
if (current.BasePriority == BindingPriority.Unset)
{
RemoveEffectiveValue(property);
current.DisposeAndRaiseUnset(this, property);
}
else
current.EndReevaluation(this, property);
if (current.CanRemove())
{
current.RemoveAnimationAndRaise(this, property);
if (current.BasePriority == BindingPriority.Unset)
{
RemoveEffectiveValue(property);
current.DisposeAndRaiseUnset(this, property);
}
else
{
current.RemoveAnimationAndRaise(this, property);
}
}
}
current?.EndReevaluation();
current.UnsubscribeIfNecessary();
}
}
finally
{
@ -925,7 +951,9 @@ namespace Avalonia.PropertyStore
{
_effectiveValues.GetKeyValue(i, out var key, out var e);
if (e.Priority == BindingPriority.Unset && !e.IsOverridenCurrentValue)
e.EndReevaluation(this, key);
if (e.CanRemove())
{
RemoveEffectiveValue(key, i);
e.DisposeAndRaiseUnset(this, key);
@ -934,7 +962,7 @@ namespace Avalonia.PropertyStore
break;
}
e.EndReevaluation();
e.UnsubscribeIfNecessary();
}
}
finally

85
src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs

@ -4,18 +4,21 @@ using Avalonia.Data;
namespace Avalonia.Reactive
{
internal class AvaloniaPropertyBindingObservable<T> : LightweightObservableBase<BindingValue<T>>, IDescription
internal class AvaloniaPropertyBindingObservable<TSource,TResult> : LightweightObservableBase<BindingValue<TResult>>, IDescription
{
private readonly WeakReference<AvaloniaObject> _target;
private readonly AvaloniaProperty _property;
private BindingValue<T> _value = BindingValue<T>.Unset;
private readonly Func<TSource, TResult>? _converter;
private BindingValue<TResult> _value = BindingValue<TResult>.Unset;
public AvaloniaPropertyBindingObservable(
AvaloniaObject target,
AvaloniaProperty property)
AvaloniaProperty property,
Func<TSource, TResult>? converter = null)
{
_target = new WeakReference<AvaloniaObject>(target);
_property = property;
_converter = converter;
}
public string Description => $"{_target.GetType().Name}.{_property.Name}";
@ -24,8 +27,17 @@ namespace Avalonia.Reactive
{
if (_target.TryGetTarget(out var target))
{
_value = (T)target.GetValue(_property)!;
target.PropertyChanged += PropertyChanged;
if (_converter is { } converter)
{
var unconvertedValue = (TSource)target.GetValue(_property)!;
_value = converter(unconvertedValue);
target.PropertyChanged += PropertyChanged_WithConversion;
}
else
{
_value = (TResult)target.GetValue(_property)!;
target.PropertyChanged += PropertyChanged;
}
}
}
@ -33,11 +45,18 @@ namespace Avalonia.Reactive
{
if (_target.TryGetTarget(out var target))
{
target.PropertyChanged -= PropertyChanged;
if (_converter is not null)
{
target.PropertyChanged -= PropertyChanged_WithConversion;
}
else
{
target.PropertyChanged -= PropertyChanged;
}
}
}
protected override void Subscribed(IObserver<BindingValue<T>> observer, bool first)
protected override void Subscribed(IObserver<BindingValue<TResult>> observer, bool first)
{
if (_value.Type != BindingValueType.UnsetValue)
{
@ -49,27 +68,59 @@ namespace Avalonia.Reactive
{
if (e.Property == _property)
{
if (e is AvaloniaPropertyChangedEventArgs<T> typedArgs)
if (e is AvaloniaPropertyChangedEventArgs<TResult> typedArgs)
{
var newValue = e.Sender.GetValue<T>(typedArgs.Property);
PublishValue(e.Sender.GetValue<TResult>(typedArgs.Property));
}
else
{
PublishUntypedValue(e.Sender.GetValue(e.Property));
}
}
}
if (!_value.HasValue || !EqualityComparer<T>.Default.Equals(newValue, _value.Value))
{
_value = newValue;
PublishNext(_value);
}
private void PropertyChanged_WithConversion(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _property)
{
if (e is AvaloniaPropertyChangedEventArgs<TSource> typedArgs)
{
var newValueRaw = e.Sender.GetValue<TSource>(typedArgs.Property);
var newValue = _converter!(newValueRaw);
PublishValue(newValue);
}
else
{
var newValue = e.Sender.GetValue(e.Property);
if (!Equals(newValue, _value))
if (newValue is TSource source)
{
_value = (T)newValue!;
PublishNext(_value);
newValue = _converter!(source);
}
PublishUntypedValue(newValue);
}
}
}
private void PublishValue(TResult newValue)
{
if (!_value.HasValue || !EqualityComparer<TResult>.Default.Equals(newValue, _value.Value))
{
_value = newValue;
PublishNext(_value);
}
}
private void PublishUntypedValue(object? newValue)
{
if (!Equals(newValue, _value))
{
_value = (TResult)newValue!;
PublishNext(_value);
}
}
}
}

73
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@ -4,18 +4,21 @@ using Avalonia.Data;
namespace Avalonia.Reactive
{
internal class AvaloniaPropertyObservable<T> : LightweightObservableBase<T>, IDescription
internal class AvaloniaPropertyObservable<TSource,TResult> : LightweightObservableBase<TResult>, IDescription
{
private readonly WeakReference<AvaloniaObject> _target;
private readonly AvaloniaProperty _property;
private Optional<T> _value;
private readonly Func<TSource, TResult>? _converter;
private Optional<TResult> _value;
public AvaloniaPropertyObservable(
AvaloniaObject target,
AvaloniaProperty property)
AvaloniaProperty property,
Func<TSource,TResult>? converter = null)
{
_target = new WeakReference<AvaloniaObject>(target);
_property = property;
_converter = converter;
}
public string Description => $"{_target.GetType().Name}.{_property.Name}";
@ -24,8 +27,17 @@ namespace Avalonia.Reactive
{
if (_target.TryGetTarget(out var target))
{
_value = (T)target.GetValue(_property)!;
target.PropertyChanged += PropertyChanged;
if (_converter is { } converter)
{
var unconvertedValue = (TSource)target.GetValue(_property)!;
_value = converter(unconvertedValue);
target.PropertyChanged += PropertyChanged_WithConversion;
}
else
{
_value = (TResult)target.GetValue(_property)!;
target.PropertyChanged += PropertyChanged;
}
}
}
@ -33,13 +45,20 @@ namespace Avalonia.Reactive
{
if (_target.TryGetTarget(out var target))
{
target.PropertyChanged -= PropertyChanged;
if (_converter is not null)
{
target.PropertyChanged -= PropertyChanged_WithConversion;
}
else
{
target.PropertyChanged -= PropertyChanged;
}
}
_value = default;
}
protected override void Subscribed(IObserver<T> observer, bool first)
protected override void Subscribed(IObserver<TResult> observer, bool first)
{
if (_value.HasValue)
observer.OnNext(_value.Value);
@ -49,23 +68,49 @@ namespace Avalonia.Reactive
{
if (e.Property == _property)
{
T newValue;
TResult newValue;
if (e is AvaloniaPropertyChangedEventArgs<T> typed)
if (e is AvaloniaPropertyChangedEventArgs<TResult> typed)
{
newValue = AvaloniaObjectExtensions.GetValue(e.Sender, typed.Property);
}
else
{
newValue = (T)e.Sender.GetValue(e.Property)!;
newValue = (TResult)e.Sender.GetValue(e.Property)!;
}
if (!_value.HasValue ||
!EqualityComparer<T>.Default.Equals(newValue, _value.Value))
PublishNewValue(newValue);
}
}
private void PropertyChanged_WithConversion(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _property)
{
TSource newValueRaw;
if (e is AvaloniaPropertyChangedEventArgs<TSource> typed)
{
_value = newValue;
PublishNext(_value.Value!);
newValueRaw = AvaloniaObjectExtensions.GetValue(e.Sender, typed.Property);
}
else
{
newValueRaw = (TSource)e.Sender.GetValue(e.Property)!;
}
var newValue = _converter!(newValueRaw);
PublishNewValue(newValue);
}
}
private void PublishNewValue(TResult newValue)
{
if (!_value.HasValue ||
!EqualityComparer<TResult>.Default.Equals(newValue, _value.Value))
{
_value = newValue;
PublishNext(_value.Value!);
}
}
}

5
src/Avalonia.Base/StyledProperty.cs

@ -176,6 +176,11 @@ namespace Avalonia
o.ClearValue<TValue>(this);
}
internal override void RouteCoerceDefaultValue(AvaloniaObject o)
{
o.GetValueStore().CoerceDefaultValue(this);
}
/// <inheritdoc/>
internal override object? RouteGetValue(AvaloniaObject o)
{

0
src/Avalonia.Controls.ColorPicker/ColorView/AlphaComponentPosition.cs → src/Avalonia.Controls.ColorPicker/AlphaComponentPosition.cs

2
src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs

@ -48,7 +48,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<AlphaComponentPosition> HexInputAlphaPositionProperty =
AvaloniaProperty.Register<ColorView, AlphaComponentPosition>(
nameof(HexInputAlphaPosition),
AlphaComponentPosition.Trailing); // Match CSS (and default slider order) instead of XAML/WinUI
AlphaComponentPosition.Leading); // By default match XAML and the WinUI control
/// <summary>
/// Defines the <see cref="HsvColor"/> property.

6
src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs

@ -61,7 +61,11 @@ namespace Avalonia.Controls
{
if (_hexTextBox != null)
{
_hexTextBox.Text = ColorToHexConverter.ToHexString(Color, HexInputAlphaPosition);
_hexTextBox.Text = ColorToHexConverter.ToHexString(
Color,
HexInputAlphaPosition,
includeAlpha: (IsAlphaEnabled && IsAlphaVisible),
includeSymbol: false);
}
}

40
src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs

@ -11,6 +11,18 @@ namespace Avalonia.Controls.Converters
/// </summary>
public class ColorToHexConverter : IValueConverter
{
/// <summary>
/// Gets or sets a value indicating whether the alpha component is visible in the Hex formatted text.
/// </summary>
/// <remarks>
/// When hidden the existing alpha component value is maintained. Also when hidden the user is still
/// able to input an 8-digit number with alpha. Alpha will be processed but then removed when displayed.
///
/// Because this property only controls whether alpha is displayed (and it is still processed regardless)
/// it is termed 'Visible' instead of 'Enabled'.
/// </remarks>
public bool IsAlphaVisible { get; set; } = true;
/// <summary>
/// Gets or sets the position of a color's alpha component relative to all other components.
/// </summary>
@ -48,7 +60,7 @@ namespace Avalonia.Controls.Converters
return AvaloniaProperty.UnsetValue;
}
return ToHexString(color, AlphaPosition, includeSymbol);
return ToHexString(color, AlphaPosition, IsAlphaVisible, includeSymbol);
}
/// <inheritdoc/>
@ -67,26 +79,40 @@ namespace Avalonia.Controls.Converters
/// </summary>
/// <param name="color">The color to represent as a hex value string.</param>
/// <param name="alphaPosition">The output position of the alpha component.</param>
/// <param name="includeAlpha">Whether the alpha component will be included in the hex string.</param>
/// <param name="includeSymbol">Whether the hex symbol '#' will be added.</param>
/// <returns>The input color converted to its hex value string.</returns>
public static string ToHexString(
Color color,
AlphaComponentPosition alphaPosition,
bool includeAlpha = true,
bool includeSymbol = false)
{
uint intColor;
if (alphaPosition == AlphaComponentPosition.Trailing)
string hexColor;
if (includeAlpha)
{
intColor = ((uint)color.R << 24) | ((uint)color.G << 16) | ((uint)color.B << 8) | (uint)color.A;
if (alphaPosition == AlphaComponentPosition.Trailing)
{
intColor = ((uint)color.R << 24) | ((uint)color.G << 16) | ((uint)color.B << 8) | (uint)color.A;
}
else
{
// Default is Leading alpha (same as XAML)
intColor = ((uint)color.A << 24) | ((uint)color.R << 16) | ((uint)color.G << 8) | (uint)color.B;
}
hexColor = intColor.ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant();
}
else
{
// Default is Leading alpha
intColor = ((uint)color.A << 24) | ((uint)color.R << 16) | ((uint)color.G << 8) | (uint)color.B;
// In this case the alpha position no longer matters
// Both cases are calculated the same
intColor = ((uint)color.R << 16) | ((uint)color.G << 8) | (uint)color.B;
hexColor = intColor.ToString("x6", CultureInfo.InvariantCulture).ToUpperInvariant();
}
string hexColor = intColor.ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant();
if (includeSymbol)
{
hexColor = '#' + hexColor;

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

@ -6,6 +6,8 @@
<ControlTheme x:Key="{x:Type ColorPicker}"
TargetType="ColorPicker">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<!-- Alpha position should match CSS (and default slider order) instead of XAML/WinUI -->
<Setter Property="HexInputAlphaPosition" Value="Trailing" />
<Setter Property="Height" Value="32" />
<Setter Property="Width" Value="64" />
<Setter Property="MinWidth" Value="64" />

1
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml

@ -64,6 +64,7 @@
<Border Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent"
BoxShadow="0 0 10 2 #BF000000"
CornerRadius="{TemplateBinding CornerRadius}"
Margin="10">

2
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@ -295,6 +295,8 @@
<ControlTheme x:Key="{x:Type ColorView}"
TargetType="ColorView">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<!-- Alpha position should match CSS (and default slider order) instead of XAML/WinUI -->
<Setter Property="HexInputAlphaPosition" Value="Trailing" />
<Setter Property="Palette">
<controls:FluentColorPalette />
</Setter>

2
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@ -6,6 +6,8 @@
<ControlTheme x:Key="{x:Type ColorPicker}"
TargetType="ColorPicker">
<Setter Property="CornerRadius" Value="0" />
<!-- Alpha position should match CSS (and default slider order) instead of XAML/WinUI -->
<Setter Property="HexInputAlphaPosition" Value="Trailing" />
<Setter Property="Height" Value="32" />
<Setter Property="Width" Value="64" />
<Setter Property="MinWidth" Value="64" />

1
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml

@ -64,6 +64,7 @@
<Border Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent"
BoxShadow="0 0 10 2 #BF000000"
CornerRadius="{TemplateBinding CornerRadius}"
Margin="10">

2
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@ -257,6 +257,8 @@
<ControlTheme x:Key="{x:Type ColorView}"
TargetType="ColorView">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<!-- Alpha position should match CSS (and default slider order) instead of XAML/WinUI -->
<Setter Property="HexInputAlphaPosition" Value="Trailing" />
<Setter Property="Palette">
<controls:FluentColorPalette />
</Setter>

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

@ -440,13 +440,10 @@ namespace Avalonia.Controls
}
Debug.Assert(OwningGrid.Parent is InputElement);
double distanceFromLeft = mousePosition.X;
double distanceFromRight = Bounds.Width - distanceFromLeft;
OnMouseMove_Resize(ref handled, mousePositionHeaders);
OnMouseMove_Reorder(ref handled, mousePosition, mousePositionHeaders, distanceFromLeft, distanceFromRight);
OnMouseMove_Reorder(ref handled, mousePosition, mousePositionHeaders);
SetDragCursor(mousePosition);
}
@ -716,7 +713,7 @@ namespace Avalonia.Controls
}
//TODO DragEvents
private void OnMouseMove_Reorder(ref bool handled, Point mousePosition, Point mousePositionHeaders, double distanceFromLeft, double distanceFromRight)
private void OnMouseMove_Reorder(ref bool handled, Point mousePosition, Point mousePositionHeaders)
{
if (handled)
{
@ -724,7 +721,7 @@ namespace Avalonia.Controls
}
//handle entry into reorder mode
if (_dragMode == DragMode.MouseDown && _dragColumn == null && _lastMousePositionHeaders != null && (distanceFromRight > DATAGRIDCOLUMNHEADER_resizeRegionWidth && distanceFromLeft > DATAGRIDCOLUMNHEADER_resizeRegionWidth))
if (_dragMode == DragMode.MouseDown && _dragColumn == null && _lastMousePositionHeaders != null)
{
var distanceFromInitial = (Vector)(mousePositionHeaders - _lastMousePositionHeaders);
if (distanceFromInitial.Length > DATAGRIDCOLUMNHEADER_columnsDragTreshold)

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

@ -90,7 +90,7 @@ namespace Avalonia.Controls
{
return new MenuFlyoutPresenter
{
[!ItemsControl.ItemsProperty] = this[!ItemsProperty],
[!ItemsControl.ItemsSourceProperty] = this[!ItemsProperty],
[!ItemsControl.ItemTemplateProperty] = this[!ItemTemplateProperty],
[!ItemsControl.ItemContainerThemeProperty] = this[!ItemContainerThemeProperty],
};

14
src/Avalonia.Controls/ItemCollection.cs

@ -106,19 +106,6 @@ namespace Avalonia.Controls
}
}
internal IList? GetItemsPropertyValue()
{
if (_mode == Mode.ObsoleteItemsSetter)
return Source == s_uninitialized ? null : Source;
return this;
}
internal void SetItems(IList? items)
{
_mode = Mode.ObsoleteItemsSetter;
SetSource(items ?? s_uninitialized);
}
internal void SetItemsSource(IEnumerable? value)
{
if (_mode != Mode.ItemsSource && Count > 0)
@ -159,7 +146,6 @@ namespace Avalonia.Controls
{
Items,
ItemsSource,
ObsoleteItemsSetter,
}
}
}

91
src/Avalonia.Controls/ItemsControl.cs

@ -30,17 +30,6 @@ namespace Avalonia.Controls
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new StackPanel());
/// <summary>
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<ItemsControl, IList?> ItemsProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, IList?>(
nameof(Items),
o => o.Items,
#pragma warning disable CS0618 // Type or member is obsolete
(o, v) => o.Items = v);
#pragma warning restore CS0618 // Type or member is obsolete
/// <summary>
/// Defines the <see cref="ItemContainerTheme"/> property.
/// </summary>
@ -94,7 +83,6 @@ namespace Avalonia.Controls
/// </summary>
[AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? DisplayMemberBinding
{
get => GetValue(DisplayMemberBindingProperty);
@ -129,48 +117,20 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the items to display.
/// Gets the items to display.
/// </summary>
/// <remarks>
/// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
/// and an <see cref="ItemsSource"/> property. The properties have the following differences:
///
/// <list type="bullet">
/// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
/// meaning that it cannot be styled </item>
/// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
/// is marked as the content property and will be used for items added via inline XAML.</item>
/// </list>
///
/// In Avalonia 11 the two properties can be used almost interchangeably but this will change
/// in a later version. In order to be ready for this change, follow the following guidance:
///
/// <list type="bullet">
/// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
/// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
/// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
/// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
/// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
/// binding a collection of models which will be transformed by a data template.</item>
/// </list>
/// You use either the <see cref="Items"/> or the <see cref="ItemsSource"/> property to
/// specify the collection that should be used to generate the content of your
/// <see cref="ItemsControl"/>. When the <see cref="ItemsSource"/> property is set, the
/// <see cref="Items"/> collection is made read-only and fixed-size.
///
/// When <see cref="ItemsSource"/> is in use, setting the <see cref="ItemsSource"/>
/// property to null removes the collection and restores usage to <see cref="Items"/>,
/// which will be an empty <see cref="ItemCollection"/>.
/// </remarks>
[Content]
public IList? Items
{
get => _items.GetItemsPropertyValue();
[Obsolete("Use ItemsSource to set or bind items.")]
set
{
var oldItems = _items.GetItemsPropertyValue();
if (value != oldItems)
{
_items.SetItems(value);
RaisePropertyChanged(ItemsProperty, oldItems, value);
}
}
}
public ItemCollection Items => _items;
/// <summary>
/// Gets or sets the <see cref="ControlTheme"/> that is applied to the container element generated for each item.
@ -210,27 +170,17 @@ namespace Avalonia.Controls
/// Gets or sets a collection used to generate the content of the <see cref="ItemsControl"/>.
/// </summary>
/// <remarks>
/// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
/// and an <see cref="ItemsSource"/> property. The properties have the following differences:
///
/// <list type="bullet">
/// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
/// meaning that it cannot be styled </item>
/// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
/// is marked as the content property and will be used for items added via inline XAML.</item>
/// </list>
///
/// In Avalonia 11 the two properties can be used almost interchangeably but this will change
/// in a later version. In order to be ready for this change, follow the following guidance:
/// A common scenario is to use an <see cref="ItemsControl"/> such as a
/// <see cref="ListBox"/> to display a data collection, or to bind an
/// <see cref="ItemsControl"/> to a collection object. To bind an <see cref="ItemsControl"/>
/// to a collection object, use the <see cref="ItemsSource"/> property.
///
/// <list type="bullet">
/// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
/// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
/// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
/// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
/// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
/// binding a collection of models which will be transformed by a data template.</item>
/// </list>
/// When the <see cref="ItemsSource"/> property is set, the <see cref="Items"/> collection
/// is made read-only and fixed-size.
///
/// When <see cref="ItemsSource"/> is in use, setting the property to null removes the
/// collection and restores usage to <see cref="Items"/>, which will be an empty
/// <see cref="ItemCollection"/>.
/// </remarks>
public IEnumerable? ItemsSource
{
@ -242,7 +192,6 @@ namespace Avalonia.Controls
/// Gets or sets the data template used to display the items in the control.
/// </summary>
[InheritDataTypeFromItems(nameof(ItemsSource))]
[InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate
{
get => GetValue(ItemTemplateProperty);

8
src/Avalonia.Controls/Platform/IInsetsManager.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Media;
using Avalonia.Metadata;
#nullable enable
@ -22,7 +23,12 @@ namespace Avalonia.Controls.Platform
/// Gets the current safe area padding.
/// </summary>
Thickness SafeAreaPadding { get; }
/// <summary>
/// Gets or sets the color of the platform's system bars
/// </summary>
Color? SystemBarColor { get; set; }
/// <summary>
/// Occurs when safe area for the current window changes.
/// </summary>

2
src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs

@ -124,7 +124,7 @@ namespace Avalonia.Controls.Primitives
treeTemplate.Match(item) &&
treeTemplate.ItemsSelector(item) is { } itemsBinding)
{
_itemsBinding = BindingOperations.Apply(this, ItemsProperty, itemsBinding, null);
_itemsBinding = BindingOperations.Apply(this, ItemsSourceProperty, itemsBinding, null);
}
}

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

@ -235,7 +235,6 @@ namespace Avalonia.Controls.Primitives
/// </summary>
[AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? SelectedValueBinding
{
get => GetValue(SelectedValueBindingProperty);

15
src/Avalonia.Controls/SelectableTextBlock.cs

@ -19,10 +19,10 @@ namespace Avalonia.Controls
public class SelectableTextBlock : TextBlock, IInlineHost
{
public static readonly StyledProperty<int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<SelectableTextBlock>(new(coerce: TextBox.CoerceCaretIndex));
TextBox.SelectionStartProperty.AddOwner<SelectableTextBlock>();
public static readonly StyledProperty<int> SelectionEndProperty =
TextBox.SelectionEndProperty.AddOwner<SelectableTextBlock>(new(coerce: TextBox.CoerceCaretIndex));
TextBox.SelectionEndProperty.AddOwner<SelectableTextBlock>();
public static readonly DirectProperty<SelectableTextBlock, string> SelectedTextProperty =
AvaloniaProperty.RegisterDirect<SelectableTextBlock, string>(
@ -231,7 +231,7 @@ namespace Avalonia.Controls
{
base.OnPointerPressed(e);
var text = Text;
var text = HasComplexContent ? Inlines?.Text : Text;
var clickInfo = e.GetCurrentPoint(this);
if (text != null && clickInfo.Properties.IsLeftButtonPressed)
@ -317,7 +317,7 @@ namespace Avalonia.Controls
// selection should not change during pointer move if the user right clicks
if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
var text = Text;
var text = HasComplexContent ? Inlines?.Text : Text;
var padding = Padding;
var point = e.GetPosition(this) - new Point(padding.Left, padding.Top);
@ -400,7 +400,10 @@ namespace Avalonia.Controls
private string GetSelection()
{
var textLength = Text?.Length ?? 0;
var text = HasComplexContent ? Inlines?.Text : Text;
var textLength = text?.Length ?? 0;
if (textLength == 0)
{
return "";
@ -418,7 +421,7 @@ namespace Avalonia.Controls
var length = Math.Max(0, end - start);
var selectedText = Text!.Substring(start, length);
var selectedText = text!.Substring(start, length);
return selectedText;
}

2
src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs

@ -70,7 +70,7 @@ namespace Avalonia.Controls
/// </summary>
protected void Toggle()
{
IsChecked = !IsChecked;
SetCurrentValue(IsCheckedProperty, !IsChecked);
}
/// <inheritdoc/>

2
src/Avalonia.Controls/SystemDialog.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls
/// Gets or sets a collection of filters which determine the types of files displayed in an
/// <see cref="OpenFileDialog"/> or an <see cref="SaveFileDialog"/>.
/// </summary>
public List<FileDialogFilter>? Filters { get; set; } = new List<FileDialogFilter>();
public List<FileDialogFilter> Filters { get; set; } = new List<FileDialogFilter>();
/// <summary>
/// Gets or sets initial file name that is displayed when the dialog is opened.

46
src/Avalonia.Controls/TopLevel.cs

@ -87,7 +87,15 @@ namespace Avalonia.Controls
/// <inheritdoc cref="ThemeVariantScope.RequestedThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<Application>();
/// <summary>
/// Defines the SystemBarColor attached property.
/// </summary>
public static readonly AttachedProperty<SolidColorBrush?> SystemBarColorProperty =
AvaloniaProperty.RegisterAttached<TopLevel, Control, SolidColorBrush?>(
"SystemBarColor",
inherits: true);
/// <summary>
/// Defines the <see cref="BackRequested"/> event.
/// </summary>
@ -124,6 +132,22 @@ namespace Avalonia.Controls
{
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue<TopLevel>(KeyboardNavigationMode.Cycle);
AffectsMeasure<TopLevel>(ClientSizeProperty);
SystemBarColorProperty.Changed.AddClassHandler<Control>((view, e) =>
{
if (e.NewValue is SolidColorBrush colorBrush)
{
if (view.Parent is TopLevel tl && tl.InsetsManager is { } insetsManager)
{
insetsManager.SystemBarColor = colorBrush.Color;
}
if (view is TopLevel topLevel && topLevel.InsetsManager is { } insets)
{
insets.SystemBarColor = colorBrush.Color;
}
}
});
}
/// <summary>
@ -379,6 +403,26 @@ namespace Avalonia.Controls
set { SetValue(AccessText.ShowAccessKeyProperty, value); }
}
/// <summary>
/// Helper for setting the color of the platform's system bars
/// </summary>
/// <param name="control">The main view attached to the toplevel, or the toplevel</param>
/// <param name="color">The color to set</param>
public static void SetSystemBarColor(Control control, SolidColorBrush? color)
{
control.SetValue(SystemBarColorProperty, color);
}
/// <summary>
/// Helper for getting the color of the platform's system bars
/// </summary>
/// <param name="control">The main view attached to the toplevel, or the toplevel</param>
/// <returns>The current color of the platform's system bars</returns>
public static SolidColorBrush? GetSystemBarColor(Control control)
{
return control.GetValue(SystemBarColorProperty);
}
/// <inheritdoc/>
double ILayoutRoot.LayoutScaling => PlatformImpl?.RenderScaling ?? 1;

6
src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.cs

@ -34,7 +34,11 @@ namespace Avalonia.Diagnostics.Controls
{
case ISolidColorBrush scb:
{
var colorView = new ColorView { Color = scb.Color };
var colorView = new ColorView
{
HexInputAlphaPosition = AlphaComponentPosition.Leading, // Always match XAML
Color = scb.Color,
};
colorView.ColorChanged += (_, e) => Brush = new ImmutableSolidColorBrush(e.NewColor);

8
src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs

@ -64,11 +64,12 @@ namespace Avalonia.Diagnostics.Views
where TControl : Control, new()
{
var control = new TControl();
var bindingMode = Property.IsReadonly ? BindingMode.OneWay : BindingMode.TwoWay;
init?.Invoke(control);
control.Bind(valueProperty,
new Binding(nameof(Property.Value), BindingMode.TwoWay)
new Binding(nameof(Property.Value), bindingMode)
{
Source = Property,
Converter = converter ?? new ValueConverter(),
@ -129,7 +130,10 @@ namespace Avalonia.Diagnostics.Views
IsEnabled = !Property.IsReadonly
};
var cv = new ColorView();
var cv = new ColorView
{
HexInputAlphaPosition = AlphaComponentPosition.Leading, // Always match XAML
};
cv.Bind(
ColorView.ColorProperty,

4
src/Avalonia.Fonts.Inter/AppBuilderExtension.cs

@ -1,4 +1,6 @@
namespace Avalonia.Fonts.Inter
using Avalonia.Fonts.Inter;
namespace Avalonia
{
public static class AppBuilderExtension
{

2
src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj

@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="Tmds.DBus.Protocol" Version="0.14.0" />
<PackageReference Include="Tmds.DBus.SourceGenerator" Version="0.0.5" />
<PackageReference Include="Tmds.DBus.SourceGenerator" Version="0.0.5" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

32
src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs

@ -2,12 +2,14 @@
using Avalonia.Input.TextInput;
using Avalonia.Native.Interop;
#nullable enable
namespace Avalonia.Native
{
internal class AvaloniaNativeTextInputMethod : ITextInputMethodImpl, IDisposable
{
private ITextInputMethodClient _client;
private IAvnTextInputMethodClient _nativeClient;
private ITextInputMethodClient? _client;
private IAvnTextInputMethodClient? _nativeClient;
private readonly IAvnTextInputMethod _inputMethod;
public AvaloniaNativeTextInputMethod(IAvnWindowBase nativeWindow)
@ -26,7 +28,7 @@ namespace Avalonia.Native
_inputMethod.Reset();
}
public void SetClient(ITextInputMethodClient client)
public void SetClient(ITextInputMethodClient? client)
{
if (_client is { SupportsSurroundingText: true })
{
@ -39,9 +41,9 @@ namespace Avalonia.Native
_nativeClient = null;
_client = client;
if (client != null)
if (_client != null)
{
_nativeClient = new AvnTextInputMethodClient(client);
_nativeClient = new AvnTextInputMethodClient(_client);
OnSurroundingTextChanged(this, EventArgs.Empty);
OnCursorRectangleChanged(this, EventArgs.Empty);
@ -53,16 +55,28 @@ namespace Avalonia.Native
_inputMethod.SetClient(_nativeClient);
}
private void OnCursorRectangleChanged(object sender, EventArgs e)
private void OnCursorRectangleChanged(object? sender, EventArgs e)
{
if (_client == null)
{
return;
}
var visualRoot = _client.TextViewVisual.VisualRoot;
var textViewVisual = _client.TextViewVisual;
if(textViewVisual is null )
{
return;
}
var visualRoot = textViewVisual.VisualRoot;
if(visualRoot is null)
{
return;
}
var transform = _client.TextViewVisual.TransformToVisual((Visual)visualRoot);
var transform = textViewVisual.TransformToVisual((Visual)visualRoot);
if (transform == null)
{
@ -74,7 +88,7 @@ namespace Avalonia.Native
_inputMethod.SetCursorRect(rect.ToAvnRect());
}
private void OnSurroundingTextChanged(object sender, EventArgs e)
private void OnSurroundingTextChanged(object? sender, EventArgs e)
{
if (_client == null)
{

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

@ -3,6 +3,7 @@
<ControlTheme x:Key="{x:Type EmbeddableControlRoot}" TargetType="EmbeddableControlRoot">
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="TopLevel.SystemBarColor" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/>
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="Template">

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

@ -3,6 +3,7 @@
<ControlTheme x:Key="{x:Type Window}" TargetType="Window">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="TransparencyBackgroundFallback" Value="{DynamicResource SystemControlBackgroundAltHighBrush}" />
<Setter Property="TopLevel.SystemBarColor" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/>
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />

3
src/Browser/Avalonia.Browser/BrowserInsetsManager.cs

@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using Avalonia.Browser.Interop;
using Avalonia.Controls.Platform;
using Avalonia.Media;
using static Avalonia.Controls.Platform.IInsetsManager;
namespace Avalonia.Browser
@ -37,6 +38,8 @@ namespace Avalonia.Browser
}
}
public Color? SystemBarColor { get; set; }
public void NotifySafeAreaPaddingChanged()
{
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));

43
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -73,32 +73,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
// Infer data type from collection binding on a control that displays items.
var property = context.ParentNodes().OfType<XamlPropertyAssignmentNode>().FirstOrDefault();
var attributeType = context.GetAvaloniaTypes().InheritDataTypeFromItemsAttribute;
var attributes = property?.Property?.GetClrProperty().CustomAttributes
.Where(a => a.Type == attributeType).ToList();
if (attributes?.Count > 0)
var attribute = property?.Property?.GetClrProperty().CustomAttributes
.FirstOrDefault(a => a.Type == attributeType);
if (attribute is not null)
{
foreach (var attribute in attributes)
var propertyName = (string)attribute.Parameters.First();
XamlAstConstructableObjectNode parentObject;
if (attribute.Properties.TryGetValue("AncestorType", out var type)
&& type is IXamlType xamlType)
{
var propertyName = (string)attribute.Parameters.First();
XamlAstConstructableObjectNode parentObject;
if (attribute.Properties.TryGetValue("AncestorType", out var type)
&& type is IXamlType xamlType)
{
parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>()
.FirstOrDefault(n => n.Type.GetClrType().FullName == xamlType.FullName);
}
else
{
parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault();
}
if (parentObject != null)
{
inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject, propertyName);
if (inferredDataContextTypeNode != null)
break;
}
parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>()
.FirstOrDefault(n => n.Type.GetClrType().FullName == xamlType.FullName);
}
else
{
parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault();
}
if (parentObject != null)
{
inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject, propertyName);
}
}

3
src/iOS/Avalonia.iOS/InsetsManager.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Controls.Platform;
using Avalonia.Media;
using UIKit;
namespace Avalonia.iOS;
@ -80,4 +81,6 @@ internal class InsetsManager : IInsetsManager
}
public Thickness SafeAreaPadding => _controller?.SafeAreaPadding ?? default;
public Color? SystemBarColor { get; set; }
}

162
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Base.UnitTests
@ -182,11 +185,118 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(-150, target.Foo);
}
[Fact]
public void Default_Value_Can_Be_Coerced()
{
var target = new Class1();
var raised = 0;
target.MinFoo = 20;
target.PropertyChanged += (s, e) =>
{
Assert.Equal(Class1.FooProperty, e.Property);
Assert.Equal(11, e.OldValue);
Assert.Equal(20, e.NewValue);
Assert.Equal(BindingPriority.Unset, e.Priority);
++raised;
};
target.CoerceValue(Class1.FooProperty);
Assert.Equal(20, target.Foo);
Assert.Equal(1, raised);
}
[Fact]
public void ClearValue_Respects_Coerced_Default_Value()
{
var target = new Class1();
var raised = 0;
target.Foo = 30;
target.MinFoo = 20;
target.PropertyChanged += (s, e) =>
{
Assert.Equal(Class1.FooProperty, e.Property);
Assert.Equal(30, e.OldValue);
Assert.Equal(20, e.NewValue);
Assert.Equal(BindingPriority.Unset, e.Priority);
++raised;
};
target.ClearValue(Class1.FooProperty);
Assert.Equal(20, target.Foo);
Assert.Equal(1, raised);
}
[Fact]
public void Deactivating_Style_Respects_Coerced_Default_Value()
{
var target = new Control1
{
MinFoo = 20,
};
var root = new TestRoot
{
Styles =
{
new Style(x => x.OfType<Control1>().Class("foo"))
{
Setters =
{
new Setter(Control1.FooProperty, 50),
},
},
},
Child = target,
};
var raised = 0;
target.Classes.Add("foo");
root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(50, target.Foo);
target.PropertyChanged += (s, e) =>
{
Assert.Equal(Control1.FooProperty, e.Property);
Assert.Equal(50, e.OldValue);
Assert.Equal(20, e.NewValue);
Assert.Equal(BindingPriority.Unset, e.Priority);
++raised;
};
target.Classes.Remove("foo");
Assert.Equal(20, target.Foo);
Assert.Equal(1, raised);
}
[Fact]
public void If_Initial_State_Has_Coerced_Default_Value_Then_CoerceValue_Must_Be_Called()
{
// This test is just explicitly describing an edge-case. If the initial state of the
// object results in a coerced property value then CoerceValue must be called before
// coercion takes effect. Confirmed as matching the behavior of WPF.
var target = new Class3();
Assert.Equal(11, target.Foo);
target.CoerceValue(Class3.FooProperty);
Assert.Equal(50, target.Foo);
}
private class Class1 : AvaloniaObject
{
public static readonly StyledProperty<int> FooProperty =
AvaloniaProperty.Register<Class1, int>(
"Qux",
"Foo",
defaultValue: 11,
coerce: CoerceFoo);
@ -215,13 +325,15 @@ namespace Avalonia.Base.UnitTests
set => SetValue(InheritedProperty, value);
}
public int MinFoo { get; set; } = 0;
public int MaxFoo { get; set; } = 100;
public List<AvaloniaPropertyChangedEventArgs> CoreChanges { get; } = new();
public static int CoerceFoo(AvaloniaObject instance, int value)
{
return Math.Min(((Class1)instance).MaxFoo, value);
var o = (Class1)instance;
return Math.Clamp(value, o.MinFoo, o.MaxFoo);
}
protected override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)
@ -266,5 +378,51 @@ namespace Avalonia.Base.UnitTests
return -value;
}
}
private class Class3: AvaloniaObject
{
public static readonly StyledProperty<int> FooProperty =
AvaloniaProperty.Register<Class3, int>(
"Foo",
defaultValue: 11,
coerce: CoerceFoo);
public int Foo
{
get => GetValue(FooProperty);
set => SetValue(FooProperty, value);
}
public static int CoerceFoo(AvaloniaObject instance, int value)
{
var o = (Class3)instance;
return Math.Clamp(value, 50, 100);
}
}
private class Control1 : Control
{
public static readonly StyledProperty<int> FooProperty =
AvaloniaProperty.Register<Control1, int>(
"Foo",
defaultValue: 11,
coerce: CoerceFoo);
public int Foo
{
get => GetValue(FooProperty);
set => SetValue(FooProperty, value);
}
public int MinFoo { get; set; } = 0;
public int MaxFoo { get; set; } = 100;
public static int CoerceFoo(AvaloniaObject instance, int value)
{
var o = (Control1)instance;
return Math.Clamp(value, o.MinFoo, o.MaxFoo);
}
}
}
}

5
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@ -179,6 +179,11 @@ namespace Avalonia.Base.UnitTests
throw new NotImplementedException();
}
internal override void RouteCoerceDefaultValue(AvaloniaObject o)
{
throw new NotImplementedException();
}
internal override object RouteGetValue(AvaloniaObject o)
{
throw new NotImplementedException();

18
tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs

@ -41,6 +41,24 @@ namespace Avalonia.Base.UnitTests.Layout
Assert.False(control.Arranged);
}
[Fact]
public void Doesnt_Measure_And_Arrange_InvalidateMeasured_Control_When_Ancestor_Is_Not_Visible()
{
var control = new LayoutTestControl();
var parent = new Decorator { Child = control };
var root = new LayoutTestRoot { Child = parent };
root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = control.Arranged = false;
parent.IsVisible = false;
control.InvalidateMeasure();
root.LayoutManager.ExecuteLayoutPass();
Assert.False(control.Measured);
Assert.False(control.Arranged);
}
[Fact]
public void Arranges_InvalidateArranged_Control()
{

2
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -334,6 +334,7 @@ namespace Avalonia.Controls.UnitTests
};
var window = new Window { Content = menu };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
topLevelMenu.IsSubMenuOpen = true;
@ -371,6 +372,7 @@ namespace Avalonia.Controls.UnitTests
};
var window = new Window { Content = menu };
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
var panel = Assert.IsType<StackPanel>(menu.Presenter.Panel);

1
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -1116,6 +1116,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
private static Window PreparedWindow(object content = null)
{
var w = new Window { Content = content };
w.Show();
w.ApplyStyling();
w.ApplyTemplate();
return w;

4
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs

@ -73,14 +73,14 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'>
<DockPanel>
<TabStrip Name='strip' DockPanel.Dock='Top' Items='{Binding Items}' SelectedIndex='0'>
<TabStrip Name='strip' DockPanel.Dock='Top' ItemsSource='{Binding Items}' SelectedIndex='0'>
<TabStrip.ItemTemplate>
<DataTemplate>
<TextBlock Text='{Binding Header}'/>
</DataTemplate>
</TabStrip.ItemTemplate>
</TabStrip>
<Carousel Name='carousel' Items='{Binding Items}' SelectedIndex='{Binding #strip.SelectedIndex}'>
<Carousel Name='carousel' ItemsSource='{Binding Items}' SelectedIndex='{Binding #strip.SelectedIndex}'>
<Carousel.ItemTemplate>
<DataTemplate>
<TextBlock Text='{Binding Detail}'/>

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs

@ -27,6 +27,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = Assert.IsType<TestTemplatedControl>(window.Content);
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.NotNull(button.Template);
@ -63,6 +64,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = Assert.IsType<TestTemplatedControl>(window.Content);
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.NotNull(button.Template);

27
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -994,6 +994,33 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_GetTextBounds_With_EndOfParagraph_RightToLeft()
{
var text = "لوحة المفاتيح العربية";
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new SingleBufferTextSource(text, defaultProperties, true);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left,
true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
var textBounds = textLine.GetTextBounds(0, 1);
Assert.Equal(1, textBounds.Count);
var firstBounds = textBounds.First();
Assert.True(firstBounds.TextRunBounds.Count > 0);
}
}
private class FixedRunsTextSource : ITextSource
{
private readonly IReadOnlyList<TextRun> _textRuns;

Loading…
Cancel
Save