Browse Source

Merge branch 'master' into expose-Ensure-Capacity

pull/6220/head
Dariusz Komosiński 5 years ago
committed by GitHub
parent
commit
d0a4afff67
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitignore
  2. 43
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  3. 15
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  4. 32
      src/Avalonia.Base/Utilities/MathUtilities.cs
  5. 30
      src/Avalonia.Controls/ApiCompatBaseline.txt
  6. 69
      src/Avalonia.Controls/ComboBox.cs
  7. 1
      src/Avalonia.Controls/ItemsControl.cs
  8. 148
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  9. 6
      src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs
  10. 74
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  11. 103
      src/Avalonia.Controls/Shapes/Arc.cs
  12. 2
      src/Avalonia.Controls/TextBox.cs
  13. 39
      src/Avalonia.Controls/Window.cs
  14. 3
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  15. 3
      src/Avalonia.Visuals/Media/Brush.cs
  16. 11
      src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs
  17. 14
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  18. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  19. 136
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  20. 20
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
  21. 2
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs
  22. 20
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
  23. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  24. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

3
.gitignore

@ -106,6 +106,9 @@ _NCrunch_*/
*.ncrunchsolution.user
nCrunchTemp_*
# CodeRush
.cr/
# Others
sql/
*.Cache

43
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -2,7 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="ControlCatalog.Pages.NumericUpDownPage">
<StackPanel Orientation="Vertical" Spacing="4">
<StackPanel Orientation="Vertical" Spacing="4"
MaxWidth="800">
<TextBlock Margin="2" Classes="h1">Numeric up-down control</TextBlock>
<TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock>
@ -68,24 +69,36 @@
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center"
Margin="2" HorizontalAlignment="Center"/>
</Grid>
</Grid>
<StackPanel Margin="2,10,2,2" Orientation="Horizontal" Spacing="10">
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of NumericUpDown:</TextBlock>
<NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5"
CultureInfo="en-US" VerticalAlignment="Center"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
<WrapPanel Margin="2,10,2,2">
<StackPanel Orientation="Vertical" Margin="10">
<Label Target="upDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of decimal NumericUpDown:</Label>
<NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5"
CultureInfo="en-US" VerticalAlignment="Center" Value="{Binding DecimalValue}"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="10">
<Label Target="DoubleUpDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of double NumericUpDown:</Label>
<NumericUpDown Name="DoubleUpDown" Minimum="0" Maximum="10" Increment="0.5"
CultureInfo="en-US" VerticalAlignment="Center" Value="{Binding DoubleValue}"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
</StackPanel>
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">NumericUpDown with Validation Errors:</TextBlock>
<NumericUpDown Minimum="0" Maximum="10" Increment="0.5"
CultureInfo="en-US" VerticalAlignment="Center"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}">
<DataValidationErrors.Error>
<sys:Exception />
</DataValidationErrors.Error>
</NumericUpDown>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="10">
<Label Target="ValidationUpDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">NumericUpDown with Validation Errors:</Label>
<NumericUpDown x:Name="ValidationUpDown" Minimum="0" Maximum="10" Increment="0.5"
CultureInfo="en-US" VerticalAlignment="Center"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}">
<DataValidationErrors.Error>
<sys:Exception />
</DataValidationErrors.Error>
</NumericUpDown>
</StackPanel>
</WrapPanel>
</StackPanel>
</UserControl>

15
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@ -32,11 +32,26 @@ namespace ControlCatalog.Pages
private FormatObject _selectedFormat;
private IList<Location> _spinnerLocations;
private double _doubleValue;
private decimal _decimalValue;
public NumbersPageViewModel()
{
SelectedFormat = Formats.FirstOrDefault();
}
public double DoubleValue
{
get { return _doubleValue; }
set { this.RaiseAndSetIfChanged(ref _doubleValue, value); }
}
public decimal DecimalValue
{
get { return _decimalValue; }
set { this.RaiseAndSetIfChanged(ref _decimalValue, value); }
}
public IList<FormatObject> Formats
{
get

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

@ -224,6 +224,34 @@ namespace Avalonia.Utilities
}
}
/// <summary>
/// Clamps a value between a minimum and maximum value.
/// </summary>
/// <param name="val">The value.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static decimal Clamp(decimal val, decimal min, decimal max)
{
if (min > max)
{
ThrowCannotBeGreaterThanException(min, max);
}
if (val < min)
{
return min;
}
else if (val > max)
{
return max;
}
else
{
return val;
}
}
/// <summary>
/// Clamps a value between a minimum and maximum value.
/// </summary>
@ -281,8 +309,8 @@ namespace Avalonia.Utilities
{
return angle * 2 * Math.PI;
}
private static void ThrowCannotBeGreaterThanException(double min, double max)
private static void ThrowCannotBeGreaterThanException<T>(T min, T max)
{
throw new ArgumentException($"{min} cannot be greater than {max}.");
}

30
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -4,14 +4,38 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.DirectProperty<Avalonia.Controls.NumericUpDown, System.Double> Avalonia.DirectProperty<Avalonia.Controls.NumericUpDown, System.Double> Avalonia.Controls.NumericUpDown.ValueProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.NumericUpDown.IncrementProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.NumericUpDown.MaximumProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.NumericUpDown.MinimumProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Increment.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Increment.set(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Maximum.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Maximum.set(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Minimum.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Minimum.set(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceIncrement(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceMaximum(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceMinimum(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceValue(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnIncrementChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnMaximumChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnMinimumChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnValueChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.RaiseValueChangedEvent(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Value.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Value.set(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDownValueChangedEventArgs..ctor(Avalonia.Interactivity.RoutedEvent, System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
Total Issues: 15
Total Issues: 39

69
src/Avalonia.Controls/ComboBox.cs

@ -77,14 +77,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
/// <summary>
/// Defines the <see cref="IsTextSearchEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsTextSearchEnabledProperty =
AvaloniaProperty.Register<ComboBox, bool>(nameof(IsTextSearchEnabled), true);
private string _textSearchTerm = string.Empty;
private DispatcherTimer _textSearchTimer;
private bool _isDropDownOpen;
private Popup _popup;
private object _selectionBoxItem;
@ -173,15 +165,6 @@ namespace Avalonia.Controls
set { SetValue(VerticalContentAlignmentProperty, value); }
}
/// <summary>
/// Gets or sets a value that specifies whether a user can jump to a value by typing.
/// </summary>
public bool IsTextSearchEnabled
{
get { return GetValue(IsTextSearchEnabledProperty); }
set { SetValue(IsTextSearchEnabledProperty, value); }
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
@ -247,32 +230,6 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc />
protected override void OnTextInput(TextInputEventArgs e)
{
if (!IsTextSearchEnabled || e.Handled)
return;
StopTextSearchTimer();
_textSearchTerm += e.Text;
bool match(ItemContainerInfo info) =>
info.ContainerControl is IContentControl control &&
control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
var info = ItemContainerGenerator.Containers.FirstOrDefault(match);
if (info != null)
{
SelectedIndex = info.Index;
}
StartTextSearchTimer();
e.Handled = true;
}
/// <inheritdoc/>
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
@ -470,31 +427,5 @@ namespace Avalonia.Controls
SelectedIndex = prev;
}
private void StartTextSearchTimer()
{
_textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_textSearchTimer.Tick += TextSearchTimer_Tick;
_textSearchTimer.Start();
}
private void StopTextSearchTimer()
{
if (_textSearchTimer == null)
{
return;
}
_textSearchTimer.Stop();
_textSearchTimer.Tick -= TextSearchTimer_Tick;
_textSearchTimer = null;
}
private void TextSearchTimer_Tick(object sender, EventArgs e)
{
_textSearchTerm = string.Empty;
StopTextSearchTimer();
}
}
}

1
src/Avalonia.Controls/ItemsControl.cs

@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;

148
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -45,10 +45,18 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="CultureInfo"/> property.
/// </summary>
[Obsolete]
public static readonly DirectProperty<NumericUpDown, CultureInfo> CultureInfoProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, CultureInfo>(nameof(CultureInfo), o => o.CultureInfo,
(o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture);
/// <summary>
/// Defines the <see cref="NumberFormat"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, NumberFormatInfo> NumberFormatProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, NumberFormatInfo>(nameof(NumberFormat), o => o.NumberFormat,
(o, v) => o.NumberFormat = v, NumberFormatInfo.CurrentInfo);
/// <summary>
/// Defines the <see cref="FormatString"/> property.
/// </summary>
@ -58,8 +66,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Increment"/> property.
/// </summary>
public static readonly StyledProperty<double> IncrementProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Increment), 1.0d, coerce: OnCoerceIncrement);
public static readonly StyledProperty<decimal> IncrementProperty =
AvaloniaProperty.Register<NumericUpDown, decimal>(nameof(Increment), 1.0m, coerce: OnCoerceIncrement);
/// <summary>
/// Defines the <see cref="IsReadOnly"/> property.
@ -70,14 +78,14 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Maximum"/> property.
/// </summary>
public static readonly StyledProperty<double> MaximumProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Maximum), double.MaxValue, coerce: OnCoerceMaximum);
public static readonly StyledProperty<decimal> MaximumProperty =
AvaloniaProperty.Register<NumericUpDown, decimal>(nameof(Maximum), decimal.MaxValue, coerce: OnCoerceMaximum);
/// <summary>
/// Defines the <see cref="Minimum"/> property.
/// </summary>
public static readonly StyledProperty<double> MinimumProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Minimum), double.MinValue, coerce: OnCoerceMinimum);
public static readonly StyledProperty<decimal> MinimumProperty =
AvaloniaProperty.Register<NumericUpDown, decimal>(nameof(Minimum), decimal.MinValue, coerce: OnCoerceMinimum);
/// <summary>
/// Defines the <see cref="ParsingNumberStyle"/> property.
@ -96,8 +104,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Value"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, double> ValueProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, double>(nameof(Value), updown => updown.Value,
public static readonly DirectProperty<NumericUpDown, decimal> ValueProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, decimal>(nameof(Value), updown => updown.Value,
(updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
/// <summary>
@ -106,7 +114,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<string> WatermarkProperty =
AvaloniaProperty.Register<NumericUpDown, string>(nameof(Watermark));
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
@ -121,7 +128,7 @@ namespace Avalonia.Controls
private IDisposable _textBoxTextChangedSubscription;
private double _value;
private decimal _value;
private string _text;
private bool _internalValueSet;
private bool _clipValueToMinMax;
@ -129,7 +136,8 @@ namespace Avalonia.Controls
private bool _isTextChangedFromUI;
private CultureInfo _cultureInfo;
private NumberStyles _parsingNumberStyle = NumberStyles.Any;
private NumberFormatInfo _numberFormat;
/// <summary>
/// Gets the Spinner template part.
/// </summary>
@ -179,10 +187,25 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the current CultureInfo.
/// </summary>
[Obsolete("CultureInfo is obsolete, please use NumberFormat instead.")]
public CultureInfo CultureInfo
{
get { return _cultureInfo; }
set { SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); }
set
{
SetAndRaise(CultureInfoProperty, ref _cultureInfo, value);
//Set and Raise the NumberFormatProperty when CultureInfo is changed.
SetAndRaise(NumberFormatProperty, ref _numberFormat, value?.NumberFormat);
}
}
/// <summary>
/// Gets or sets the current NumberFormatInfo
/// </summary>
public NumberFormatInfo NumberFormat
{
get { return _numberFormat; }
set { SetAndRaise(NumberFormatProperty, ref _numberFormat, value); }
}
/// <summary>
@ -197,7 +220,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the amount in which to increment the <see cref="Value"/>.
/// </summary>
public double Increment
public decimal Increment
{
get { return GetValue(IncrementProperty); }
set { SetValue(IncrementProperty, value); }
@ -215,7 +238,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the maximum allowed value.
/// </summary>
public double Maximum
public decimal Maximum
{
get { return GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
@ -224,7 +247,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the minimum allowed value.
/// </summary>
public double Minimum
public decimal Minimum
{
get { return GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
@ -251,7 +274,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the value.
/// </summary>
public double Value
public decimal Value
{
get { return _value; }
set
@ -270,7 +293,6 @@ namespace Avalonia.Controls
set { SetValue(WatermarkProperty, value); }
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
@ -311,6 +333,7 @@ namespace Avalonia.Controls
static NumericUpDown()
{
CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
NumberFormatProperty.Changed.Subscribe(OnNumberFormatChanged);
FormatStringProperty.Changed.Subscribe(FormatStringChanged);
IncrementProperty.Changed.Subscribe(IncrementChanged);
IsReadOnlyProperty.Changed.Subscribe(OnIsReadOnlyChanged);
@ -397,6 +420,19 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="NumberFormat"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnNumberFormatChanged(NumberFormatInfo oldValue, NumberFormatInfo newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(false, null);
}
}
/// <summary>
/// Called when the <see cref="FormatString"/> property value changed.
/// </summary>
@ -415,7 +451,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnIncrementChanged(double oldValue, double newValue)
protected virtual void OnIncrementChanged(decimal oldValue, decimal newValue)
{
if (IsInitialized)
{
@ -438,7 +474,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnMaximumChanged(double oldValue, double newValue)
protected virtual void OnMaximumChanged(decimal oldValue, decimal newValue)
{
if (IsInitialized)
{
@ -455,7 +491,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnMinimumChanged(double oldValue, double newValue)
protected virtual void OnMinimumChanged(decimal oldValue, decimal newValue)
{
if (IsInitialized)
{
@ -485,7 +521,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnValueChanged(double oldValue, double newValue)
protected virtual void OnValueChanged(decimal oldValue, decimal newValue)
{
if (!_internalValueSet && IsInitialized)
{
@ -501,7 +537,7 @@ namespace Avalonia.Controls
/// Called when the <see cref="Increment"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceIncrement(double baseValue)
protected virtual decimal OnCoerceIncrement(decimal baseValue)
{
return baseValue;
}
@ -510,7 +546,7 @@ namespace Avalonia.Controls
/// Called when the <see cref="Maximum"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceMaximum(double baseValue)
protected virtual decimal OnCoerceMaximum(decimal baseValue)
{
return Math.Max(baseValue, Minimum);
}
@ -519,7 +555,7 @@ namespace Avalonia.Controls
/// Called when the <see cref="Minimum"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceMinimum(double baseValue)
protected virtual decimal OnCoerceMinimum(decimal baseValue)
{
return Math.Min(baseValue, Maximum);
}
@ -528,7 +564,7 @@ namespace Avalonia.Controls
/// Called when the <see cref="Value"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceValue(double baseValue)
protected virtual decimal OnCoerceValue(decimal baseValue)
{
return baseValue;
}
@ -562,7 +598,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void RaiseValueChangedEvent(double oldValue, double newValue)
protected virtual void RaiseValueChangedEvent(decimal oldValue, decimal newValue)
{
var e = new NumericUpDownValueChangedEventArgs(ValueChangedEvent, oldValue, newValue);
RaiseEvent(e);
@ -571,9 +607,9 @@ namespace Avalonia.Controls
/// <summary>
/// Converts the formatted text to a value.
/// </summary>
private double ConvertTextToValue(string text)
private decimal ConvertTextToValue(string text)
{
double result = 0;
decimal result = 0;
if (string.IsNullOrEmpty(text))
{
@ -609,10 +645,10 @@ namespace Avalonia.Controls
//Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
if (FormatString.Contains("{0"))
{
return string.Format(CultureInfo, FormatString, Value);
return string.Format(NumberFormat, FormatString, Value);
}
return Value.ToString(FormatString, CultureInfo);
return Value.ToString(FormatString, NumberFormat);
}
/// <summary>
@ -674,6 +710,20 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="NumberFormat"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnNumberFormatChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (NumberFormatInfo)e.OldValue;
var newValue = (NumberFormatInfo)e.NewValue;
upDown.OnNumberFormatChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Increment"/> property value changed.
/// </summary>
@ -682,8 +732,8 @@ namespace Avalonia.Controls
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
var oldValue = (decimal)e.OldValue;
var newValue = (decimal)e.NewValue;
upDown.OnIncrementChanged(oldValue, newValue);
}
}
@ -724,8 +774,8 @@ namespace Avalonia.Controls
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
var oldValue = (decimal)e.OldValue;
var newValue = (decimal)e.NewValue;
upDown.OnMaximumChanged(oldValue, newValue);
}
}
@ -738,8 +788,8 @@ namespace Avalonia.Controls
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
var oldValue = (decimal)e.OldValue;
var newValue = (decimal)e.NewValue;
upDown.OnMinimumChanged(oldValue, newValue);
}
}
@ -766,13 +816,13 @@ namespace Avalonia.Controls
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
var oldValue = (decimal)e.OldValue;
var newValue = (decimal)e.NewValue;
upDown.OnValueChanged(oldValue, newValue);
}
}
private void SetValueInternal(double value)
private void SetValueInternal(decimal value)
{
_internalValueSet = true;
try
@ -785,7 +835,7 @@ namespace Avalonia.Controls
}
}
private static double OnCoerceMaximum(IAvaloniaObject instance, double value)
private static decimal OnCoerceMaximum(IAvaloniaObject instance, decimal value)
{
if (instance is NumericUpDown upDown)
{
@ -795,7 +845,7 @@ namespace Avalonia.Controls
return value;
}
private static double OnCoerceMinimum(IAvaloniaObject instance, double value)
private static decimal OnCoerceMinimum(IAvaloniaObject instance, decimal value)
{
if (instance is NumericUpDown upDown)
{
@ -805,7 +855,7 @@ namespace Avalonia.Controls
return value;
}
private static double OnCoerceIncrement(IAvaloniaObject instance, double value)
private static decimal OnCoerceIncrement(IAvaloniaObject instance, decimal value)
{
if (instance is NumericUpDown upDown)
{
@ -977,23 +1027,23 @@ namespace Avalonia.Controls
return parsedTextIsValid;
}
private double ConvertTextToValueCore(string currentValueText, string text)
private decimal ConvertTextToValueCore(string currentValueText, string text)
{
double result;
decimal result;
if (IsPercent(FormatString))
{
result = decimal.ToDouble(ParsePercent(text, CultureInfo));
result = ParsePercent(text, NumberFormat);
}
else
{
// Problem while converting new text
if (!double.TryParse(text, ParsingNumberStyle, CultureInfo, out var outputValue))
if (!decimal.TryParse(text, ParsingNumberStyle, NumberFormat, out var outputValue))
{
var shouldThrow = true;
// Check if CurrentValueText is also failing => it also contains special characters. ex : 90°
if (!double.TryParse(currentValueText, ParsingNumberStyle, CultureInfo, out var _))
if (!decimal.TryParse(currentValueText, ParsingNumberStyle, NumberFormat, out var _))
{
// extract non-digit characters
var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c));
@ -1006,7 +1056,7 @@ namespace Avalonia.Controls
text = text.Replace(character.ToString(), string.Empty);
}
// if without the special characters, parsing is good, do not throw
if (double.TryParse(text, ParsingNumberStyle, CultureInfo, out outputValue))
if (decimal.TryParse(text, ParsingNumberStyle, NumberFormat, out outputValue))
{
shouldThrow = false;
}
@ -1023,7 +1073,7 @@ namespace Avalonia.Controls
return result;
}
private void ValidateMinMax(double value)
private void ValidateMinMax(decimal value)
{
if (value < Minimum)
{

6
src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs

@ -4,13 +4,13 @@ namespace Avalonia.Controls
{
public class NumericUpDownValueChangedEventArgs : RoutedEventArgs
{
public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, double oldValue, double newValue) : base(routedEvent)
public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, decimal oldValue, decimal newValue) : base(routedEvent)
{
OldValue = oldValue;
NewValue = newValue;
}
public double OldValue { get; }
public double NewValue { get; }
public decimal OldValue { get; }
public decimal NewValue { get; }
}
}

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

@ -10,6 +10,7 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.VisualTree;
#nullable enable
@ -91,6 +92,12 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register<SelectingItemsControl, SelectionMode>(
nameof(SelectionMode));
/// <summary>
/// Defines the <see cref="IsTextSearchEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsTextSearchEnabledProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(IsTextSearchEnabled), true);
/// <summary>
/// Event that should be raised by items that implement <see cref="ISelectable"/> to
/// notify the parent <see cref="SelectingItemsControl"/> that their selection state
@ -110,6 +117,8 @@ namespace Avalonia.Controls.Primitives
RoutingStrategies.Bubble);
private static readonly IList Empty = Array.Empty<object>();
private string _textSearchTerm = string.Empty;
private DispatcherTimer? _textSearchTimer;
private ISelectionModel? _selection;
private int _oldSelectedIndex;
private object? _oldSelectedItem;
@ -305,6 +314,15 @@ namespace Avalonia.Controls.Primitives
}
}
/// <summary>
/// Gets or sets a value that specifies whether a user can jump to a value by typing.
/// </summary>
public bool IsTextSearchEnabled
{
get { return GetValue(IsTextSearchEnabledProperty); }
set { SetValue(IsTextSearchEnabledProperty, value); }
}
/// <summary>
/// Gets or sets the selection mode.
/// </summary>
@ -490,6 +508,36 @@ namespace Avalonia.Controls.Primitives
}
}
protected override void OnTextInput(TextInputEventArgs e)
{
if (!e.Handled)
{
if (!IsTextSearchEnabled)
return;
StopTextSearchTimer();
_textSearchTerm += e.Text;
bool match(ItemContainerInfo info) =>
info.ContainerControl is IContentControl control &&
control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
var info = ItemContainerGenerator.Containers.FirstOrDefault(match);
if (info != null)
{
SelectedIndex = info.Index;
}
StartTextSearchTimer();
e.Handled = true;
}
base.OnTextInput(e);
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
@ -962,6 +1010,32 @@ namespace Avalonia.Controls.Primitives
}
}
private void StartTextSearchTimer()
{
_textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_textSearchTimer.Tick += TextSearchTimer_Tick;
_textSearchTimer.Start();
}
private void StopTextSearchTimer()
{
if (_textSearchTimer == null)
{
return;
}
_textSearchTimer.Tick -= TextSearchTimer_Tick;
_textSearchTimer.Stop();
_textSearchTimer = null;
}
private void TextSearchTimer_Tick(object sender, EventArgs e)
{
_textSearchTerm = string.Empty;
StopTextSearchTimer();
}
// When in a BeginInit..EndInit block, or when the DataContext is updating, we need to
// defer changes to the selection model because we have no idea in which order properties
// will be set. Consider:

103
src/Avalonia.Controls/Shapes/Arc.cs

@ -0,0 +1,103 @@
using System;
using Avalonia.Media;
namespace Avalonia.Controls.Shapes
{
public class Arc : Shape
{
/// <summary>
/// Defines the <see cref="StartAngle"/> property.
/// </summary>
public static readonly StyledProperty<double> StartAngleProperty =
AvaloniaProperty.Register<Arc, double>(nameof(StartAngle), 0.0);
/// <summary>
/// Defines the <see cref="SweepAngle"/> property.
/// </summary>
public static readonly StyledProperty<double> SweepAngleProperty =
AvaloniaProperty.Register<Arc, double>(nameof(SweepAngle), 0.0);
static Arc()
{
StrokeThicknessProperty.OverrideDefaultValue<Arc>(1);
AffectsGeometry<Arc>(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, SweepAngleProperty);
}
/// <summary>
/// Gets or sets the angle at which the arc starts, in degrees.
/// </summary>
public double StartAngle
{
get => GetValue(StartAngleProperty);
set => SetValue(StartAngleProperty, value);
}
/// <summary>
/// Gets or sets the angle, in degrees, added to the <see cref="StartAngle"/> defining where the arc ends.
/// A positive value is clockwise, negative is counter-clockwise.
/// </summary>
public double SweepAngle
{
get => GetValue(SweepAngleProperty);
set => SetValue(SweepAngleProperty, value);
}
protected override Geometry CreateDefiningGeometry()
{
var angle1 = DegreesToRad(StartAngle);
var angle2 = angle1 + DegreesToRad(SweepAngle);
var startAngle = Math.Min(angle1, angle2);
var sweepAngle = Math.Max(angle1, angle2);
var normStart = RadToNormRad(startAngle);
var normEnd = RadToNormRad(sweepAngle);
var rect = new Rect(Bounds.Size);
if ((normStart == normEnd) && (startAngle != sweepAngle)) // Complete ring.
{
return new EllipseGeometry(rect.Deflate(StrokeThickness / 2));
}
else if (SweepAngle == 0)
{
return new StreamGeometry();
}
else // Partial arc.
{
var deflatedRect = rect.Deflate(StrokeThickness / 2);
var centerX = rect.Center.X;
var centerY = rect.Center.Y;
var radiusX = deflatedRect.Width / 2;
var radiusY = deflatedRect.Height / 2;
var angleGap = RadToNormRad(sweepAngle - startAngle);
var startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle);
var endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle);
var arcGeometry = new StreamGeometry();
using (var ctx = arcGeometry.Open())
{
ctx.BeginFigure(startPoint, false);
ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI,
SweepDirection.Clockwise);
ctx.EndFigure(false);
}
return arcGeometry;
}
}
static double DegreesToRad(double inAngle) =>
inAngle * Math.PI / 180;
static double RadToNormRad(double inAngle) => ((inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2);
static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) =>
new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY);
}
}

2
src/Avalonia.Controls/TextBox.cs

@ -145,7 +145,7 @@ namespace Avalonia.Controls
(o, v) => o.UndoLimit = v,
unsetValue: -1);
struct UndoRedoState : IEquatable<UndoRedoState>
readonly struct UndoRedoState : IEquatable<UndoRedoState>
{
public string Text { get; }
public int CaretPosition { get; }

39
src/Avalonia.Controls/Window.cs

@ -592,6 +592,14 @@ namespace Avalonia.Controls
owner.RemoveChild(this);
}
if (_children.Count > 0)
{
foreach (var child in _children.ToArray())
{
child.child.Hide();
}
}
Owner = null;
PlatformImpl?.Hide();
@ -635,6 +643,22 @@ namespace Avalonia.Controls
throw new InvalidOperationException("Cannot re-show a closed window.");
}
if (parent != null)
{
if (parent.PlatformImpl == null)
{
throw new InvalidOperationException("Cannot show a window with a closed parent.");
}
else if (parent == this)
{
throw new InvalidOperationException("A Window cannot be its own parent.");
}
else if (!parent.IsVisible)
{
throw new InvalidOperationException("Cannot show window with non-visible parent.");
}
}
if (IsVisible)
{
return;
@ -708,11 +732,22 @@ namespace Avalonia.Controls
{
throw new ArgumentNullException(nameof(owner));
}
if (IsVisible)
else if (owner.PlatformImpl == null)
{
throw new InvalidOperationException("Cannot show a window with a closed owner.");
}
else if (owner == this)
{
throw new InvalidOperationException("A Window cannot be its own owner.");
}
else if (IsVisible)
{
throw new InvalidOperationException("The window is already being shown.");
}
else if (!owner.IsVisible)
{
throw new InvalidOperationException("Cannot show window with non-visible parent.");
}
RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));

3
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -5,6 +5,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract.
CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
@ -73,4 +74,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 74
Total Issues: 75

3
src/Avalonia.Visuals/Media/Brush.cs

@ -2,6 +2,7 @@ using System;
using System.ComponentModel;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
@ -47,7 +48,7 @@ namespace Avalonia.Media
if (s[0] == '#')
{
return new SolidColorBrush(Color.Parse(s));
return new ImmutableSolidColorBrush(Color.Parse(s));
}
var brush = KnownColors.GetKnownBrush(s);

11
src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs

@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable
/// <summary>
/// Fills an area with a solid color.
/// </summary>
public readonly struct ImmutableSolidColorBrush : ISolidColorBrush, IEquatable<ImmutableSolidColorBrush>
public class ImmutableSolidColorBrush : ISolidColorBrush, IEquatable<ImmutableSolidColorBrush>
{
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableSolidColorBrush"/> class.
@ -48,8 +48,9 @@ namespace Avalonia.Media.Immutable
public bool Equals(ImmutableSolidColorBrush other)
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
return Color == other.Color && Opacity == other.Opacity;
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Color.Equals(other.Color) && Opacity.Equals(other.Opacity);
}
public override bool Equals(object obj)
@ -67,12 +68,12 @@ namespace Avalonia.Media.Immutable
public static bool operator ==(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right)
{
return left.Equals(right);
return Equals(left, right);
}
public static bool operator !=(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right)
{
return !left.Equals(right);
return !Equals(left, right);
}
/// <summary>

14
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@ -207,6 +207,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return true;
}
if (types.IBrush.IsAssignableFrom(type))
{
if (Color.TryParse(text, out Color color))
{
var brushTypeRef = new XamlAstClrTypeReference(node, types.ImmutableSolidColorBrush, false);
result = new XamlAstNewClrObjectNode(node, brushTypeRef,
types.ImmutableSolidColorBrushConstructorColor,
new List<IXamlAstValueNode> { new XamlConstantNode(node, types.UInt, color.ToUint32()) });
return true;
}
}
result = null;
return false;
}

9
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -80,7 +80,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ColumnDefinitions { get; }
public IXamlType Classes { get; }
public IXamlMethod ClassesBindMethod { get; }
public IXamlProperty StyledElementClassesProperty { get; set; }
public IXamlProperty StyledElementClassesProperty { get; }
public IXamlType IBrush { get; }
public IXamlType ImmutableSolidColorBrush { get; }
public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
@ -178,6 +181,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.FindMethod( "BindClass", IDisposable, false, IStyledElement,
cfg.WellKnownTypes.String,
IBinding, cfg.WellKnownTypes.Object);
IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush");
ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush");
ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List<IXamlType> { UInt });
}
}

136
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -279,10 +279,11 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = Mock.Of<Window>();
var parent = new Window();
var renderer = new Mock<IRenderer>();
var target = new Window(CreateImpl(renderer));
parent.Show();
target.ShowDialog<object>(parent);
renderer.Verify(x => x.Start(), Times.Once);
@ -294,10 +295,11 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = Mock.Of<Window>();
var parent = new Window();
var target = new Window();
var raised = false;
parent.Show();
target.Opened += (s, e) => raised = true;
target.ShowDialog<object>(parent);
@ -326,14 +328,15 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Mock<Window>();
var parent = new Window();
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
parent.Show();
var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(parent.Object);
var task = target.ShowDialog<bool>(parent);
windowImpl.Object.Closed();
@ -366,14 +369,16 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Mock<Window>();
var parent = new Window();
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
parent.Show();
var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(parent.Object);
var task = target.ShowDialog<bool>(parent);
windowImpl.Object.Closed();
await task;
@ -381,12 +386,128 @@ namespace Avalonia.Controls.UnitTests
var openedRaised = false;
target.Opened += (s, e) => openedRaised = true;
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog<bool>(parent.Object));
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog<bool>(parent));
Assert.Equal("Cannot re-show a closed window.", ex.Message);
Assert.False(openedRaised);
}
}
[Fact]
public void Calling_Show_With_Closed_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
var target = new Window();
parent.Close();
var ex = Assert.Throws<InvalidOperationException>(() => target.Show(parent));
Assert.Equal("Cannot show a window with a closed parent.", ex.Message);
}
}
[Fact]
public async Task Calling_ShowDialog_With_Closed_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
var target = new Window();
parent.Close();
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog(parent));
Assert.Equal("Cannot show a window with a closed owner.", ex.Message);
}
}
[Fact]
public void Calling_Show_With_Invisible_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
var target = new Window();
var ex = Assert.Throws<InvalidOperationException>(() => target.Show(parent));
Assert.Equal("Cannot show window with non-visible parent.", ex.Message);
}
}
[Fact]
public async Task Calling_ShowDialog_With_Invisible_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
var target = new Window();
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog(parent));
Assert.Equal("Cannot show window with non-visible parent.", ex.Message);
}
}
[Fact]
public void Calling_Show_With_Self_As_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new Window();
var ex = Assert.Throws<InvalidOperationException>(() => target.Show(target));
Assert.Equal("A Window cannot be its own parent.", ex.Message);
}
}
[Fact]
public async Task Calling_ShowDialog_With_Self_As_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new Window();
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog(target));
Assert.Equal("A Window cannot be its own owner.", ex.Message);
}
}
[Fact]
public void Hiding_Parent_Window_Should_Close_Children()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var parent = new Window();
var child = new Window();
parent.Show();
child.Show(parent);
parent.Hide();
Assert.False(parent.IsVisible);
Assert.False(child.IsVisible);
}
}
[Fact]
public void Hiding_Parent_Window_Should_Close_Dialog_Children()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var parent = new Window();
var child = new Window();
parent.Show();
child.ShowDialog(parent);
parent.Hide();
Assert.False(parent.IsVisible);
Assert.False(child.IsVisible);
}
}
[Fact]
public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen()
{
@ -686,6 +807,7 @@ namespace Avalonia.Controls.UnitTests
protected override void Show(Window window)
{
var owner = new Window();
owner.Show();
window.ShowDialog(owner);
}
}

20
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

@ -33,7 +33,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -80,7 +80,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -108,7 +108,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -140,7 +140,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -212,7 +212,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
var brush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -241,7 +241,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
var brush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -275,7 +275,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = window.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -317,7 +317,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.Show();
var border = (Border)button.GetVisualChildren().Single();
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -648,7 +648,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
window.Content = null;
@ -657,7 +657,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.Content = border;
brush = (SolidColorBrush)border.Background;
brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}

2
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs

@ -39,7 +39,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}

20
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -119,7 +119,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -149,7 +149,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -200,7 +200,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
var brush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -229,7 +229,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
var brush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -263,7 +263,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = window.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -344,7 +344,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.Show();
var border = (Border)button.GetVisualChildren().Single();
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
@ -484,12 +484,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
userControl.Content = null;
brush = (SolidColorBrush)border.Background;
brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}

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

@ -512,7 +512,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
style.TryGetResource("Brush", out var brush);
Assert.NotNull(brush);
Assert.IsType<SolidColorBrush>(brush);
Assert.IsAssignableFrom<ISolidColorBrush>(brush);
Assert.Equal(Colors.White, ((ISolidColorBrush)brush).Color);
style.TryGetResource("Double", out var d);

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

@ -101,7 +101,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
</UserControl.Styles>
</UserControl>";
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var brush = (SolidColorBrush)((Style)userControl.Styles[0]).Resources["brush"];
var brush = (ISolidColorBrush)((Style)userControl.Styles[0]).Resources["brush"];
Assert.Equal(0xff506070, brush.Color.ToUint32());
}

Loading…
Cancel
Save