Browse Source

Merge branch 'master' into bindingoperations-donothing

pull/2641/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
895e58a740
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitmodules
  2. 1
      Avalonia.sln
  3. 4
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  4. 3
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  5. 4
      samples/VirtualizationDemo/MainWindow.xaml
  6. 7
      samples/VirtualizationDemo/ViewModels/ItemViewModel.cs
  7. 18
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  8. 11
      src/Avalonia.Build.Tasks/Program.cs
  9. 23
      src/Avalonia.Controls/Button.cs
  10. 4
      src/Avalonia.Controls/ColumnDefinitions.cs
  11. 2
      src/Avalonia.Controls/ComboBox.cs
  12. 323
      src/Avalonia.Controls/DefinitionBase.cs
  13. 7
      src/Avalonia.Controls/DefinitionList.cs
  14. 953
      src/Avalonia.Controls/Grid.cs
  15. 36
      src/Avalonia.Controls/MenuItem.cs
  16. 9
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  17. 64
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  18. 4
      src/Avalonia.Controls/Primitives/RangeBase.cs
  19. 8
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  20. 4
      src/Avalonia.Controls/RowDefinitions.cs
  21. 2
      src/Avalonia.Controls/Shapes/Ellipse.cs
  22. 2
      src/Avalonia.Controls/Shapes/Rectangle.cs
  23. 2
      src/Avalonia.Controls/Slider.cs
  24. 38
      src/Avalonia.Controls/TextBox.cs
  25. 6
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  26. 1
      src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs
  27. 10
      src/Avalonia.Input/DragDropDevice.cs
  28. 5
      src/Avalonia.Input/DragEventArgs.cs
  29. 8
      src/Avalonia.Input/FocusManager.cs
  30. 6
      src/Avalonia.Input/IInputElement.cs
  31. 83
      src/Avalonia.Input/InputElement.cs
  32. 2
      src/Avalonia.Input/InputExtensions.cs
  33. 4
      src/Avalonia.Input/Navigation/FocusExtensions.cs
  34. 4
      src/Avalonia.Native/Avalonia.Native.csproj
  35. 2
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  36. 2
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  37. 6
      src/Avalonia.Themes.Default/Button.xaml
  38. 7
      src/Avalonia.Themes.Default/TextBox.xaml
  39. 8
      src/Avalonia.Themes.Default/ToggleButton.xaml
  40. 12
      src/Avalonia.Visuals/Rect.cs
  41. 24
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  42. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  43. 62
      src/Avalonia.Visuals/Visual.cs
  44. 92
      src/Avalonia.Visuals/VisualExtensions.cs
  45. 11
      src/Avalonia.Visuals/VisualTree/IVisual.cs
  46. 10
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  47. 21
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  48. 107
      src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
  49. 159
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  50. 99
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs
  51. 1
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  52. 3
      src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs
  53. 1
      src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs
  54. 1
      src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs
  55. 89
      src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs
  56. 79
      src/Markup/Avalonia.Markup.Xaml/Converters/ParseTypeConverter.cs
  57. 27
      src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs
  58. 49
      src/Markup/Avalonia.Markup.Xaml/Converters/SetterValueTypeConverter.cs
  59. 41
      src/Markup/Avalonia.Markup.Xaml/Extensions.cs
  60. 9
      src/Markup/Avalonia.Markup.Xaml/MarkupExtension.cs
  61. 14
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  62. 9
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  63. 7
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs
  64. 12
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
  65. 22
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  66. 9
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleIncludeExtension.cs
  67. 39
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AttributeExtensions.cs
  68. 83
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs
  69. 56
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaNameScope.cs
  70. 147
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs
  71. 117
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs
  72. 31
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlContext.cs
  73. 222
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs
  74. 327
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
  75. 388
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  76. 101
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs
  77. 1
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  78. 32
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  79. 26
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs
  80. 11
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  81. 7
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  82. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  83. 61
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  84. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  85. 34
      src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs
  86. 4
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  87. 2
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  88. 23
      src/Skia/Avalonia.Skia/TypefaceCache.cs
  89. 44
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  90. 107
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  91. 183
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
  92. 50
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  93. 6
      tests/Avalonia.DesignerSupport.TestApp/App.xaml
  94. 2
      tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj
  95. 6
      tests/Avalonia.DesignerSupport.TestApp/MainWindow.xaml
  96. 17
      tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs
  97. 101
      tests/Avalonia.Input.UnitTests/InputElement_Enabled.cs
  98. 26
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs
  99. 70
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  100. 14
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs

3
.gitmodules

@ -1,6 +1,3 @@
[submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"]
path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
url = https://github.com/AvaloniaUI/Portable.Xaml.git
[submodule "nukebuild/Numerge"] [submodule "nukebuild/Numerge"]
path = nukebuild/Numerge path = nukebuild/Numerge
url = https://github.com/kekekeks/Numerge.git url = https://github.com/kekekeks/Numerge.git

1
Avalonia.sln

@ -146,7 +146,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Serilog.props = build\Serilog.props build\Serilog.props = build\Serilog.props
build\SharpDX.props = build\SharpDX.props build\SharpDX.props = build\SharpDX.props
build\SkiaSharp.props = build\SkiaSharp.props build\SkiaSharp.props = build\SkiaSharp.props
build\Splat.props = build\Splat.props
build\System.Memory.props = build\System.Memory.props build\System.Memory.props = build\System.Memory.props
build\XUnit.props = build\XUnit.props build\XUnit.props = build\XUnit.props
EndProjectSection EndProjectSection

4
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -26,6 +26,10 @@
<TextBox Width="200" Text="Left aligned text" TextAlignment="Left" /> <TextBox Width="200" Text="Left aligned text" TextAlignment="Left" />
<TextBox Width="200" Text="Center aligned text" TextAlignment="Center" /> <TextBox Width="200" Text="Center aligned text" TextAlignment="Center" />
<TextBox Width="200" Text="Right aligned text" TextAlignment="Right" /> <TextBox Width="200" Text="Right aligned text" TextAlignment="Right" />
<TextBox Width="200" Text="Custom selection brush"
SelectionStart="5" SelectionEnd="22"
SelectionBrush="Green" SelectionForegroundBrush="Yellow"/>
<TextBox Width="200" Text="Custom caret brush" CaretBrush="DarkOrange"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" Spacing="8"> <StackPanel Orientation="Vertical" Spacing="8">

3
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using ReactiveUI; using ReactiveUI;
@ -11,7 +12,7 @@ namespace ControlCatalog.ViewModels
public MenuPageViewModel() public MenuPageViewModel()
{ {
OpenCommand = ReactiveCommand.CreateFromTask(Open); OpenCommand = ReactiveCommand.CreateFromTask(Open);
SaveCommand = ReactiveCommand.Create(Save); SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false));
OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent); OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent);
MenuItems = new[] MenuItems = new[]

4
samples/VirtualizationDemo/MainWindow.xaml

@ -39,6 +39,8 @@
<Button Command="{Binding RecreateCommand}">Recreate</Button> <Button Command="{Binding RecreateCommand}">Recreate</Button>
<Button Command="{Binding SelectFirstCommand}">Select First</Button> <Button Command="{Binding SelectFirstCommand}">Select First</Button>
<Button Command="{Binding SelectLastCommand}">Select Last</Button> <Button Command="{Binding SelectLastCommand}">Select Last</Button>
<Button Command="{Binding RandomizeSize}">Randomize Size</Button>
<Button Command="{Binding ResetSize}">Reset Size</Button>
</StackPanel> </StackPanel>
<ListBox Name="listBox" <ListBox Name="listBox"
@ -55,7 +57,7 @@
</ListBox.ItemsPanel> </ListBox.ItemsPanel>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding Header}" TextWrapping="Wrap"/> <TextBlock Text="{Binding Header}" Height="{Binding Height}" TextWrapping="Wrap"/>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>

7
samples/VirtualizationDemo/ViewModels/ItemViewModel.cs

@ -10,6 +10,7 @@ namespace VirtualizationDemo.ViewModels
{ {
private string _prefix; private string _prefix;
private int _index; private int _index;
private double _height = double.NaN;
public ItemViewModel(int index, string prefix = "Item") public ItemViewModel(int index, string prefix = "Item")
{ {
@ -18,5 +19,11 @@ namespace VirtualizationDemo.ViewModels
} }
public string Header => $"{_prefix} {_index}"; public string Header => $"{_prefix} {_index}";
public double Height
{
get => _height;
set => this.RaiseAndSetIfChanged(ref _height, value);
}
} }
} }

18
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -98,6 +98,24 @@ namespace VirtualizationDemo.ViewModels
public ReactiveCommand SelectFirstCommand { get; private set; } public ReactiveCommand SelectFirstCommand { get; private set; }
public ReactiveCommand SelectLastCommand { get; private set; } public ReactiveCommand SelectLastCommand { get; private set; }
public void RandomizeSize()
{
var random = new Random();
foreach (var i in Items)
{
i.Height = random.Next(240) + 10;
}
}
public void ResetSize()
{
foreach (var i in Items)
{
i.Height = double.NaN;
}
}
private void ResizeItems(int count) private void ResizeItems(int count)
{ {
if (Items == null) if (Items == null)

11
src/Avalonia.Build.Tasks/Program.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.IO; using System.IO;
using System.Linq;
using Microsoft.Build.Framework; using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks namespace Avalonia.Build.Tasks
@ -11,8 +12,14 @@ namespace Avalonia.Build.Tasks
{ {
if (args.Length != 3) if (args.Length != 3)
{ {
Console.Error.WriteLine("input references output"); if (args.Length == 1)
return 1; args = new[] {"original.dll", "references", "out.dll"}
.Select(x => Path.Combine(args[0], x)).ToArray();
else
{
Console.Error.WriteLine("input references output");
return 1;
}
} }
return new CompileAvaloniaXamlTask() return new CompileAvaloniaXamlTask()

23
src/Avalonia.Controls/Button.cs

@ -33,8 +33,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class Button : ContentControl public class Button : ContentControl
{ {
private ICommand _command;
/// <summary> /// <summary>
/// Defines the <see cref="ClickMode"/> property. /// Defines the <see cref="ClickMode"/> property.
/// </summary> /// </summary>
@ -75,6 +73,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsPressedProperty = public static readonly StyledProperty<bool> IsPressedProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsPressed)); AvaloniaProperty.Register<Button, bool>(nameof(IsPressed));
private ICommand _command;
private bool _commandCanExecute = true;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Button"/> class. /// Initializes static members of the <see cref="Button"/> class.
/// </summary> /// </summary>
@ -147,6 +148,8 @@ namespace Avalonia.Controls
private set { SetValue(IsPressedProperty, value); } private set { SetValue(IsPressedProperty, value); }
} }
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
@ -292,7 +295,11 @@ namespace Avalonia.Controls
{ {
if (status?.ErrorType == BindingErrorType.Error) if (status?.ErrorType == BindingErrorType.Error)
{ {
IsEnabled = false; if (_commandCanExecute)
{
_commandCanExecute = false;
UpdateIsEffectivelyEnabled();
}
} }
} }
} }
@ -351,9 +358,13 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void CanExecuteChanged(object sender, EventArgs e) private void CanExecuteChanged(object sender, EventArgs e)
{ {
// HACK: Just set the IsEnabled property for the moment. This needs to be changed to var canExecute = Command == null || Command.CanExecute(CommandParameter);
// use IsEnabledCore etc. but it will do for now.
IsEnabled = Command == null || Command.CanExecute(CommandParameter); if (canExecute != _commandCanExecute)
{
_commandCanExecute = canExecute;
UpdateIsEffectivelyEnabled();
}
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls/ColumnDefinitions.cs

@ -16,10 +16,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ColumnDefinitions"/> class. /// Initializes a new instance of the <see cref="ColumnDefinitions"/> class.
/// </summary> /// </summary>
public ColumnDefinitions() public ColumnDefinitions() : base ()
{ {
ResetBehavior = ResetBehavior.Remove;
CollectionChanged += OnCollectionChanged;
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/ComboBox.cs

@ -302,7 +302,7 @@ namespace Avalonia.Controls
} }
} }
private bool CanFocus(IControl control) => control.Focusable && control.IsEnabledCore && control.IsVisible; private bool CanFocus(IControl control) => control.Focusable && control.IsEffectivelyEnabled && control.IsVisible;
private void UpdateSelectionBoxItem(object item) private void UpdateSelectionBoxItem(object item)
{ {

323
src/Avalonia.Controls/DefinitionBase.cs

@ -20,49 +20,15 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public abstract class DefinitionBase : AvaloniaObject public abstract class DefinitionBase : AvaloniaObject
{ {
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/* internal DefinitionBase(bool isColumnDefinition)
{
_isColumnDefinition = isColumnDefinition;
_parentIndex = -1;
}*/
#endregion Constructors
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
/// <summary> /// <summary>
/// SharedSizeGroup property. /// SharedSizeGroup property.
/// </summary> /// </summary>
public string SharedSizeGroup public string SharedSizeGroup
{ {
get { return (string) GetValue(SharedSizeGroupProperty); } get { return (string)GetValue(SharedSizeGroupProperty); }
set { SetValue(SharedSizeGroupProperty, value); } set { SetValue(SharedSizeGroupProperty, value); }
} }
#endregion Public Properties
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
/// <summary> /// <summary>
/// Callback to notify about entering model tree. /// Callback to notify about entering model tree.
/// </summary> /// </summary>
@ -86,15 +52,6 @@ namespace Avalonia.Controls
} }
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.PropertyType == typeof(GridLength)
|| e.Property.PropertyType == typeof(double))
OnUserSizePropertyChanged(e);
base.OnPropertyChanged(e);
}
/// <summary> /// <summary>
/// Callback to notify about exitting model tree. /// Callback to notify about exitting model tree.
/// </summary> /// </summary>
@ -139,119 +96,14 @@ namespace Avalonia.Controls
_minSize = minSize; _minSize = minSize;
} }
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal void OnUserSizePropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (InParentLogicalTree)
{
if (_sharedState != null)
{
_sharedState.Invalidate();
}
else
{
if (((GridLength)e.OldValue).GridUnitType != ((GridLength)e.NewValue).GridUnitType)
{
Parent.Invalidate();
}
else
{
Parent.InvalidateMeasure();
}
}
}
}
/// <summary>
/// <see cref="AvaloniaProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static bool IsUserSizePropertyValueValid(object value)
{
return (((GridLength)value).Value >= 0);
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static void OnUserMinSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
DefinitionBase definition = (DefinitionBase) d;
if (definition.InParentLogicalTree)
{
Grid parentGrid = (Grid) definition.Parent;
parentGrid.InvalidateMeasure();
}
}
/// <summary>
/// <see cref="AvaloniaProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static bool IsUserMinSizePropertyValueValid(object value)
{
double v = (double)value;
return (!double.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v));
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static void OnUserMaxSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
DefinitionBase definition = (DefinitionBase) d;
if (definition.InParentLogicalTree)
{
Grid parentGrid = (Grid) definition.Parent;
parentGrid.InvalidateMeasure();
}
}
/// <summary>
/// <see cref="AvaloniaProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// This method needs to be internal to be accessable from derived classes.
/// </remarks>
internal static bool IsUserMaxSizePropertyValueValid(object value)
{
double v = (double)value;
return (!double.IsNaN(v) && v >= 0.0d);
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remarks> /// <remarks>
/// This method reflects Grid.SharedScopeProperty state by setting / clearing /// This method reflects Grid.SharedScopeProperty state by setting / clearing
/// dynamic property PrivateSharedSizeScopeProperty. Value of PrivateSharedSizeScopeProperty /// dynamic property PrivateSharedSizeScopeProperty. Value of PrivateSharedSizeScopeProperty
/// is a collection of SharedSizeState objects for the scope. /// is a collection of SharedSizeState objects for the scope.
/// Also PrivateSharedSizeScopeProperty is FrameworkPropertyMetadataOptions.Inherits property. So that all children
/// elements belonging to a certain scope can easily access SharedSizeState collection. As well
/// as been norified about enter / exit a scope.
/// </remarks> /// </remarks>
internal static void OnIsSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) internal static void OnIsSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{ {
// is it possible to optimize here something like this: if ((bool)e.NewValue)
// if ((bool)d.GetValue(Grid.IsSharedSizeScopeProperty) == (d.GetLocalValue(PrivateSharedSizeScopeProperty) != null)
// { /* do nothing */ }
if ((bool) e.NewValue)
{ {
SharedSizeScope sharedStatesCollection = new SharedSizeScope(); SharedSizeScope sharedStatesCollection = new SharedSizeScope();
d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection); d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection);
@ -262,16 +114,6 @@ namespace Avalonia.Controls
} }
} }
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
/// <summary> /// <summary>
/// Returns <c>true</c> if this definition is a part of shared group. /// Returns <c>true</c> if this definition is a part of shared group.
/// </summary> /// </summary>
@ -349,8 +191,8 @@ namespace Avalonia.Controls
get get
{ {
double preferredSize = MinSize; double preferredSize = MinSize;
if ( _sizeType != Grid.LayoutTimeSizeType.Auto if (_sizeType != Grid.LayoutTimeSizeType.Auto
&& preferredSize < _measureSize ) && preferredSize < _measureSize)
{ {
preferredSize = _measureSize; preferredSize = _measureSize;
} }
@ -375,9 +217,9 @@ namespace Avalonia.Controls
get get
{ {
double minSize = _minSize; double minSize = _minSize;
if ( UseSharedMinimum if (UseSharedMinimum
&& _sharedState != null && _sharedState != null
&& minSize < _sharedState.MinSize ) && minSize < _sharedState.MinSize)
{ {
minSize = _sharedState.MinSize; minSize = _sharedState.MinSize;
} }
@ -393,9 +235,9 @@ namespace Avalonia.Controls
get get
{ {
double minSize = _minSize; double minSize = _minSize;
if ( _sharedState != null if (_sharedState != null
&& (UseSharedMinimum || !LayoutWasUpdated) && (UseSharedMinimum || !LayoutWasUpdated)
&& minSize < _sharedState.MinSize ) && minSize < _sharedState.MinSize)
{ {
minSize = _sharedState.MinSize; minSize = _sharedState.MinSize;
} }
@ -426,27 +268,9 @@ namespace Avalonia.Controls
/// Internal helper to access up-to-date UserMaxSize property value. /// Internal helper to access up-to-date UserMaxSize property value.
/// </summary> /// </summary>
internal abstract double UserMaxSizeValueCache { get; } internal abstract double UserMaxSizeValueCache { get; }
/// <summary>
/// Protected. Returns <c>true</c> if this DefinitionBase instance is in parent's logical tree.
/// </summary>
internal bool InParentLogicalTree
{
get { return (_parentIndex != -1); }
}
internal Grid Parent { get; set; } internal Grid Parent { get; set; }
#endregion Internal Properties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
/// <summary> /// <summary>
/// SetFlags is used to set or unset one or multiple /// SetFlags is used to set or unset one or multiple
/// flags on the object. /// flags on the object.
@ -465,16 +289,13 @@ namespace Avalonia.Controls
return ((_flags & flags) == flags); return ((_flags & flags) == flags);
} }
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{ {
DefinitionBase definition = (DefinitionBase) d; DefinitionBase definition = (DefinitionBase)d;
if (definition.InParentLogicalTree) if (definition.Parent != null)
{ {
string sharedSizeGroupId = (string) e.NewValue; string sharedSizeGroupId = (string)e.NewValue;
if (definition._sharedState != null) if (definition._sharedState != null)
{ {
@ -483,7 +304,7 @@ namespace Avalonia.Controls
definition._sharedState.RemoveMember(definition); definition._sharedState.RemoveMember(definition);
definition._sharedState = null; definition._sharedState = null;
} }
if ((definition._sharedState == null) && (sharedSizeGroupId != null)) if ((definition._sharedState == null) && (sharedSizeGroupId != null))
{ {
SharedSizeScope privateSharedSizeScope = definition.PrivateSharedSizeScope; SharedSizeScope privateSharedSizeScope = definition.PrivateSharedSizeScope;
@ -498,9 +319,6 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// <see cref="AvaloniaProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks> /// <remarks>
/// Verifies that Shared Size Group Property string /// Verifies that Shared Size Group Property string
/// a) not empty. /// a) not empty.
@ -520,10 +338,10 @@ namespace Avalonia.Controls
{ {
bool isDigit = Char.IsDigit(id[i]); bool isDigit = Char.IsDigit(id[i]);
if ( (i == 0 && isDigit) if ((i == 0 && isDigit)
|| !( isDigit || !(isDigit
|| Char.IsLetter(id[i]) || Char.IsLetter(id[i])
|| '_' == id[i] ) ) || '_' == id[i]))
{ {
break; break;
} }
@ -535,12 +353,9 @@ namespace Avalonia.Controls
} }
} }
throw new ArgumentException("Invalid SharedSizeGroup string."); throw new ArgumentException("Invalid SharedSizeGroup string.");
} }
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
/// <remark> /// <remark>
/// OnPrivateSharedSizeScopePropertyChanged is called when new scope enters or /// OnPrivateSharedSizeScopePropertyChanged is called when new scope enters or
/// existing scope just left. In both cases if the DefinitionBase object is already registered /// existing scope just left. In both cases if the DefinitionBase object is already registered
@ -550,9 +365,9 @@ namespace Avalonia.Controls
{ {
DefinitionBase definition = (DefinitionBase)d; DefinitionBase definition = (DefinitionBase)d;
if (definition.InParentLogicalTree) if (definition.Parent != null)
{ {
SharedSizeScope privateSharedSizeScope = (SharedSizeScope) e.NewValue; SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue;
if (definition._sharedState != null) if (definition._sharedState != null)
{ {
@ -576,22 +391,12 @@ namespace Avalonia.Controls
} }
} }
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
#region Private Properties
/// <summary> /// <summary>
/// Private getter of shared state collection dynamic property. /// Private getter of shared state collection dynamic property.
/// </summary> /// </summary>
private SharedSizeScope PrivateSharedSizeScope private SharedSizeScope PrivateSharedSizeScope
{ {
get { return (SharedSizeScope) GetValue(PrivateSharedSizeScopeProperty); } get { return (SharedSizeScope)GetValue(PrivateSharedSizeScopeProperty); }
} }
/// <summary> /// <summary>
@ -612,16 +417,6 @@ namespace Avalonia.Controls
set { SetFlags(value, Flags.LayoutWasUpdated); } set { SetFlags(value, Flags.LayoutWasUpdated); }
} }
#endregion Private Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private readonly bool _isColumnDefinition; // when "true", this is a ColumnDefinition; when "false" this is a RowDefinition (faster than a type check)
private Flags _flags; // flags reflecting various aspects of internal state private Flags _flags; // flags reflecting various aspects of internal state
internal int _parentIndex = -1; // this instance's index in parent's children collection internal int _parentIndex = -1; // this instance's index in parent's children collection
@ -633,19 +428,6 @@ namespace Avalonia.Controls
private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case)
private SharedSizeState _sharedState; // reference to shared state object this instance is registered with private SharedSizeState _sharedState; // reference to shared state object this instance is registered with
internal const bool ThisIsColumnDefinition = true;
internal const bool ThisIsRowDefinition = false;
#endregion Private Fields
//------------------------------------------------------
//
// Private Structures / Classes
//
//------------------------------------------------------
#region Private Structures Classes
[System.Flags] [System.Flags]
private enum Flags : byte private enum Flags : byte
@ -653,8 +435,8 @@ namespace Avalonia.Controls
// //
// bool flags // bool flags
// //
UseSharedMinimum = 0x00000020, // when "1", definition will take into account shared state's minimum UseSharedMinimum = 0x00000020, // when "1", definition will take into account shared state's minimum
LayoutWasUpdated = 0x00000040, // set to "1" every time the parent grid is measured LayoutWasUpdated = 0x00000040, // set to "1" every time the parent grid is measured
} }
/// <summary> /// <summary>
@ -799,8 +581,8 @@ namespace Avalonia.Controls
for (int i = 0, count = _registry.Count; i < count; ++i) for (int i = 0, count = _registry.Count; i < count; ++i)
{ {
Debug.Assert( _userSize.GridUnitType == GridUnitType.Auto Debug.Assert(_userSize.GridUnitType == GridUnitType.Auto
|| _userSize.GridUnitType == GridUnitType.Pixel ); || _userSize.GridUnitType == GridUnitType.Pixel);
GridLength currentGridLength = _registry[i].UserSizeValueCache; GridLength currentGridLength = _registry[i].UserSizeValueCache;
if (currentGridLength.GridUnitType == GridUnitType.Pixel) if (currentGridLength.GridUnitType == GridUnitType.Pixel)
@ -845,7 +627,7 @@ namespace Avalonia.Controls
{ {
DefinitionBase definitionBase = _registry[i]; DefinitionBase definitionBase = _registry[i];
if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated)
{ {
// if definition's min size is different, then need to re-measure // if definition's min size is different, then need to re-measure
if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize))
@ -857,7 +639,7 @@ namespace Avalonia.Controls
else else
{ {
definitionBase.UseSharedMinimum = false; definitionBase.UseSharedMinimum = false;
// if measure is valid then also need to check arrange. // if measure is valid then also need to check arrange.
// Note: definitionBase.SizeCache is volatile but at this point // Note: definitionBase.SizeCache is volatile but at this point
// it contains up-to-date final size // it contains up-to-date final size
@ -879,27 +661,34 @@ namespace Avalonia.Controls
_broadcastInvalidation = true; _broadcastInvalidation = true;
} }
private readonly SharedSizeScope _sharedSizeScope; // the scope this state belongs to
private readonly string _sharedSizeGroupId; // Id of the shared size group this object is servicing
private readonly List<DefinitionBase> _registry; // registry of participating definitions
private readonly EventHandler _layoutUpdated; // instance event handler for layout updated event
private Control _layoutUpdatedHost; // Control for which layout updated event handler is registered
private bool _broadcastInvalidation; // "true" when broadcasting of invalidation is needed
private bool _userSizeValid; // "true" when _userSize is up to date
private GridLength _userSize; // shared state
private double _minSize; // shared state
}
#endregion Private Structures Classes // the scope this state belongs to
private readonly SharedSizeScope _sharedSizeScope;
// Id of the shared size group this object is servicing
private readonly string _sharedSizeGroupId;
// Registry of participating definitions
private readonly List<DefinitionBase> _registry;
//------------------------------------------------------ // Instance event handler for layout updated event
// private readonly EventHandler _layoutUpdated;
// Properties
//
//------------------------------------------------------
#region Properties // Control for which layout updated event handler is registered
private Control _layoutUpdatedHost;
// "true" when broadcasting of invalidation is needed
private bool _broadcastInvalidation;
// "true" when _userSize is up to date
private bool _userSizeValid;
// shared state
private GridLength _userSize;
// shared state
private double _minSize;
}
/// <summary> /// <summary>
/// Private shared size scope property holds a collection of shared state objects for the a given shared size scope. /// Private shared size scope property holds a collection of shared state objects for the a given shared size scope.
@ -931,7 +720,7 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<string> SharedSizeGroupProperty = public static readonly AttachedProperty<string> SharedSizeGroupProperty =
AvaloniaProperty.RegisterAttached<DefinitionBase, Control, string>( AvaloniaProperty.RegisterAttached<DefinitionBase, Control, string>(
"SharedSizeGroup", "SharedSizeGroup",
validate:SharedSizeGroupPropertyValueValid); validate: SharedSizeGroupPropertyValueValid);
/// <summary> /// <summary>
/// Static ctor. Used for static registration of properties. /// Static ctor. Used for static registration of properties.
@ -941,7 +730,5 @@ namespace Avalonia.Controls
SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged); SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged);
PrivateSharedSizeScopeProperty.Changed.AddClassHandler<DefinitionBase>(OnPrivateSharedSizeScopePropertyChanged); PrivateSharedSizeScopeProperty.Changed.AddClassHandler<DefinitionBase>(OnPrivateSharedSizeScopePropertyChanged);
} }
#endregion Properties
} }
} }

7
src/Avalonia.Controls/DefinitionList.cs

@ -10,6 +10,12 @@ namespace Avalonia.Controls
{ {
public abstract class DefinitionList<T> : AvaloniaList<T> where T : DefinitionBase public abstract class DefinitionList<T> : AvaloniaList<T> where T : DefinitionBase
{ {
public DefinitionList()
{
ResetBehavior = ResetBehavior.Remove;
CollectionChanged += OnCollectionChanged;
}
internal bool IsDirty = true; internal bool IsDirty = true;
private Grid _parent; private Grid _parent;
@ -19,7 +25,6 @@ namespace Avalonia.Controls
set => SetParent(value); set => SetParent(value);
} }
private void SetParent(Grid value) private void SetParent(Grid value)
{ {
_parent = value; _parent = value;

953
src/Avalonia.Controls/Grid.cs

File diff suppressed because it is too large

36
src/Avalonia.Controls/MenuItem.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Generators;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
@ -20,8 +21,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable
{ {
private ICommand _command;
/// <summary> /// <summary>
/// Defines the <see cref="Command"/> property. /// Defines the <see cref="Command"/> property.
/// </summary> /// </summary>
@ -91,9 +90,8 @@ namespace Avalonia.Controls
private static readonly ITemplate<IPanel> DefaultPanel = private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel()); new FuncTemplate<IPanel>(() => new StackPanel());
/// <summary> private ICommand _command;
/// The submenu popup. private bool _commandCanExecute = true;
/// </summary>
private Popup _popup; private Popup _popup;
/// <summary> /// <summary>
@ -231,6 +229,8 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
IMenuElement IMenuItem.Parent => Parent as IMenuElement; IMenuElement IMenuItem.Parent => Parent as IMenuElement;
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
/// <inheritdoc/> /// <inheritdoc/>
bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap); bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
@ -394,6 +394,22 @@ namespace Avalonia.Controls
} }
} }
protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status)
{
base.UpdateDataValidation(property, status);
if (property == CommandProperty)
{
if (status?.ErrorType == BindingErrorType.Error)
{
if (_commandCanExecute)
{
_commandCanExecute = false;
UpdateIsEffectivelyEnabled();
}
}
}
}
/// <summary> /// <summary>
/// Closes all submenus of the menu item. /// Closes all submenus of the menu item.
/// </summary> /// </summary>
@ -437,9 +453,13 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void CanExecuteChanged(object sender, EventArgs e) private void CanExecuteChanged(object sender, EventArgs e)
{ {
// HACK: Just set the IsEnabled property for the moment. This needs to be changed to var canExecute = Command == null || Command.CanExecute(CommandParameter);
// use IsEnabledCore etc. but it will do for now.
IsEnabled = Command == null || Command.CanExecute(CommandParameter); if (canExecute != _commandCanExecute)
{
_commandCanExecute = canExecute;
UpdateIsEffectivelyEnabled();
}
} }
/// <summary> /// <summary>

9
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -20,6 +20,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
internal class ItemVirtualizerSimple : ItemVirtualizer internal class ItemVirtualizerSimple : ItemVirtualizer
{ {
private int _anchor;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ItemVirtualizerSimple"/> class. /// Initializes a new instance of the <see cref="ItemVirtualizerSimple"/> class.
/// </summary> /// </summary>
@ -362,7 +364,10 @@ namespace Avalonia.Controls.Presenters
if (panel.OverflowCount > 0) if (panel.OverflowCount > 0)
{ {
RemoveContainers(panel.OverflowCount); if (_anchor <= FirstIndex)
{
RemoveContainers(panel.OverflowCount);
}
} }
} }
@ -540,7 +545,9 @@ namespace Avalonia.Controls.Presenters
// it means we're running a unit test. // it means we're running a unit test.
if (container != null && layoutManager != null) if (container != null && layoutManager != null)
{ {
_anchor = index;
layoutManager.ExecuteLayoutPass(); layoutManager.ExecuteLayoutPass();
_anchor = -1;
if (newOffset != -1 && newOffset != OffsetValue) if (newOffset != -1 && newOffset != OffsetValue)
{ {

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

@ -19,6 +19,15 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<char> PasswordCharProperty = public static readonly StyledProperty<char> PasswordCharProperty =
AvaloniaProperty.Register<TextPresenter, char>(nameof(PasswordChar)); AvaloniaProperty.Register<TextPresenter, char>(nameof(PasswordChar));
public static readonly StyledProperty<IBrush> SelectionBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush>(nameof(SelectionBrushProperty));
public static readonly StyledProperty<IBrush> SelectionForegroundBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush>(nameof(SelectionForegroundBrushProperty));
public static readonly StyledProperty<IBrush> CaretBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush>(nameof(CaretBrushProperty));
public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty = public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>( TextBox.SelectionStartProperty.AddOwner<TextPresenter>(
o => o.SelectionStart, o => o.SelectionStart,
@ -34,11 +43,12 @@ namespace Avalonia.Controls.Presenters
private int _selectionStart; private int _selectionStart;
private int _selectionEnd; private int _selectionEnd;
private bool _caretBlink; private bool _caretBlink;
private IBrush _highlightBrush;
static TextPresenter() static TextPresenter()
{ {
AffectsRender<TextPresenter>(PasswordCharProperty); AffectsRender<TextPresenter>(PasswordCharProperty,
SelectionBrushProperty, SelectionForegroundBrushProperty,
SelectionStartProperty, SelectionEndProperty);
} }
public TextPresenter() public TextPresenter()
@ -79,6 +89,24 @@ namespace Avalonia.Controls.Presenters
set => SetValue(PasswordCharProperty, value); set => SetValue(PasswordCharProperty, value);
} }
public IBrush SelectionBrush
{
get => GetValue(SelectionBrushProperty);
set => SetValue(SelectionBrushProperty, value);
}
public IBrush SelectionForegroundBrush
{
get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value);
}
public IBrush CaretBrush
{
get => GetValue(CaretBrushProperty);
set => SetValue(CaretBrushProperty, value);
}
public int SelectionStart public int SelectionStart
{ {
get get
@ -129,14 +157,9 @@ namespace Avalonia.Controls.Presenters
var rects = FormattedText.HitTestTextRange(start, length); var rects = FormattedText.HitTestTextRange(start, length);
if (_highlightBrush == null)
{
_highlightBrush = (IBrush)this.FindResource("HighlightBrush");
}
foreach (var rect in rects) foreach (var rect in rects)
{ {
context.FillRectangle(_highlightBrush, rect); context.FillRectangle(SelectionBrush, rect);
} }
} }
@ -144,16 +167,21 @@ namespace Avalonia.Controls.Presenters
if (selectionStart == selectionEnd) if (selectionStart == selectionEnd)
{ {
var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color; var caretBrush = CaretBrush;
var caretBrush = Brushes.Black;
if (backgroundColor.HasValue) if (caretBrush is null)
{ {
byte red = (byte)~(backgroundColor.Value.R); var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color;
byte green = (byte)~(backgroundColor.Value.G); if (backgroundColor.HasValue)
byte blue = (byte)~(backgroundColor.Value.B); {
byte red = (byte)~(backgroundColor.Value.R);
caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue)); byte green = (byte)~(backgroundColor.Value.G);
byte blue = (byte)~(backgroundColor.Value.B);
caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue));
}
else
caretBrush = Brushes.Black;
} }
if (_caretBlink) if (_caretBlink)
@ -252,7 +280,7 @@ namespace Avalonia.Controls.Presenters
{ {
result.Spans = new[] result.Spans = new[]
{ {
new FormattedTextStyleSpan(start, length, foregroundBrush: Brushes.White), new FormattedTextStyleSpan(start, length, SelectionForegroundBrush),
}; };
} }

4
src/Avalonia.Controls/Primitives/RangeBase.cs

@ -44,13 +44,13 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="SmallChange"/> property. /// Defines the <see cref="SmallChange"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> SmallChangeProperty = public static readonly StyledProperty<double> SmallChangeProperty =
AvaloniaProperty.Register<RangeBase, double>(nameof(SmallChange), 0.1); AvaloniaProperty.Register<RangeBase, double>(nameof(SmallChange), 1);
/// <summary> /// <summary>
/// Defines the <see cref="LargeChange"/> property. /// Defines the <see cref="LargeChange"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> LargeChangeProperty = public static readonly StyledProperty<double> LargeChangeProperty =
AvaloniaProperty.Register<RangeBase, double>(nameof(LargeChange), 1); AvaloniaProperty.Register<RangeBase, double>(nameof(LargeChange), 10);
private double _minimum; private double _minimum;
private double _maximum = 100.0; private double _maximum = 100.0;

8
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -216,25 +216,25 @@ namespace Avalonia.Controls.Primitives
private void SmallDecrement() private void SmallDecrement()
{ {
Value = Math.Max(Value - SmallChange * ViewportSize, Minimum); Value = Math.Max(Value - SmallChange, Minimum);
OnScroll(ScrollEventType.SmallDecrement); OnScroll(ScrollEventType.SmallDecrement);
} }
private void SmallIncrement() private void SmallIncrement()
{ {
Value = Math.Min(Value + SmallChange * ViewportSize, Maximum); Value = Math.Min(Value + SmallChange, Maximum);
OnScroll(ScrollEventType.SmallIncrement); OnScroll(ScrollEventType.SmallIncrement);
} }
private void LargeDecrement() private void LargeDecrement()
{ {
Value = Math.Max(Value - LargeChange * ViewportSize, Minimum); Value = Math.Max(Value - LargeChange, Minimum);
OnScroll(ScrollEventType.LargeDecrement); OnScroll(ScrollEventType.LargeDecrement);
} }
private void LargeIncrement() private void LargeIncrement()
{ {
Value = Math.Min(Value + LargeChange * ViewportSize, Maximum); Value = Math.Min(Value + LargeChange, Maximum);
OnScroll(ScrollEventType.LargeIncrement); OnScroll(ScrollEventType.LargeIncrement);
} }

4
src/Avalonia.Controls/RowDefinitions.cs

@ -14,10 +14,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RowDefinitions"/> class. /// Initializes a new instance of the <see cref="RowDefinitions"/> class.
/// </summary> /// </summary>
public RowDefinitions() public RowDefinitions() : base()
{ {
ResetBehavior = ResetBehavior.Remove;
CollectionChanged += OnCollectionChanged;
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/Shapes/Ellipse.cs

@ -14,7 +14,7 @@ namespace Avalonia.Controls.Shapes
protected override Geometry CreateDefiningGeometry() protected override Geometry CreateDefiningGeometry()
{ {
var rect = new Rect(Bounds.Size).Deflate(StrokeThickness); var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2);
return new EllipseGeometry(rect); return new EllipseGeometry(rect);
} }

2
src/Avalonia.Controls/Shapes/Rectangle.cs

@ -14,7 +14,7 @@ namespace Avalonia.Controls.Shapes
protected override Geometry CreateDefiningGeometry() protected override Geometry CreateDefiningGeometry()
{ {
var rect = new Rect(Bounds.Size).Deflate(StrokeThickness); var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2);
return new RectangleGeometry(rect); return new RectangleGeometry(rect);
} }

2
src/Avalonia.Controls/Slider.cs

@ -47,8 +47,6 @@ namespace Avalonia.Controls
Thumb.DragStartedEvent.AddClassHandler<Slider>(x => x.OnThumbDragStarted, RoutingStrategies.Bubble); Thumb.DragStartedEvent.AddClassHandler<Slider>(x => x.OnThumbDragStarted, RoutingStrategies.Bubble);
Thumb.DragDeltaEvent.AddClassHandler<Slider>(x => x.OnThumbDragDelta, RoutingStrategies.Bubble); Thumb.DragDeltaEvent.AddClassHandler<Slider>(x => x.OnThumbDragDelta, RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler<Slider>(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble);
SmallChangeProperty.OverrideDefaultValue<Slider>(1);
LargeChangeProperty.OverrideDefaultValue<Slider>(10);
} }
/// <summary> /// <summary>

38
src/Avalonia.Controls/TextBox.cs

@ -38,6 +38,15 @@ namespace Avalonia.Controls
public static readonly StyledProperty<char> PasswordCharProperty = public static readonly StyledProperty<char> PasswordCharProperty =
AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar)); AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar));
public static readonly StyledProperty<IBrush> SelectionBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush>(nameof(SelectionBrushProperty));
public static readonly StyledProperty<IBrush> SelectionForegroundBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush>(nameof(SelectionForegroundBrushProperty));
public static readonly StyledProperty<IBrush> CaretBrushProperty =
AvaloniaProperty.Register<TextBox, IBrush>(nameof(CaretBrushProperty));
public static readonly DirectProperty<TextBox, int> SelectionStartProperty = public static readonly DirectProperty<TextBox, int> SelectionStartProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>( AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(SelectionStart), nameof(SelectionStart),
@ -169,6 +178,24 @@ namespace Avalonia.Controls
set => SetValue(PasswordCharProperty, value); set => SetValue(PasswordCharProperty, value);
} }
public IBrush SelectionBrush
{
get => GetValue(SelectionBrushProperty);
set => SetValue(SelectionBrushProperty, value);
}
public IBrush SelectionForegroundBrush
{
get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value);
}
public IBrush CaretBrush
{
get => GetValue(CaretBrushProperty);
set => SetValue(CaretBrushProperty, value);
}
public int SelectionStart public int SelectionStart
{ {
get get
@ -287,16 +314,11 @@ namespace Avalonia.Controls
{ {
DecideCaretVisibility(); DecideCaretVisibility();
} }
e.Handled = true;
} }
private void DecideCaretVisibility() private void DecideCaretVisibility()
{ {
if (!IsReadOnly) _presenter.ShowCaret();
_presenter?.ShowCaret();
else
_presenter?.HideCaret();
} }
protected override void OnLostFocus(RoutedEventArgs e) protected override void OnLostFocus(RoutedEventArgs e)
@ -456,7 +478,7 @@ namespace Avalonia.Controls
movement = true; movement = true;
selection = false; selection = false;
handled = true; handled = true;
} }
else if (Match(keymap.MoveCursorToTheEndOfLine)) else if (Match(keymap.MoveCursorToTheEndOfLine))
{ {
@ -485,7 +507,7 @@ namespace Avalonia.Controls
movement = true; movement = true;
selection = true; selection = true;
handled = true; handled = true;
} }
else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection)) else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
{ {

6
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -9,7 +9,6 @@ using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Designer; using Avalonia.Remote.Protocol.Designer;
using Avalonia.Remote.Protocol.Viewport; using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading; using Avalonia.Threading;
using Portable.Xaml;
namespace Avalonia.DesignerSupport.Remote namespace Avalonia.DesignerSupport.Remote
{ {
@ -206,7 +205,6 @@ namespace Avalonia.DesignerSupport.Remote
} }
catch (Exception e) catch (Exception e)
{ {
var xamlException = e as XamlException;
var xmlException = e as XmlException; var xmlException = e as XmlException;
s_transport.Send(new UpdateXamlResultMessage s_transport.Send(new UpdateXamlResultMessage
@ -216,8 +214,8 @@ namespace Avalonia.DesignerSupport.Remote
{ {
ExceptionType = e.GetType().FullName, ExceptionType = e.GetType().FullName,
Message = e.Message.ToString(), Message = e.Message.ToString(),
LineNumber = xamlException?.LineNumber ?? xmlException?.LineNumber, LineNumber = xmlException?.LineNumber,
LinePosition = xamlException?.LinePosition ?? xmlException?.LinePosition, LinePosition = xmlException?.LinePosition,
} }
}); });
} }

1
src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs

@ -56,6 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
if (IsEnabled.GetValueOrDefault() && !_isRegistered) if (IsEnabled.GetValueOrDefault() && !_isRegistered)
{ {
// FIXME: This leaks event handlers.
_event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); _event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true);
_isRegistered = true; _isRegistered = true;
} }

10
src/Avalonia.Input/DragDropDevice.cs

@ -23,7 +23,13 @@ namespace Avalonia.Input
{ {
if (target == null) if (target == null)
return DragDropEffects.None; return DragDropEffects.None;
var args = new DragEventArgs(routedEvent, data, target, inputRoot.TranslatePoint(point, target), modifiers)
var p = inputRoot.TranslatePoint(point, target);
if (!p.HasValue)
return DragDropEffects.None;
var args = new DragEventArgs(routedEvent, data, target, p.Value, modifiers)
{ {
RoutedEvent = routedEvent, RoutedEvent = routedEvent,
DragEffects = operation DragEffects = operation
@ -108,4 +114,4 @@ namespace Avalonia.Input
} }
} }
} }
} }

5
src/Avalonia.Input/DragEventArgs.cs

@ -26,8 +26,9 @@ namespace Avalonia.Input
if (_target != null) if (_target != null)
{ {
point = _target.TranslatePoint(_targetLocation, relativeTo); point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point;
} }
return point; return point;
} }
@ -41,4 +42,4 @@ namespace Avalonia.Input
} }
} }
} }

8
src/Avalonia.Input/FocusManager.cs

@ -23,7 +23,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FocusManager"/> class. /// Initializes a new instance of the <see cref="FocusManager"/> class.
/// </summary> /// </summary>
public FocusManager() static FocusManager()
{ {
InputElement.PointerPressedEvent.AddClassHandler( InputElement.PointerPressedEvent.AddClassHandler(
typeof(IInputElement), typeof(IInputElement),
@ -146,7 +146,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if the element can be focused.</returns> /// <returns>True if the element can be focused.</returns>
private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible;
/// <summary> /// <summary>
/// Gets the focus scope ancestors of the specified control, traversing popups. /// Gets the focus scope ancestors of the specified control, traversing popups.
@ -174,7 +174,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
/// <param name="sender">The event sender.</param> /// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void OnPreviewPointerPressed(object sender, RoutedEventArgs e) private static void OnPreviewPointerPressed(object sender, RoutedEventArgs e)
{ {
var ev = (PointerPressedEventArgs)e; var ev = (PointerPressedEventArgs)e;
@ -191,7 +191,7 @@ namespace Avalonia.Input
if (element != null) if (element != null)
{ {
Focus(element, NavigationMethod.Pointer, ev.InputModifiers); Instance?.Focus(element, NavigationMethod.Pointer, ev.InputModifiers);
} }
} }
} }

6
src/Avalonia.Input/IInputElement.cs

@ -83,14 +83,14 @@ namespace Avalonia.Input
Cursor Cursor { get; } Cursor Cursor { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the control is effectively enabled for user interaction. /// Gets a value indicating whether this control and all its parents are enabled.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual /// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual
/// controls. The <see cref="IsEnabledCore"/> property takes into account the /// controls. The <see cref="IsEffectivelyEnabled"/> property takes into account the
/// <see cref="IsEnabled"/> value of this control and its parent controls. /// <see cref="IsEnabled"/> value of this control and its parent controls.
/// </remarks> /// </remarks>
bool IsEnabledCore { get; } bool IsEffectivelyEnabled { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the control is focused. /// Gets a value indicating whether the control is focused.

83
src/Avalonia.Input/InputElement.cs

@ -28,10 +28,12 @@ namespace Avalonia.Input
AvaloniaProperty.Register<InputElement, bool>(nameof(IsEnabled), true); AvaloniaProperty.Register<InputElement, bool>(nameof(IsEnabled), true);
/// <summary> /// <summary>
/// Defines the <see cref="IsEnabledCore"/> property. /// Defines the <see cref="IsEffectivelyEnabled"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsEnabledCoreProperty = public static readonly DirectProperty<InputElement, bool> IsEffectivelyEnabledProperty =
AvaloniaProperty.Register<InputElement, bool>(nameof(IsEnabledCore), true); AvaloniaProperty.RegisterDirect<InputElement, bool>(
nameof(IsEffectivelyEnabled),
o => o.IsEffectivelyEnabled);
/// <summary> /// <summary>
/// Gets or sets associated mouse cursor. /// Gets or sets associated mouse cursor.
@ -155,6 +157,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static readonly RoutedEvent<RoutedEventArgs> DoubleTappedEvent = Gestures.DoubleTappedEvent; public static readonly RoutedEvent<RoutedEventArgs> DoubleTappedEvent = Gestures.DoubleTappedEvent;
private bool _isEffectivelyEnabled = true;
private bool _isFocused; private bool _isFocused;
private bool _isPointerOver; private bool _isPointerOver;
private GestureRecognizerCollection _gestureRecognizers; private GestureRecognizerCollection _gestureRecognizers;
@ -179,7 +182,7 @@ namespace Avalonia.Input
PointerCaptureLostEvent.AddClassHandler<InputElement>(x => x.OnPointerCaptureLost); PointerCaptureLostEvent.AddClassHandler<InputElement>(x => x.OnPointerCaptureLost);
PointerWheelChangedEvent.AddClassHandler<InputElement>(x => x.OnPointerWheelChanged); PointerWheelChangedEvent.AddClassHandler<InputElement>(x => x.OnPointerWheelChanged);
PseudoClass<InputElement, bool>(IsEnabledCoreProperty, x => !x, ":disabled"); PseudoClass<InputElement, bool>(IsEffectivelyEnabledProperty, x => !x, ":disabled");
PseudoClass<InputElement>(IsFocusedProperty, ":focus"); PseudoClass<InputElement>(IsFocusedProperty, ":focus");
PseudoClass<InputElement>(IsPointerOverProperty, ":pointerover"); PseudoClass<InputElement>(IsPointerOverProperty, ":pointerover");
} }
@ -365,31 +368,25 @@ namespace Avalonia.Input
internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); } internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); }
} }
/// <summary> /// <inheritdoc/>
/// Gets a value indicating whether the control is effectively enabled for user interaction. public bool IsEffectivelyEnabled
/// </summary> {
/// <remarks> get => _isEffectivelyEnabled;
/// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual private set => SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
/// controls. The <see cref="IsEnabledCore"/> property takes into account the }
/// <see cref="IsEnabled"/> value of this control and its parent controls.
/// </remarks> public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
bool IInputElement.IsEnabledCore => IsEnabledCore;
/// <summary> /// <summary>
/// Gets a value indicating whether the control is effectively enabled for user interaction. /// Allows a derived class to override the enabled state of the control.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual /// Derived controls may wish to disable the enabled state of the control without overwriting the
/// controls. The <see cref="IsEnabledCore"/> property takes into account the /// user-supplied <see cref="IsEnabled"/> setting. This can be done by overriding this property
/// <see cref="IsEnabled"/> value of this control and its parent controls. /// to return the overridden enabled state. If the value returned from <see cref="IsEnabledCore"/>
/// should change, then the derived control should call <see cref="UpdateIsEffectivelyEnabled()"/>.
/// </remarks> /// </remarks>
protected bool IsEnabledCore protected virtual bool IsEnabledCore => IsEnabled;
{
get { return GetValue(IsEnabledCoreProperty); }
set { SetValue(IsEnabledCoreProperty, value); }
}
public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
public GestureRecognizerCollection GestureRecognizers public GestureRecognizerCollection GestureRecognizers
=> _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this)); => _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this));
@ -417,7 +414,7 @@ namespace Avalonia.Input
protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTreeCore(e); base.OnAttachedToVisualTreeCore(e);
UpdateIsEnabledCore(); UpdateIsEffectivelyEnabled();
} }
/// <summary> /// <summary>
@ -525,9 +522,18 @@ namespace Avalonia.Input
{ {
} }
/// <summary>
/// Updates the <see cref="IsEffectivelyEnabled"/> property value according to the parent
/// control's enabled state and <see cref="IsEnabledCore"/>.
/// </summary>
protected void UpdateIsEffectivelyEnabled()
{
UpdateIsEffectivelyEnabled(this.GetVisualParent<InputElement>());
}
private static void IsEnabledChanged(AvaloniaPropertyChangedEventArgs e) private static void IsEnabledChanged(AvaloniaPropertyChangedEventArgs e)
{ {
((InputElement)e.Sender).UpdateIsEnabledCore(); ((InputElement)e.Sender).UpdateIsEffectivelyEnabled();
} }
/// <summary> /// <summary>
@ -551,32 +557,17 @@ namespace Avalonia.Input
} }
/// <summary> /// <summary>
/// Updates the <see cref="IsEnabledCore"/> property value. /// Updates the <see cref="IsEffectivelyEnabled"/> property based on the parent's
/// </summary> /// <see cref="IsEffectivelyEnabled"/>.
private void UpdateIsEnabledCore()
{
UpdateIsEnabledCore(this.GetVisualParent<InputElement>());
}
/// <summary>
/// Updates the <see cref="IsEnabledCore"/> property based on the parent's
/// <see cref="IsEnabledCore"/>.
/// </summary> /// </summary>
/// <param name="parent">The parent control.</param> /// <param name="parent">The parent control.</param>
private void UpdateIsEnabledCore(InputElement parent) private void UpdateIsEffectivelyEnabled(InputElement parent)
{ {
if (parent != null) IsEffectivelyEnabled = IsEnabledCore && (parent?.IsEffectivelyEnabled ?? true);
{
IsEnabledCore = IsEnabled && parent.IsEnabledCore;
}
else
{
IsEnabledCore = IsEnabled;
}
foreach (var child in this.GetVisualChildren().OfType<InputElement>()) foreach (var child in this.GetVisualChildren().OfType<InputElement>())
{ {
child.UpdateIsEnabledCore(this); child.UpdateIsEffectivelyEnabled(this);
} }
} }
} }

2
src/Avalonia.Input/InputExtensions.cs

@ -45,7 +45,7 @@ namespace Avalonia.Input
return element != null && return element != null &&
element.IsVisible && element.IsVisible &&
element.IsHitTestVisible && element.IsHitTestVisible &&
element.IsEnabledCore && element.IsEffectivelyEnabled &&
element.IsAttachedToVisualTree; element.IsAttachedToVisualTree;
} }
} }

4
src/Avalonia.Input/Navigation/FocusExtensions.cs

@ -13,13 +13,13 @@ namespace Avalonia.Input.Navigation
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if the element can be focused.</returns> /// <returns>True if the element can be focused.</returns>
public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible;
/// <summary> /// <summary>
/// Checks if descendants of the specified element can be focused. /// Checks if descendants of the specified element can be focused.
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if descendants of the element can be focused.</returns> /// <returns>True if descendants of the element can be focused.</returns>
public static bool CanFocusDescendants(this IInputElement e) => e.IsEnabledCore && e.IsVisible; public static bool CanFocusDescendants(this IInputElement e) => e.IsEffectivelyEnabled && e.IsVisible;
} }
} }

4
src/Avalonia.Native/Avalonia.Native.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
@ -7,8 +7,6 @@
<CastXmlPath Condition="Exists('/usr/bin/castxml')">/usr/bin/castxml</CastXmlPath> <CastXmlPath Condition="Exists('/usr/bin/castxml')">/usr/bin/castxml</CastXmlPath>
<CastXmlPath Condition="Exists('/usr/local/bin/castxml')">/usr/local/bin/castxml</CastXmlPath> <CastXmlPath Condition="Exists('/usr/local/bin/castxml')">/usr/local/bin/castxml</CastXmlPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- This is needed because Rider doesn't see generated files in obj for some reason -->
<SharpGenGeneratedCodeFolder>$(MSBuildThisFileDirectory)/Generated</SharpGenGeneratedCodeFolder>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release' AND '$([MSBuild]::IsOSPlatform(OSX))' == 'true'"> <ItemGroup Condition="'$(Configuration)' == 'Release' AND '$([MSBuild]::IsOSPlatform(OSX))' == 'true'">

2
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@ -22,6 +22,7 @@
<Color x:Key="ThemeForegroundLowColor">#FF808080</Color> <Color x:Key="ThemeForegroundLowColor">#FF808080</Color>
<Color x:Key="HighlightColor">#FF119EDA</Color> <Color x:Key="HighlightColor">#FF119EDA</Color>
<Color x:Key="HighlightForegroundColor">#FFFFFFFF</Color>
<Color x:Key="ErrorColor">#FFFF0000</Color> <Color x:Key="ErrorColor">#FFFF0000</Color>
<Color x:Key="ErrorLowColor">#10FF0000</Color> <Color x:Key="ErrorLowColor">#10FF0000</Color>
@ -39,6 +40,7 @@
<SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="{DynamicResource ThemeForegroundLowColor}"></SolidColorBrush> <SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="{DynamicResource ThemeForegroundLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="HighlightBrush" Color="{DynamicResource HighlightColor}"></SolidColorBrush> <SolidColorBrush x:Key="HighlightBrush" Color="{DynamicResource HighlightColor}"></SolidColorBrush>
<SolidColorBrush x:Key="HighlightForegroundBrush" Color="{DynamicResource HighlightForegroundColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush" Color="{DynamicResource ThemeAccentColor}"></SolidColorBrush> <SolidColorBrush x:Key="ThemeAccentBrush" Color="{DynamicResource ThemeAccentColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush2" Color="{DynamicResource ThemeAccentColor2}"></SolidColorBrush> <SolidColorBrush x:Key="ThemeAccentBrush2" Color="{DynamicResource ThemeAccentColor2}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush3" Color="{DynamicResource ThemeAccentColor3}"></SolidColorBrush> <SolidColorBrush x:Key="ThemeAccentBrush3" Color="{DynamicResource ThemeAccentColor3}"></SolidColorBrush>

2
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -22,6 +22,7 @@
<Color x:Key="ThemeForegroundLowColor">#FF808080</Color> <Color x:Key="ThemeForegroundLowColor">#FF808080</Color>
<Color x:Key="HighlightColor">#FF086F9E</Color> <Color x:Key="HighlightColor">#FF086F9E</Color>
<Color x:Key="HighlightForegroundColor">#FFFFFFFF</Color>
<Color x:Key="ErrorColor">#FFFF0000</Color> <Color x:Key="ErrorColor">#FFFF0000</Color>
<Color x:Key="ErrorLowColor">#10FF0000</Color> <Color x:Key="ErrorLowColor">#10FF0000</Color>
@ -39,6 +40,7 @@
<SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="{DynamicResource ThemeForegroundLowColor}"></SolidColorBrush> <SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="{DynamicResource ThemeForegroundLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="HighlightBrush" Color="{DynamicResource HighlightColor}"></SolidColorBrush> <SolidColorBrush x:Key="HighlightBrush" Color="{DynamicResource HighlightColor}"></SolidColorBrush>
<SolidColorBrush x:Key="HighlightForegroundBrush" Color="{DynamicResource HighlightForegroundColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush" Color="{DynamicResource ThemeAccentColor}"></SolidColorBrush> <SolidColorBrush x:Key="ThemeAccentBrush" Color="{DynamicResource ThemeAccentColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush2" Color="{DynamicResource ThemeAccentColor2}"></SolidColorBrush> <SolidColorBrush x:Key="ThemeAccentBrush2" Color="{DynamicResource ThemeAccentColor2}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush3" Color="{DynamicResource ThemeAccentColor3}"></SolidColorBrush> <SolidColorBrush x:Key="ThemeAccentBrush3" Color="{DynamicResource ThemeAccentColor3}"></SolidColorBrush>

6
src/Avalonia.Themes.Default/Button.xaml

@ -22,13 +22,13 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Button:pointerover /template/ ContentPresenter"> <Style Selector="Button:pointerover">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
</Style> </Style>
<Style Selector="Button:pressed /template/ ContentPresenter"> <Style Selector="Button:pressed">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/>
</Style> </Style>
<Style Selector="Button:disabled"> <Style Selector="Button:disabled">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/> <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style> </Style>
</Styles> </Styles>

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

@ -3,6 +3,8 @@
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/> <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="SelectionBrush" Value="{DynamicResource HighlightBrush}"/>
<Setter Property="SelectionForegroundBrush" Value="{DynamicResource HighlightForegroundBrush}"/>
<Setter Property="Padding" Value="4"/> <Setter Property="Padding" Value="4"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
@ -44,7 +46,10 @@
SelectionEnd="{TemplateBinding SelectionEnd}" SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
PasswordChar="{TemplateBinding PasswordChar}"/> PasswordChar="{TemplateBinding PasswordChar}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}"/>
</Panel> </Panel>
</ScrollViewer> </ScrollViewer>
</DataValidationErrors> </DataValidationErrors>

8
src/Avalonia.Themes.Default/ToggleButton.xaml

@ -22,17 +22,17 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="ToggleButton:checked /template/ ContentPresenter"> <Style Selector="ToggleButton:checked">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
</Style> </Style>
<Style Selector="ToggleButton:pointerover /template/ ContentPresenter"> <Style Selector="ToggleButton:pointerover">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
</Style> </Style>
<Style Selector="ToggleButton:pressed /template/ ContentPresenter"> <Style Selector="ToggleButton:pressed">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/>
</Style> </Style>
<Style Selector="ToggleButton:disabled"> <Style Selector="ToggleButton:disabled">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/> <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style> </Style>
</Styles> </Styles>

12
src/Avalonia.Visuals/Rect.cs

@ -256,7 +256,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Inflates the rectangle. /// Inflates the rectangle.
/// </summary> /// </summary>
/// <param name="thickness">The thickness.</param> /// <param name="thickness">The thickness to be subtracted for each side of the rectangle.</param>
/// <returns>The inflated rectangle.</returns> /// <returns>The inflated rectangle.</returns>
public Rect Inflate(double thickness) public Rect Inflate(double thickness)
{ {
@ -266,7 +266,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Inflates the rectangle. /// Inflates the rectangle.
/// </summary> /// </summary>
/// <param name="thickness">The thickness.</param> /// <param name="thickness">The thickness to be subtracted for each side of the rectangle.</param>
/// <returns>The inflated rectangle.</returns> /// <returns>The inflated rectangle.</returns>
public Rect Inflate(Thickness thickness) public Rect Inflate(Thickness thickness)
{ {
@ -278,20 +278,18 @@ namespace Avalonia
/// <summary> /// <summary>
/// Deflates the rectangle. /// Deflates the rectangle.
/// </summary> /// </summary>
/// <param name="thickness">The thickness.</param> /// <param name="thickness">The thickness to be subtracted for each side of the rectangle.</param>
/// <returns>The deflated rectangle.</returns> /// <returns>The deflated rectangle.</returns>
/// <remarks>The deflated rectangle size cannot be less than 0.</remarks>
public Rect Deflate(double thickness) public Rect Deflate(double thickness)
{ {
return Deflate(new Thickness(thickness / 2)); return Deflate(new Thickness(thickness));
} }
/// <summary> /// <summary>
/// Deflates the rectangle by a <see cref="Thickness"/>. /// Deflates the rectangle by a <see cref="Thickness"/>.
/// </summary> /// </summary>
/// <param name="thickness">The thickness.</param> /// <param name="thickness">The thickness to be subtracted for each side of the rectangle.</param>
/// <returns>The deflated rectangle.</returns> /// <returns>The deflated rectangle.</returns>
/// <remarks>The deflated rectangle size cannot be less than 0.</remarks>
public Rect Deflate(Thickness thickness) public Rect Deflate(Thickness thickness)
{ {
return new Rect( return new Rect(

24
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@ -105,8 +105,28 @@ namespace Avalonia.Rendering.SceneGraph
} }
} }
// TODO: This doesn't respect CornerRadius yet.
/// <inheritdoc/> /// <inheritdoc/>
public override bool HitTest(Point p) => Bounds.Contains(p); public override bool HitTest(Point p)
{
// TODO: This doesn't respect CornerRadius yet.
if (Transform.HasInverse)
{
p *= Transform.Invert();
if (Brush != null)
{
var rect = Rect.Inflate((Pen?.Thickness / 2) ?? 0);
return rect.Contains(p);
}
else
{
var borderRect = Rect.Inflate((Pen?.Thickness / 2) ?? 0);
var emptyRect = Rect.Deflate((Pen?.Thickness / 2) ?? 0);
return borderRect.Contains(p) && !emptyRect.Contains(p);
}
}
return false;
}
} }
} }

2
src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

@ -173,7 +173,7 @@ namespace Avalonia.Rendering.SceneGraph
if (node.GeometryClip != null) if (node.GeometryClip != null)
{ {
var controlPoint = Root.Visual.TranslatePoint(p, node.Visual); var controlPoint = Root.Visual.TranslatePoint(p, node.Visual);
clipped = !node.GeometryClip.FillContains(controlPoint); clipped = !node.GeometryClip.FillContains(controlPoint.Value);
} }
if (!clipped) if (!clipped)

62
src/Avalonia.Visuals/Visual.cs

@ -291,29 +291,6 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(context != null); Contract.Requires<ArgumentNullException>(context != null);
} }
/// <summary>
/// Returns a transform that transforms the visual's coordinates into the coordinates
/// of the specified <paramref name="visual"/>.
/// </summary>
/// <param name="visual">The visual to translate the coordinates to.</param>
/// <returns>
/// A <see cref="Matrix"/> containing the transform or null if the visuals don't share a
/// common ancestor.
/// </returns>
public Matrix? TransformToVisual(IVisual visual)
{
var common = this.FindCommonVisualAncestor(visual);
if (common != null)
{
var thisOffset = GetOffsetFrom(common, this);
var thatOffset = GetOffsetFrom(common, visual);
return -thatOffset * thisOffset;
}
return null;
}
/// <summary> /// <summary>
/// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be /// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be
/// called. /// called.
@ -480,45 +457,6 @@ namespace Avalonia
} }
} }
/// <summary>
/// Gets the visual offset from the specified ancestor.
/// </summary>
/// <param name="ancestor">The ancestor visual.</param>
/// <param name="visual">The visual.</param>
/// <returns>The visual offset.</returns>
private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual)
{
var result = Matrix.Identity;
while (visual != ancestor)
{
if (visual.RenderTransform?.Value != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(visual.Bounds.Size);
var offset = Matrix.CreateTranslation(origin);
var renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
result *= renderTransform;
}
var topLeft = visual.Bounds.TopLeft;
if (topLeft != default)
{
result *= Matrix.CreateTranslation(topLeft);
}
visual = visual.VisualParent;
if (visual == null)
{
throw new ArgumentException("'visual' is not a descendant of 'ancestor'.");
}
}
return result;
}
/// <summary> /// <summary>
/// Called when a visual's <see cref="RenderTransform"/> changes. /// Called when a visual's <see cref="RenderTransform"/> changes.
/// </summary> /// </summary>

92
src/Avalonia.Visuals/VisualExtensions.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Rendering;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia namespace Avalonia
@ -20,10 +19,8 @@ namespace Avalonia
/// <returns>The point in client coordinates.</returns> /// <returns>The point in client coordinates.</returns>
public static Point PointToClient(this IVisual visual, PixelPoint point) public static Point PointToClient(this IVisual visual, PixelPoint point)
{ {
var (root, offset) = GetRootAndPosition(visual); var rootPoint = visual.VisualRoot.PointToClient(point);
var screenOffset = PixelPoint.FromPoint((Point)offset, root.RenderScaling); return visual.VisualRoot.TranslatePoint(rootPoint, visual).Value;
var screenPoint = new PixelPoint(point.X - screenOffset.X, point.Y - screenOffset.Y);
return root.PointToClient(screenPoint);
} }
/// <summary> /// <summary>
@ -34,48 +31,93 @@ namespace Avalonia
/// <returns>The point in screen coordinates.</returns> /// <returns>The point in screen coordinates.</returns>
public static PixelPoint PointToScreen(this IVisual visual, Point point) public static PixelPoint PointToScreen(this IVisual visual, Point point)
{ {
var p = GetRootAndPosition(visual); var p = visual.TranslatePoint(point, visual.VisualRoot);
return p.Item1.PointToScreen(point + p.Item2); return visual.VisualRoot.PointToScreen(p.Value);
}
/// <summary>
/// Returns a transform that transforms the visual's coordinates into the coordinates
/// of the specified <paramref name="to"/>.
/// </summary>
/// <param name="from">The visual whose coordinates are to be transformed.</param>
/// <param name="to">The visual to translate the coordinates to.</param>
/// <returns>
/// A <see cref="Matrix"/> containing the transform or null if the visuals don't share a
/// common ancestor.
/// </returns>
public static Matrix? TransformToVisual(this IVisual from, IVisual to)
{
var common = from.FindCommonVisualAncestor(to);
if (common != null)
{
var thisOffset = GetOffsetFrom(common, from);
var thatOffset = GetOffsetFrom(common, to);
return -thatOffset * thisOffset;
}
return null;
} }
/// <summary> /// <summary>
/// Translates a point relative to this visual to coordinates that are relative to the specified visual. /// Translates a point relative to this visual to coordinates that are relative to the specified visual.
/// The visual and relativeTo should be descendants of the same root window
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <param name="point">The point value, as relative to this visual.</param> /// <param name="point">The point value, as relative to this visual.</param>
/// <param name="relativeTo">The visual to translate the given point into.</param> /// <param name="relativeTo">The visual to translate the given point into.</param>
/// <returns>A point value, now relative to the target visual rather than this source element.</returns> /// <returns>
public static Point TranslatePoint(this IVisual visual, Point point, IVisual relativeTo) /// A point value, now relative to the target visual rather than this source element, or null if the
/// two elements have no common ancestor.
/// </returns>
public static Point? TranslatePoint(this IVisual visual, Point point, IVisual relativeTo)
{ {
var pos = GetRootAndPosition(visual); var transform = visual.TransformToVisual(relativeTo);
var relToPos = GetRootAndPosition(relativeTo);
return point - (relToPos.Item2 - pos.Item2); if (transform.HasValue)
{
return point.Transform(transform.Value);
}
return null;
} }
/// <summary> /// <summary>
/// Gets the root of the control's visual tree and the position of the control /// Gets a transform from an ancestor to a descendent.
/// in the root's coordinate space.
/// </summary> /// </summary>
/// <param name="v">The visual.</param> /// <param name="ancestor">The ancestor visual.</param>
/// <returns>A tuple containing the root and the position of the control.</returns> /// <param name="visual">The visual.</param>
private static Tuple<IRenderRoot, Vector> GetRootAndPosition(IVisual v) /// <returns>The transform.</returns>
private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual)
{ {
var result = new Vector(); var result = Matrix.Identity;
while (!(v is IRenderRoot)) while (visual != ancestor)
{ {
result = new Vector(result.X + v.Bounds.X, result.Y + v.Bounds.Y); if (visual.RenderTransform?.Value != null)
v = v.VisualParent; {
var origin = visual.RenderTransformOrigin.ToPixels(visual.Bounds.Size);
var offset = Matrix.CreateTranslation(origin);
var renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
result *= renderTransform;
}
var topLeft = visual.Bounds.TopLeft;
if (topLeft != default)
{
result *= Matrix.CreateTranslation(topLeft);
}
visual = visual.VisualParent;
if (v == null) if (visual == null)
{ {
throw new InvalidOperationException("Control is not attached to visual tree."); throw new ArgumentException("'visual' is not a descendant of 'ancestor'.");
} }
} }
return Tuple.Create((IRenderRoot)v, result); return result;
} }
} }
} }

11
src/Avalonia.Visuals/VisualTree/IVisual.cs

@ -116,16 +116,5 @@ namespace Avalonia.VisualTree
/// </summary> /// </summary>
/// <param name="context">The context.</param> /// <param name="context">The context.</param>
void Render(DrawingContext context); void Render(DrawingContext context);
/// <summary>
/// Returns a transform that transforms the visual's coordinates into the coordinates
/// of the specified <paramref name="visual"/>.
/// </summary>
/// <param name="visual">The visual to translate the coordinates to.</param>
/// <returns>
/// A <see cref="Matrix"/> containing the transform or null if the visuals don't share a
/// common ancestor.
/// </returns>
Matrix? TransformToVisual(IVisual visual);
} }
} }

10
src/Avalonia.Visuals/VisualTree/VisualExtensions.cs

@ -132,8 +132,14 @@ namespace Avalonia.VisualTree
Contract.Requires<ArgumentNullException>(visual != null); Contract.Requires<ArgumentNullException>(visual != null);
var root = visual.GetVisualRoot(); var root = visual.GetVisualRoot();
p = visual.TranslatePoint(p, root); var rootPoint = visual.TranslatePoint(p, root);
return root.Renderer.HitTest(p, visual, filter);
if (rootPoint.HasValue)
{
return root.Renderer.HitTest(rootPoint.Value, visual, filter);
}
return Enumerable.Empty<IVisual>();
} }
/// <summary> /// <summary>

21
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -11,38 +11,22 @@
<ItemGroup> <ItemGroup>
<Compile Include="AvaloniaXamlLoader.cs" /> <Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\AvaloniaUriTypeConverter.cs" /> <Compile Include="Converters\AvaloniaUriTypeConverter.cs" />
<Compile Include="Converters\AvaloniaEventConverter.cs" />
<Compile Include="Converters\FontFamilyTypeConverter.cs" /> <Compile Include="Converters\FontFamilyTypeConverter.cs" />
<Compile Include="Converters\MemberSelectorTypeConverter.cs" /> <Compile Include="Converters\MemberSelectorTypeConverter.cs" />
<Compile Include="Converters\NullableTypeConverter.cs" />
<Compile Include="Converters\ParseTypeConverter.cs" />
<Compile Include="Converters\SetterValueTypeConverter.cs" />
<Compile Include="Converters\TimeSpanTypeConverter.cs" /> <Compile Include="Converters\TimeSpanTypeConverter.cs" />
<Compile Include="Extensions.cs" /> <Compile Include="Extensions.cs" />
<Compile Include="MarkupExtension.cs" />
<Compile Include="MarkupExtensions\DynamicResourceExtension.cs" /> <Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
<Compile Include="MarkupExtensions\ResourceInclude.cs" /> <Compile Include="MarkupExtensions\ResourceInclude.cs" />
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" /> <Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
<Compile Include="MarkupExtensions\StyleIncludeExtension.cs" /> <Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
<Compile Include="Parsers\PropertyParser.cs" /> <Compile Include="Parsers\PropertyParser.cs" />
<Compile Include="PortableXaml\AvaloniaResourceXamlInfo.cs" />
<Compile Include="PortableXaml\AvaloniaXamlContext.cs" />
<Compile Include="PortableXaml\AttributeExtensions.cs" />
<Compile Include="PortableXaml\AvaloniaMemberAttributeProvider.cs" />
<Compile Include="PortableXaml\AvaloniaNameScope.cs" />
<Compile Include="AvaloniaTypeConverters.cs" />
<Compile Include="PortableXaml\AvaloniaXamlObjectWriter.cs" />
<Compile Include="PortableXaml\AvaloniaRuntimeTypeProvider.cs" />
<Compile Include="PortableXaml\AvaloniaXamlSchemaContext.cs" />
<Compile Include="Converters\BitmapTypeConverter.cs" /> <Compile Include="Converters\BitmapTypeConverter.cs" />
<Compile Include="Converters\IconTypeConverter.cs" /> <Compile Include="Converters\IconTypeConverter.cs" />
<Compile Include="Converters\AvaloniaPropertyTypeConverter.cs" /> <Compile Include="Converters\AvaloniaPropertyTypeConverter.cs" />
<Compile Include="Converters\PointsListTypeConverter.cs" /> <Compile Include="Converters\PointsListTypeConverter.cs" />
<Compile Include="Converters\SelectorTypeConverter.cs" />
<Compile Include="MarkupExtensions\BindingExtension.cs" /> <Compile Include="MarkupExtensions\BindingExtension.cs" />
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" /> <Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="PortableXaml\AvaloniaTypeAttributeProvider.cs" />
<Compile Include="PortableXaml\AvaloniaXamlType.cs" />
<Compile Include="PortableXaml\TypeDescriptorExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Styling\StyleInclude.cs" /> <Compile Include="Styling\StyleInclude.cs" />
<Compile Include="Templates\ControlTemplate.cs" /> <Compile Include="Templates\ControlTemplate.cs" />
@ -52,7 +36,6 @@
<Compile Include="Templates\MemberSelector.cs" /> <Compile Include="Templates\MemberSelector.cs" />
<Compile Include="Templates\Template.cs" /> <Compile Include="Templates\Template.cs" />
<Compile Include="Templates\TemplateContent.cs" /> <Compile Include="Templates\TemplateContent.cs" />
<Compile Include="Templates\TemplateLoader.cs" />
<Compile Include="Templates\TreeDataTemplate.cs" /> <Compile Include="Templates\TreeDataTemplate.cs" />
<Compile Include="XamlIl\AvaloniaXamlIlRuntimeCompiler.cs" /> <Compile Include="XamlIl\AvaloniaXamlIlRuntimeCompiler.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionHackTransformer.cs" /> <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionHackTransformer.cs" />
@ -76,11 +59,11 @@
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" /> <Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
<Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" /> <Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
<Compile Include="XamlLoadException.cs" /> <Compile Include="XamlLoadException.cs" />
<Compile Include="PortableXaml\portable.xaml.github\src\Portable.Xaml\**\*.cs" Exclude="PortableXaml\portable.xaml.github\src\Portable.Xaml\Assembly\**\*.cs" />
<Compile Include="XamlIl\xamlil.github\src\XamlIl\**\*.cs" /> <Compile Include="XamlIl\xamlil.github\src\XamlIl\**\*.cs" />
<Compile Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs" /> <Compile Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs" />
<Compile Remove="XamlIl\xamlil.github\**\obj\**\*.cs" /> <Compile Remove="XamlIl\xamlil.github\**\obj\**\*.cs" />
<Compile Include="..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" /> <Compile Include="..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" />
<Compile Include="XamlTypes.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" /> <ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" />

107
src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs

@ -1,107 +0,0 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Media.Imaging;
using Avalonia.Styling;
using Avalonia.Controls.Templates;
namespace Avalonia.Markup.Xaml
{
using System.Reflection;
using Avalonia.Media;
/// <summary>
/// Maintains a repository of <see cref="TypeConverter"/>s for XAML parsing on top of those
/// maintained by <see cref="TypeDescriptor"/>.
/// </summary>
/// <remarks>
/// The default method of defining type converters using <see cref="TypeConverterAttribute"/>
/// isn't powerful enough for our purposes:
///
/// - It doesn't handle non-constructed generic types (such as <see cref="AvaloniaList{T}"/>)
/// - Type converters which require XAML features cannot be defined in non-XAML assemblies and
/// so can't be referenced using <see cref="TypeConverterAttribute"/>
/// - Many types have a static `Parse(string)` method which can be used implicitly; this class
/// detects such methods and auto-creates a type converter
/// </remarks>
public static class AvaloniaTypeConverters
{
// When adding item to that list make sure to modify AvaloniaXamlIlLanguage
private static Dictionary<Type, Type> _converters = new Dictionary<Type, Type>()
{
{ typeof(AvaloniaList<>), typeof(AvaloniaListConverter<>) },
{ typeof(AvaloniaProperty), typeof(AvaloniaPropertyTypeConverter) },
{ typeof(IBitmap), typeof(BitmapTypeConverter) },
{ typeof(IList<Point>), typeof(PointsListTypeConverter) },
{ typeof(IMemberSelector), typeof(MemberSelectorTypeConverter) },
{ typeof(Selector), typeof(SelectorTypeConverter) },
{ typeof(TimeSpan), typeof(TimeSpanTypeConverter) },
{ typeof(WindowIcon), typeof(IconTypeConverter) },
{ typeof(CultureInfo), typeof(CultureInfoConverter) },
{ typeof(Uri), typeof(AvaloniaUriTypeConverter) },
{ typeof(FontFamily), typeof(FontFamilyTypeConverter) },
{ typeof(EventInfo), typeof(AvaloniaEventConverter) },
};
internal static Type GetBuiltinTypeConverter(Type type)
{
_converters.TryGetValue(type, out var result);
return result;
}
/// <summary>
/// Tries to lookup a <see cref="TypeConverter"/> for a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The type converter.</returns>
public static Type GetTypeConverter(Type type)
{
if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var inner = GetTypeConverter(type.GetGenericArguments()[0]);
if (inner == null)
return null;
return typeof(NullableTypeConverter<>).MakeGenericType(inner);
}
if (_converters.TryGetValue(type, out var result))
{
return result;
}
// Converters for non-constructed generic types can't be specified using
// TypeConverterAttribute. Allow them to be registered here and handle them sanely.
if (type.IsConstructedGenericType &&
_converters.TryGetValue(type.GetGenericTypeDefinition(), out result))
{
return result?.MakeGenericType(type.GetGenericArguments());
}
// If the type isn't a primitive or a type that XAML already handles, but has a static
// Parse method, use that
if (!type.IsPrimitive &&
type != typeof(DateTime) &&
type != typeof(Uri) &&
ParseTypeConverter.HasParseMethod(type))
{
result = typeof(ParseTypeConverter<>).MakeGenericType(type);
_converters.Add(type, result);
return result;
}
_converters.Add(type, null);
return null;
}
/// <summary>
/// Registers a type converter for a type.
/// </summary>
/// <param name="type">The type. Maybe be a non-constructed generic type.</param>
/// <param name="converterType">The converter type. Maybe be a non-constructed generic type.</param>
public static void Register(Type type, Type converterType) => _converters[type] = converterType;
}
}

159
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -15,7 +15,6 @@ using Avalonia.Controls;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.PortableXaml; using Avalonia.Markup.Xaml.PortableXaml;
using Avalonia.Platform; using Avalonia.Platform;
using Portable.Xaml;
namespace Avalonia.Markup.Xaml namespace Avalonia.Markup.Xaml
{ {
@ -26,71 +25,14 @@ namespace Avalonia.Markup.Xaml
{ {
public bool IsDesignMode { get; set; } public bool IsDesignMode { get; set; }
public static bool UseLegacyXamlLoader { get; set; } = false;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaXamlLoader"/> class.
/// </summary>
public AvaloniaXamlLoader()
{
}
/// <summary> /// <summary>
/// Loads the XAML into a Avalonia component. /// Loads the XAML into a Avalonia component.
/// </summary> /// </summary>
/// <param name="obj">The object to load the XAML into.</param> /// <param name="obj">The object to load the XAML into.</param>
public static void Load(object obj) public static void Load(object obj)
{ {
Contract.Requires<ArgumentNullException>(obj != null); throw new XamlLoadException(
$"No precompiled XAML found for {obj.GetType()}, make sure to specify x:Class and include your XAML file as AvaloniaResource");
var loader = new AvaloniaXamlLoader();
loader.Load(obj.GetType(), obj);
}
/// <summary>
/// Loads the XAML for a type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns>
public object Load(Type type, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(type != null);
// HACK: Currently Visual Studio is forcing us to change the extension of xaml files
// in certain situations, so we try to load .xaml and if that's not found we try .xaml.
// Ideally we'd be able to use .xaml everywhere
var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
if (assetLocator == null)
{
throw new InvalidOperationException(
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
foreach (var uri in GetUrisFor(assetLocator, type))
{
if (assetLocator.Exists(uri))
{
using (var stream = assetLocator.Open(uri))
{
var initialize = rootInstance as ISupportInitialize;
initialize?.BeginInit();
try
{
return Load(stream, type.Assembly, rootInstance, uri);
}
finally
{
initialize?.EndInit();
}
}
}
}
throw new FileNotFoundException("Unable to find view for " + type.FullName);
} }
/// <summary> /// <summary>
@ -100,11 +42,8 @@ namespace Avalonia.Markup.Xaml
/// <param name="baseUri"> /// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative. /// A base URI to use if <paramref name="uri"/> is relative.
/// </param> /// </param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns> /// <returns>The loaded object.</returns>
public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) public object Load(Uri uri, Uri baseUri = null)
{ {
Contract.Requires<ArgumentNullException>(uri != null); Contract.Requires<ArgumentNullException>(uri != null);
@ -133,7 +72,7 @@ namespace Avalonia.Markup.Xaml
using (var stream = asset.stream) using (var stream = asset.stream)
{ {
var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri); var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
return Load(stream, asset.assembly, rootInstance, absoluteUri); return Load(stream, asset.assembly, null, absoluteUri);
} }
} }
@ -166,95 +105,9 @@ namespace Avalonia.Markup.Xaml
/// </param> /// </param>
/// <param name="uri">The URI of the XAML</param> /// <param name="uri">The URI of the XAML</param>
/// <returns>The loaded object.</returns> /// <returns>The loaded object.</returns>
public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null) public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null)
{ => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, IsDesignMode);
if (!UseLegacyXamlLoader)
return AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, IsDesignMode);
var readerSettings = new XamlXmlReaderSettings()
{
BaseUri = uri,
LocalAssembly = localAssembly,
ProvideLineInfo = true,
};
var context = IsDesignMode ? AvaloniaXamlSchemaContext.DesignInstance : AvaloniaXamlSchemaContext.Instance;
var reader = new XamlXmlReader(stream, context, readerSettings);
object result = LoadFromReader(
reader,
AvaloniaXamlContext.For(readerSettings, rootInstance));
var topLevel = result as TopLevel;
if (topLevel != null)
{
DelayedBinding.ApplyBindings(topLevel);
}
return result;
}
internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null, IAmbientProvider parentAmbientProvider = null)
{
var writer = AvaloniaXamlObjectWriter.Create(
(AvaloniaXamlSchemaContext)reader.SchemaContext,
context,
parentAmbientProvider);
XamlServices.Transform(reader, writer);
writer.ApplyAllDelayedProperties();
return writer.Result;
}
internal static object LoadFromReader(XamlReader reader)
{
//return XamlServices.Load(reader);
return LoadFromReader(reader, null);
}
private static readonly DataContractSerializer s_xamlInfoSerializer =
new DataContractSerializer(typeof(AvaloniaResourceXamlInfo));
/// <summary>
/// Gets the URI for a type.
/// </summary>
/// <param name="assetLocator"></param>
/// <param name="type">The type.</param>
/// <returns>The URI.</returns>
private static IEnumerable<Uri> GetUrisFor(IAssetLoader assetLocator, Type type)
{
var asm = type.GetTypeInfo().Assembly.GetName().Name;
var xamlInfoUri = new Uri($"avares://{asm}/!AvaloniaResourceXamlInfo");
var typeName = type.FullName;
if (typeName == null)
throw new ArgumentException("Type doesn't have a FullName");
if (assetLocator.Exists(xamlInfoUri))
{
using (var xamlInfoStream = assetLocator.Open(xamlInfoUri))
{
var assetDoc = XDocument.Load(xamlInfoStream);
XNamespace assetNs = assetDoc.Root.Attribute("xmlns").Value;
XNamespace arrayNs = "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
Dictionary<string,string> xamlInfo =
assetDoc.Root.Element(assetNs + "ClassToResourcePathIndex").Elements(arrayNs + "KeyValueOfstringstring")
.ToDictionary(entry =>entry.Element(arrayNs + "Key").Value,
entry => entry.Element(arrayNs + "Value").Value);
if (xamlInfo.TryGetValue(typeName, out var rv))
{
yield return new Uri($"avares://{asm}{rv}");
yield break;
}
}
}
yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);
}
public static object Parse(string xaml, Assembly localAssembly = null) public static object Parse(string xaml, Assembly localAssembly = null)
=> new AvaloniaXamlLoader().Load(xaml, localAssembly); => new AvaloniaXamlLoader().Load(xaml, localAssembly);

99
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs

@ -1,99 +0,0 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.PortableXaml;
using Portable.Xaml;
namespace Avalonia.Markup.Xaml.Converters
{
internal class AvaloniaEventConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var text = value as string;
if (text != null)
{
var rootObjectProvider = context.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
var destinationTypeProvider = context.GetService(typeof(IDestinationTypeProvider)) as IDestinationTypeProvider;
if (rootObjectProvider != null && destinationTypeProvider != null)
{
var target = rootObjectProvider.RootObject;
var eventType = destinationTypeProvider.GetDestinationType();
var eventParameters = eventType.GetRuntimeMethods().First(r => r.Name == "Invoke").GetParameters();
// go in reverse to match System.Xaml behaviour
var methods = target.GetType().GetRuntimeMethods().Reverse();
// find based on exact match parameter types first
foreach (var method in methods)
{
if (method.Name != text)
continue;
var parameters = method.GetParameters();
if (eventParameters.Length != parameters.Length)
continue;
if (parameters.Length == 0)
return method.CreateDelegate(eventType, target);
for (int i = 0; i < parameters.Length; i++)
{
var param = parameters[i];
var eventParam = eventParameters[i];
if (param.ParameterType != eventParam.ParameterType)
break;
if (i == parameters.Length - 1)
return method.CreateDelegate(eventType, target);
}
}
// EnhancedXaml: Find method with compatible base class parameters
foreach (var method in methods)
{
if (method.Name != text)
continue;
var parameters = method.GetParameters();
if (parameters.Length == 0 || eventParameters.Length != parameters.Length)
continue;
for (int i = 0; i < parameters.Length; i++)
{
var param = parameters[i];
var eventParam = eventParameters[i];
if (!param.ParameterType.GetTypeInfo().IsAssignableFrom(eventParam.ParameterType.GetTypeInfo()))
break;
if (i == parameters.Length - 1)
return method.CreateDelegate(eventType, target);
}
}
var contextProvider = (IXamlSchemaContextProvider)context.GetService(typeof(IXamlSchemaContextProvider));
var avaloniaContext = (AvaloniaXamlSchemaContext)contextProvider.SchemaContext;
if (avaloniaContext.IsDesignMode)
{
// We want to ignore missing events in the designer, so if event handler
// wasn't found create an empty delegate.
var lambdaExpression = Expression.Lambda(
eventType,
Expression.Empty(),
eventParameters.Select(x => Expression.Parameter(x.ParameterType)));
return lambdaExpression.Compile();
}
else
{
throw new XamlObjectWriterException($"Referenced value method {text} in type {target.GetType()} indicated by event {eventType.FullName} was not found");
}
}
}
return base.ConvertFrom(context, culture, value);
}
}
}

1
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@ -11,7 +11,6 @@ using Avalonia.Markup.Xaml.Parsers;
using Avalonia.Markup.Xaml.Templates; using Avalonia.Markup.Xaml.Templates;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Utilities; using Avalonia.Utilities;
using Portable.Xaml.ComponentModel;
namespace Avalonia.Markup.Xaml.Converters namespace Avalonia.Markup.Xaml.Converters
{ {

3
src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs

@ -8,8 +8,7 @@ using Avalonia.Platform;
namespace Avalonia.Markup.Xaml.Converters namespace Avalonia.Markup.Xaml.Converters
{ {
using Portable.Xaml.ComponentModel; using System.ComponentModel;
using System.ComponentModel;
public class BitmapTypeConverter : TypeConverter public class BitmapTypeConverter : TypeConverter
{ {

1
src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs

@ -7,7 +7,6 @@ using System.Globalization;
using Avalonia.Media; using Avalonia.Media;
using Portable.Xaml.ComponentModel;
namespace Avalonia.Markup.Xaml.Converters namespace Avalonia.Markup.Xaml.Converters
{ {

1
src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs

@ -9,7 +9,6 @@ using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters namespace Avalonia.Markup.Xaml.Converters
{ {
using Portable.Xaml.ComponentModel;
using System.ComponentModel; using System.ComponentModel;
public class IconTypeConverter : TypeConverter public class IconTypeConverter : TypeConverter

89
src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs

@ -1,89 +0,0 @@
using System;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters
{
public class NullableTypeConverter<T> : TypeConverter where T : TypeConverter, new()
{
private TypeConverter _inner;
public NullableTypeConverter()
{
_inner = new T();
}
public NullableTypeConverter(TypeConverter inner)
{
_inner = inner;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value == null)
return null;
return _inner.ConvertTo(context, culture, value, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
return null;
if (value as string == "")
return null;
return _inner.ConvertFrom(context, culture, value);
}
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
{
return _inner.CreateInstance(context, propertyValues);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return _inner.GetStandardValuesSupported(context);
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return _inner.GetStandardValuesExclusive(context);
}
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
return _inner.GetCreateInstanceSupported(context);
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return _inner.GetPropertiesSupported(context);
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return _inner.GetStandardValues(context);
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
return _inner.GetProperties(context, value, attributes);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return _inner.CanConvertTo(context, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return _inner.CanConvertFrom(context, sourceType);
}
public override bool IsValid(ITypeDescriptorContext context, object value)
{
return _inner.IsValid(context, value);
}
}
}

79
src/Markup/Avalonia.Markup.Xaml/Converters/ParseTypeConverter.cs

@ -1,79 +0,0 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
namespace Avalonia.Markup.Xaml.Converters
{
/// <summary>
/// Base class for type converters which call a static Parse method.
/// </summary>
public abstract class ParseTypeConverter : TypeConverter
{
protected const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static;
protected static readonly Type[] StringParameter = new[] { typeof(string) };
protected static readonly Type[] StringIFormatProviderParameters = new[] { typeof(string), typeof(IFormatProvider) };
/// <summary>
/// Checks whether a type has a suitable Parse method.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>True if the type has a suitable parse method, otherwise false.</returns>
public static bool HasParseMethod(Type type)
{
return type.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null) != null ||
type.GetMethod("Parse", PublicStatic, null, StringParameter, null) != null;
}
}
/// <summary>
/// A type converter which calls a static Parse method.
/// </summary>
/// <typeparam name="T">The type with the Parse method.</typeparam>
public class ParseTypeConverter<T> : ParseTypeConverter
{
private static Func<string, T> _parse;
private static Func<string, IFormatProvider, T> _parseWithFormat;
static ParseTypeConverter()
{
var method = typeof(T).GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null);
if (method != null)
{
_parseWithFormat = (Func<string, IFormatProvider, T>)method
.CreateDelegate(typeof(Func<string, IFormatProvider, T>));
return;
}
method = typeof(T).GetMethod("Parse", PublicStatic, null, StringParameter, null);
if (method != null)
{
_parse = (Func<string, T>)method.CreateDelegate(typeof(Func<string, T>));
}
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value != null)
{
if (_parse != null)
{
return _parse(value.ToString());
}
else if (_parseWithFormat != null)
{
return _parseWithFormat(value.ToString(), culture);
}
}
return null;
}
}
}

27
src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs

@ -1,27 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Globalization;
using Avalonia.Markup.Parsers;
namespace Avalonia.Markup.Xaml.Converters
{
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
public class SelectorTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var parser = new SelectorParser(context.ResolveType);
return parser.Parse((string)value);
}
}
}

49
src/Markup/Avalonia.Markup.Xaml/Converters/SetterValueTypeConverter.cs

@ -1,49 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using Portable.Xaml.Markup;
using System;
using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters
{
public class SetterValueTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
object setter = context.GetService<IProvideValueTarget>().TargetObject;
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext;
return ConvertSetterValue(context, schemaContext, culture, (setter as Setter), value);
}
[Obsolete("TODO: try assosiate Setter.Value property with SetterValueTypeConverter, so far coouldn't make it :(")]
internal static object ConvertSetterValue(ITypeDescriptorContext dcontext, XamlSchemaContext context, CultureInfo info, Setter setter, object value)
{
Type targetType = setter?.Property?.PropertyType;
if (targetType == null)
{
return value;
}
var ttConv = context.GetXamlType(targetType)?.TypeConverter?.ConverterInstance;
if (ttConv != null)
{
value = ttConv.ConvertFromString(dcontext, info, value as string);
}
return value;
}
}
}

41
src/Markup/Avalonia.Markup.Xaml/Extensions.cs

@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.Runtime; using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Portable.Xaml;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml namespace Avalonia.Markup.Xaml
{ {
@ -13,42 +11,19 @@ namespace Avalonia.Markup.Xaml
public static T GetService<T>(this IServiceProvider sp) => (T)sp?.GetService(typeof(T)); public static T GetService<T>(this IServiceProvider sp) => (T)sp?.GetService(typeof(T));
public static Uri GetContextBaseUri(this IServiceProvider ctx) public static Uri GetContextBaseUri(this IServiceProvider ctx) => ctx.GetService<IUriContext>().BaseUri;
{
var properService = ctx.GetService<IUriContext>();
if (properService != null)
return properService.BaseUri;
// Ugly hack with casts
return Portable.Xaml.ComponentModel.TypeDescriptorExtensions.GetBaseUri((ITypeDescriptorContext)ctx);
}
public static T GetFirstParent<T>(this IServiceProvider ctx) where T : class public static T GetFirstParent<T>(this IServiceProvider ctx) where T : class
{ => ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().FirstOrDefault();
var parentStack = ctx.GetService<IAvaloniaXamlIlParentStackProvider>();
if (parentStack != null) public static T GetLastParent<T>(this IServiceProvider ctx) where T : class
return parentStack.Parents.OfType<T>().FirstOrDefault(); => ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().LastOrDefault();
return Portable.Xaml.ComponentModel.TypeDescriptorExtensions.GetFirstAmbientValue<T>((ITypeDescriptorContext)ctx);
}
public static T GetLastParent<T>(this IServiceProvider ctx) where T : class
{
var parentStack = ctx.GetService<IAvaloniaXamlIlParentStackProvider>();
if (parentStack != null)
return parentStack.Parents.OfType<T>().LastOrDefault();
return Portable.Xaml.ComponentModel.TypeDescriptorExtensions.GetLastOrDefaultAmbientValue<T>(
(ITypeDescriptorContext)ctx);
}
public static IEnumerable<T> GetParents<T>(this IServiceProvider sp) public static IEnumerable<T> GetParents<T>(this IServiceProvider sp)
{ {
var stack = sp.GetService<IAvaloniaXamlIlParentStackProvider>(); return sp.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>();
if (stack != null)
return stack.Parents.OfType<T>();
var context = (ITypeDescriptorContext)sp;
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext;
var ambientProvider = context.GetService<IAmbientProvider>();
return ambientProvider.GetAllAmbientValues(schemaContext.GetXamlType(typeof(T))).OfType<T>();
} }
public static Type ResolveType(this IServiceProvider ctx, string namespacePrefix, string type) public static Type ResolveType(this IServiceProvider ctx, string namespacePrefix, string type)

9
src/Markup/Avalonia.Markup.Xaml/MarkupExtension.cs

@ -0,0 +1,9 @@
using System;
namespace Avalonia.Markup.Xaml
{
public abstract class MarkupExtension
{
public abstract object ProvideValue(IServiceProvider serviceProvider);
}
}

14
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -10,14 +10,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
using Avalonia.Styling; using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
using PortableXaml;
using System.ComponentModel; using System.ComponentModel;
[MarkupExtensionReturnType(typeof(IBinding))] public class BindingExtension
public class BindingExtension : MarkupExtension
{ {
public BindingExtension() public BindingExtension()
{ {
@ -28,12 +23,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Path = path; Path = path;
} }
public override object ProvideValue(IServiceProvider serviceProvider) public Binding ProvideValue(IServiceProvider serviceProvider)
{
return ProvideTypedValue(serviceProvider);
}
public Binding ProvideTypedValue(IServiceProvider serviceProvider)
{ {
var descriptorContext = (ITypeDescriptorContext)serviceProvider; var descriptorContext = (ITypeDescriptorContext)serviceProvider;

9
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -7,13 +7,10 @@ using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
public class DynamicResourceExtension : MarkupExtension, IBinding public class DynamicResourceExtension : IBinding
{ {
private IResourceNode _anchor; private IResourceNode _anchor;
@ -28,9 +25,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public object ResourceKey { get; set; } public object ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider); public IBinding ProvideValue(IServiceProvider serviceProvider)
public IBinding ProvideTypedValue(IServiceProvider serviceProvider)
{ {
var provideTarget = serviceProvider.GetService<IProvideValueTarget>(); var provideTarget = serviceProvider.GetService<IProvideValueTarget>();

7
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs

@ -3,11 +3,10 @@
using System; using System;
using Avalonia.Data; using Avalonia.Data;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
public class RelativeSourceExtension : MarkupExtension public class RelativeSourceExtension
{ {
public RelativeSourceExtension() public RelativeSourceExtension()
{ {
@ -18,7 +17,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Mode = mode; Mode = mode;
} }
public override object ProvideValue(IServiceProvider serviceProvider) public RelativeSource ProvideValue(IServiceProvider serviceProvider)
{ {
return new RelativeSource return new RelativeSource
{ {
@ -38,4 +37,4 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public int AncestorLevel { get; set; } = 1; public int AncestorLevel { get; set; } = 1;
} }
} }

12
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs

@ -1,15 +1,13 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Controls; using Avalonia.Controls;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
/// <summary> /// <summary>
/// Loads a resource dictionary from a specified URL. /// Loads a resource dictionary from a specified URL.
/// </summary> /// </summary>
public class ResourceInclude : MarkupExtension, IResourceProvider public class ResourceInclude :IResourceProvider
{ {
private Uri _baseUri; private Uri _baseUri;
private IResourceDictionary _loaded; private IResourceDictionary _loaded;
@ -52,13 +50,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
return Loaded.TryGetResource(key, out value); return Loaded.TryGetResource(key, out value);
} }
/// <inhertidoc/> public ResourceInclude ProvideValue(IServiceProvider serviceProvider)
public override object ProvideValue(IServiceProvider serviceProvider)
{
return ProvideTypedValue(serviceProvider);
}
public ResourceInclude ProvideTypedValue(IServiceProvider serviceProvider)
{ {
var tdc = (ITypeDescriptorContext)serviceProvider; var tdc = (ITypeDescriptorContext)serviceProvider;
_baseUri = tdc?.GetContextBaseUri(); _baseUri = tdc?.GetContextBaseUri();

22
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -7,13 +7,10 @@ using System.ComponentModel;
using System.Reflection; using System.Reflection;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
public class StaticResourceExtension : MarkupExtension public class StaticResourceExtension
{ {
public StaticResourceExtension() public StaticResourceExtension()
{ {
@ -26,26 +23,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public string ResourceKey { get; set; } public string ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider) public object ProvideValue(IServiceProvider serviceProvider)
{ {
// Look upwards though the ambient context for IResourceProviders which might be able // Look upwards though the ambient context for IResourceProviders which might be able
// to give us the resource. // to give us the resource.
foreach (var resourceProvider in serviceProvider.GetParents<IResourceNode>()) foreach (var resourceProvider in serviceProvider.GetParents<IResourceNode>())
{ {
// We override XamlType.CanAssignTo in BindingXamlType so the results we get back if (resourceProvider.TryGetResource(ResourceKey, out var value))
// from GetAllAmbientValues aren't necessarily of the correct type.
if (AvaloniaXamlLoader.UseLegacyXamlLoader
&& resourceProvider is IControl control && control.StylingParent != null)
{
// If we've got to a control that has a StylingParent then it's probably
// a top level control and its StylingParent is pointing to the global
// styles. If this is case just do a FindResource on it.
return control.FindResource(ResourceKey);
}
else if (resourceProvider.TryGetResource(ResourceKey, out var value))
{ {
return value; return value;
} }

9
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleIncludeExtension.cs

@ -3,23 +3,18 @@
using Avalonia.Markup.Xaml.Styling; using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling; using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using System.ComponentModel; using System.ComponentModel;
using Portable.Xaml.Markup;
using System; using System;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
[MarkupExtensionReturnType(typeof(IStyle))] public class StyleIncludeExtension
public class StyleIncludeExtension : MarkupExtension
{ {
public StyleIncludeExtension() public StyleIncludeExtension()
{ {
} }
public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider); public IStyle ProvideValue(IServiceProvider serviceProvider)
public IStyle ProvideTypedValue(IServiceProvider serviceProvider)
{ {
return new StyleInclude(serviceProvider.GetContextBaseUri()) { Source = Source }; return new StyleInclude(serviceProvider.GetContextBaseUri()) { Source = Source };
} }

39
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AttributeExtensions.cs

@ -1,39 +0,0 @@
using Avalonia.Markup.Xaml.Templates;
using avm = Avalonia.Metadata;
using pm = Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.PortableXaml
{
internal static class AttributeExtensions
{
public static pm.XamlDeferLoadAttribute ToPortableXaml(this avm.TemplateContentAttribute attrib)
{
if (attrib == null)
{
return null;
}
return new pm.XamlDeferLoadAttribute(typeof(TemplateLoader), typeof(TemplateContent));
}
public static pm.AmbientAttribute ToPortableXaml(this avm.AmbientAttribute attrib)
{
if (attrib == null)
{
return null;
}
return new pm.AmbientAttribute();
}
public static pm.DependsOnAttribute ToPortableXaml(this avm.DependsOnAttribute attrib)
{
if (attrib == null)
{
return null;
}
return new pm.DependsOnAttribute(attrib.Name);
}
}
}

83
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs

@ -1,83 +0,0 @@
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Styling;
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using System;
using System.Linq;
using System.Reflection;
using avm = Avalonia.Metadata;
using pm = Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.PortableXaml
{
public class AvaloniaMemberAttributeProvider : ICustomAttributeProvider
{
public AvaloniaMemberAttributeProvider(MemberInfo info)
{
_info = info;
}
public object[] GetCustomAttributes(bool inherit)
{
throw new NotImplementedException();
}
public object[] GetCustomAttributes(Type attributeType, bool inherit)
{
Attribute result = null;
if (attributeType == typeof(pm.XamlDeferLoadAttribute))
{
result = _info.GetCustomAttribute<avm.TemplateContentAttribute>(inherit)
.ToPortableXaml();
}
else if (attributeType == typeof(pm.AmbientAttribute))
{
result = _info.GetCustomAttribute<avm.AmbientAttribute>(inherit)
.ToPortableXaml();
}
else if (attributeType == typeof(pm.DependsOnAttribute))
{
result = _info.GetCustomAttribute<avm.DependsOnAttribute>(inherit)
.ToPortableXaml();
}
else if (attributeType == typeof(TypeConverterAttribute) &&
_info.DeclaringType == typeof(Setter) &&
_info.Name == nameof(Setter.Value))
{
//actually it never comes here looks like if property type is object
//Portable.Xaml is not searching for Type Converter
result = new TypeConverterAttribute(typeof(SetterValueTypeConverter));
}
else if (attributeType == typeof(TypeConverterAttribute) && _info is EventInfo)
{
// If a type converter for `EventInfo` is registered, then use that to convert
// event handler values. This is used by the designer to override the lookup
// for event handlers with a null handler.
var eventConverter = AvaloniaTypeConverters.GetTypeConverter(typeof(EventInfo));
if (eventConverter != null)
{
result = new TypeConverterAttribute(eventConverter);
}
}
if (result == null)
{
var attr = _info.GetCustomAttributes(attributeType, inherit);
return (attr as object[]) ?? attr.ToArray();
}
else
{
return new object[] { result };
}
}
public bool IsDefined(Type attributeType, bool inherit)
{
throw new NotImplementedException();
}
private readonly MemberInfo _info;
}
}

56
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaNameScope.cs

@ -1,56 +0,0 @@
using System.Collections.Generic;
using Avalonia.Controls;
namespace Avalonia.Markup.Xaml.PortableXaml
{
internal class AvaloniaNameScope : Portable.Xaml.Markup.INameScope
{
public object Instance { get; set; }
private Dictionary<string, object> _names = new Dictionary<string, object>();
public object FindName(string name)
{
object result;
if (_names.TryGetValue(name, out result))
return result;
return null;
}
public void RegisterName(string name, object scopedElement)
{
if (scopedElement != null)
_names.Add(name, scopedElement);
//TODO: ???
//var control = scopedElement as Control;
//if (control != null)
//{
// var nameScope = (Instance as INameScope) ?? control.FindNameScope();
// if (nameScope != null)
// {
// nameScope.Register(name, scopedElement);
// }
//}
}
public void UnregisterName(string name)
{
}
public void RegisterOnNameScope(object target)
{
var nameScope = target as INameScope;
if (nameScope != null)
{
foreach (var v in _names)
{
nameScope.Register(v.Key, v.Value);
}
}
}
}
}

147
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs

@ -1,147 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.Context
{
using ClrNamespaceInfo = Tuple<string, Assembly>;
public interface IRuntimeTypeProvider
{
Type FindType(string xamlNamespace, string name, Type[] genArgs);
IEnumerable<Assembly> ReferencedAssemblies { get; }
}
public class AvaloniaRuntimeTypeProvider : IRuntimeTypeProvider
{
private const string ClrNamespace = "clr-namespace:";
// private const string AvaloniaNs = "https://github.com/avaloniaui";
private static readonly IEnumerable<Assembly> ForcedAssemblies = new[]
{
typeof(AvaloniaObject).GetTypeInfo().Assembly,
typeof(Animation.Animation).GetTypeInfo().Assembly,
typeof(Control).GetTypeInfo().Assembly,
typeof(Style).GetTypeInfo().Assembly,
typeof(DataTemplate).GetTypeInfo().Assembly,
typeof(SolidColorBrush).GetTypeInfo().Assembly,
typeof(Binding).GetTypeInfo().Assembly,
};
private Dictionary<string, HashSet<ClrNamespaceInfo>> _namespaces = new Dictionary<string, HashSet<ClrNamespaceInfo>>();
private List<Assembly> _scanned = new List<Assembly>();
public IEnumerable<Assembly> ReferencedAssemblies => _scanned;
public AvaloniaRuntimeTypeProvider()
{
ScanAssemblies(ForcedAssemblies);
ScanNewAssemblies();
}
private static bool IsClrNamespace(string ns)
{
return ns.StartsWith(ClrNamespace);
}
private static Assembly GetAssembly(string assemblyName)
{
return Assembly.Load(new AssemblyName(assemblyName));
}
private void ScanAssemblies(IEnumerable<Assembly> assemblies)
{
foreach (var assembly in assemblies)
{
var namespaces = assembly.GetCustomAttributes<XmlnsDefinitionAttribute>()
.Select(x => new { x.XmlNamespace, x.ClrNamespace })
.GroupBy(x => x.XmlNamespace);
foreach (var nsa in namespaces)
{
HashSet<ClrNamespaceInfo> reg;
if (!_namespaces.TryGetValue(nsa.Key, out reg))
{
_namespaces[nsa.Key] = reg = new HashSet<Tuple<string, Assembly>>();
}
foreach (var child in nsa)
{
reg.Add(new ClrNamespaceInfo(child.ClrNamespace, assembly));
}
}
_scanned.Add(assembly);
}
}
private void ScanNewAssemblies()
{
IEnumerable<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies();
if (assemblies != null)
{
assemblies = assemblies.Except(_scanned);
ScanAssemblies(assemblies);
}
}
private Dictionary<string, Type> _typeCache = new Dictionary<string, Type>();
public Type FindType(string xamlNamespace, string name, Type[] genArgs)
{
if (IsClrNamespace(xamlNamespace))
{
//we need to handle only xaml url namespaces for avalonia,
//the other namespaces are handled well in portable.xaml
return null;
}
string key = $"{xamlNamespace}:{name}";
Type type;
if (_typeCache.TryGetValue(key, out type))
{
return type;
}
HashSet<ClrNamespaceInfo> reg;
if (!_namespaces.TryGetValue(xamlNamespace, out reg))
{
return null;
}
if (genArgs != null)
name += "`" + genArgs.Length;
foreach (var ns in reg)
{
var n = ns.Item1 + "." + name;
var t = ns.Item2.GetType(n);
if (t != null)
{
_typeCache[key] = t;
return t;
}
}
return null;
}
}
}

117
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs

@ -1,117 +0,0 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using System;
using System.Linq;
using System.Reflection;
using avm = Avalonia.Metadata;
using pm = Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.PortableXaml
{
internal class AvaloniaTypeAttributeProvider : ICustomAttributeProvider
{
public AvaloniaTypeAttributeProvider(Type type)
{
_type = type;
}
public object[] GetCustomAttributes(bool inherit)
{
throw new NotImplementedException();
}
public object[] GetCustomAttributes(Type attributeType, bool inherit)
{
Attribute result = null;
var ti = _type.GetTypeInfo();
if (attributeType == typeof(pm.ContentPropertyAttribute))
{
result = GetContentPropertyAttribute(inherit);
}
else if (attributeType == typeof(pm.RuntimeNamePropertyAttribute))
{
if (_namedType.IsAssignableFrom(ti))
{
result = new pm.RuntimeNamePropertyAttribute(nameof(INamed.Name));
}
}
else if (attributeType == typeof(TypeConverterAttribute))
{
var builtin = AvaloniaTypeConverters.GetBuiltinTypeConverter(_type);
if (builtin != null)
result = new TypeConverterAttribute(builtin);
result = result ?? ti.GetCustomAttribute(attributeType, inherit);
if (result == null)
{
var convType = AvaloniaTypeConverters.GetTypeConverter(_type);
if (convType != null)
{
result = new TypeConverterAttribute(convType);
}
}
}
else if (attributeType == typeof(pm.AmbientAttribute))
{
result = ti.GetCustomAttribute<avm.AmbientAttribute>(inherit)
.ToPortableXaml();
}
if (result == null)
{
var attr = ti.GetCustomAttributes(attributeType, inherit);
return (attr as object[]) ?? attr.ToArray();
}
else
{
return new object[] { result };
}
}
public bool IsDefined(Type attributeType, bool inherit)
{
throw new NotImplementedException();
}
private readonly TypeInfo _namedType = typeof(INamed).GetTypeInfo();
private readonly Type _type;
private Attribute GetContentPropertyAttribute(bool inherit)
{
var type = _type;
while (type != null)
{
var properties = type.GetTypeInfo().DeclaredProperties
.Where(x => x.GetCustomAttribute<avm.ContentAttribute>() != null);
string result = null;
foreach (var property in properties)
{
if (result != null)
{
throw new Exception($"Content property defined more than once on {type}.");
}
result = property.Name;
}
if (result != null)
{
return new pm.ContentPropertyAttribute(result);
}
type = inherit ? type.GetTypeInfo().BaseType : null;
}
return null;
}
}
}

31
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlContext.cs

@ -1,31 +0,0 @@
using Portable.Xaml;
using Portable.Xaml.Markup;
using System;
using System.Reflection;
namespace Avalonia.Markup.Xaml.PortableXaml
{
public class AvaloniaXamlContext : IUriContext
{
private AvaloniaXamlContext()
{
}
public Assembly LocalAssembly { get; private set; }
public Uri BaseUri { get; set; }
public object RootInstance { get; private set; }
internal static AvaloniaXamlContext For(XamlXmlReaderSettings sett,
object rootInstance)
{
return new AvaloniaXamlContext()
{
BaseUri = sett.BaseUri,
LocalAssembly = sett.LocalAssembly,
RootInstance = rootInstance
};
}
}
}

222
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs

@ -1,222 +0,0 @@
using Avalonia.Data;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Portable.Xaml.Schema;
namespace Avalonia.Markup.Xaml.PortableXaml
{
class AvaloniaXamlObjectWriter : XamlObjectWriter
{
private static Dictionary<XamlDirective, string> DesignDirectives = new Dictionary<string, string>
{
["DataContext"] = "DataContext",
["DesignWidth"] = "Width", ["DesignHeight"] = "Height", ["PreviewWith"] = "PreviewWith"
}
.ToDictionary(p => new XamlDirective(
new[] {"http://schemas.microsoft.com/expression/blend/2008"}, p.Key,
XamlLanguage.Object, null, AllowedMemberLocations.Attribute), p => p.Value);
private readonly AvaloniaXamlSchemaContext _schemaContext;
public static AvaloniaXamlObjectWriter Create(
AvaloniaXamlSchemaContext schemaContext,
AvaloniaXamlContext context,
IAmbientProvider parentAmbientProvider = null)
{
var nameScope = new AvaloniaNameScope { Instance = context?.RootInstance };
var writerSettings = new XamlObjectWriterSettings()
{
ExternalNameScope = nameScope,
RegisterNamesOnExternalNamescope = true,
RootObjectInstance = context?.RootInstance
};
return new AvaloniaXamlObjectWriter(schemaContext,
writerSettings.WithContext(context),
nameScope,
parentAmbientProvider);
}
private readonly DelayedValuesHelper _delayedValuesHelper = new DelayedValuesHelper();
private AvaloniaNameScope _nameScope;
private AvaloniaXamlObjectWriter(
AvaloniaXamlSchemaContext schemaContext,
XamlObjectWriterSettings settings,
AvaloniaNameScope nameScope,
IAmbientProvider parentAmbientProvider)
: base(schemaContext, settings, parentAmbientProvider)
{
_nameScope = nameScope;
_schemaContext = schemaContext;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_nameScope != null && Result != null)
{
_nameScope.RegisterOnNameScope(Result);
}
}
base.Dispose(disposing);
}
public void ApplyAllDelayedProperties()
{
//HACK: We need this because Begin/EndInit ordering is broken
_delayedValuesHelper.ApplyAll();
}
protected internal override void OnAfterProperties(object value)
{
_delayedValuesHelper.EndInit(value);
base.OnAfterProperties(value);
}
protected internal override void OnBeforeProperties(object value)
{
if (value != null)
_delayedValuesHelper.BeginInit(value);
base.OnBeforeProperties(value);
}
protected internal override bool OnSetValue(object target, XamlMember member, object value)
{
if (_delayedValuesHelper.TryAdd(target, member, value))
{
return true;
}
return base.OnSetValue(target, member, value);
}
public override void WriteStartMember(XamlMember property)
{
foreach(var d in DesignDirectives)
if (property == d.Key && _schemaContext.IsDesignMode)
{
base.WriteStartMember(new XamlMember(d.Value,
typeof(Design).GetMethod("Get" + d.Value, BindingFlags.Static | BindingFlags.Public),
typeof(Design).GetMethod("Set" + d.Value, BindingFlags.Static | BindingFlags.Public),
SchemaContext));
return;
}
base.WriteStartMember(property);
}
private class DelayedValuesHelper
{
private int _cnt;
private HashSet<object> _targets = new HashSet<object>();
private IList<DelayedValue> _values = new List<DelayedValue>();
private IEnumerable<DelayedValue> Values => _values;
public void BeginInit(object target)
{
++_cnt;
AddTargetIfNeeded(target);
}
public void EndInit(object target)
{
--_cnt;
if (_cnt == 0)
{
ApplyAll();
}
}
public bool TryAdd(object target, XamlMember member, object value)
{
if (value is IBinding)
{
Add(new DelayedValue(target, member, value));
return true;
}
return false;
}
private void Add(DelayedValue value)
{
_values.Add(value);
var target = value.Target;
if (!_targets.Contains(value.Target))
{
_targets.Add(target);
(target as ISupportInitialize)?.BeginInit();
}
}
private void AddTargetIfNeeded(object target)
{
if (!_targets.Contains(target))
{
Add(new DelayedValue(target, null, null));
}
}
public void ApplyAll()
{
//TODO: revisit this
//apply delayed values and clear
//that's the last object let's set all delayed bindings
foreach (var dv in Values.Where(v => v.Member != null))
{
dv.Member.Invoker.SetValue(dv.Target, dv.Value);
}
//TODO: check/add some order of end init
//currently we are sending end init in the order of
//objects creation
foreach (var v in Values)
{
var target = v.Target;
if (_targets.Contains(target))
{
_targets.Remove(target);
(target as ISupportInitialize)?.EndInit();
}
}
_targets.Clear();
_values.Clear();
}
private class DelayedValue
{
public DelayedValue(object target, XamlMember member, object value)
{
Target = target;
Member = member;
Value = value;
}
public XamlMember Member { get; }
public object Target { get; }
public object Value { get; }
}
}
}
}

327
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs

@ -1,327 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Context;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Styling;
using Portable.Xaml;
namespace Avalonia.Markup.Xaml.PortableXaml
{
internal class AvaloniaXamlSchemaContext : XamlSchemaContext
{
private static AvaloniaXamlSchemaContext s_instance;
private static AvaloniaXamlSchemaContext s_designInstance;
public static AvaloniaXamlSchemaContext Instance
{
get
{
if (s_instance == null)
{
s_instance = Create();
}
return s_instance;
}
}
public static AvaloniaXamlSchemaContext DesignInstance
{
get
{
if (s_designInstance == null)
{
s_designInstance = Create();
s_designInstance.IsDesignMode = true;
}
return s_designInstance;
}
}
public bool IsDesignMode { get; private set; }
public static AvaloniaXamlSchemaContext Create(IRuntimeTypeProvider typeProvider = null)
{
return new AvaloniaXamlSchemaContext(typeProvider ?? new AvaloniaRuntimeTypeProvider());
}
private AvaloniaXamlSchemaContext(IRuntimeTypeProvider typeProvider)
//better not set the references assemblies
//TODO: check this on iOS
//: base(typeProvider.ReferencedAssemblies)
{
Contract.Requires<ArgumentNullException>(typeProvider != null);
_avaloniaTypeProvider = typeProvider;
}
private IRuntimeTypeProvider _avaloniaTypeProvider;
protected override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
{
XamlType type = null;
try
{
type = ResolveXamlTypeName(xamlNamespace, name, typeArguments, false);
if (type == null)
{
type = base.GetXamlType(xamlNamespace, name, typeArguments);
}
}
catch (Exception e)
{
//TODO: log or wrap exception
throw e;
}
return type;
}
private XamlType ResolveXamlTypeName(string xmlNamespace, string xmlLocalName, XamlType[] typeArguments, bool required)
{
Type[] genArgs = null;
if (typeArguments != null && typeArguments.Any())
{
genArgs = typeArguments.Select(t => t?.UnderlyingType).ToArray();
if (genArgs.Any(t => t == null))
{
return null;
}
}
// MarkupExtension type could omit "Extension" part in XML name.
Type type = _avaloniaTypeProvider.FindType(xmlNamespace,
xmlLocalName,
genArgs) ??
_avaloniaTypeProvider.FindType(xmlNamespace,
xmlLocalName + "Extension",
genArgs);
if (type != null)
{
Type extType;
if (_wellKnownExtensionTypes.TryGetValue(type, out extType))
{
type = extType;
}
}
if (type == null)
{
//let's try the simple types
//in Portable xaml like xmlns:sys='clr-namespace:System;assembly=mscorlib'
//and sys:Double is not resolved properly
return ResolveSimpleTypeName(xmlNamespace, xmlLocalName);
}
return GetXamlType(type);
}
#region Workaround for bug in Portablexaml system types like double,int etc ...
private static Type[] _simpleTypes = new Type[]
{
typeof(bool),
typeof(byte),
typeof(char),
typeof(decimal),
typeof(double),
typeof(Int16),
typeof(Int32),
typeof(Int64),
typeof(float),
typeof(string),
typeof(TimeSpan),
typeof(Uri),
};
private static Dictionary<Tuple<string, string>, XamlType> _simpleXamlTypes;
//in Portable xaml like xmlns:sys='clr-namespace:System;assembly=mscorlib'
//and sys:Double is not resolved properly
[Obsolete("TODO: remove once it's fixed in Portable.xaml")]
private static XamlType ResolveSimpleTypeName(string xmlNamespace, string xmlLocalName)
{
if (_simpleXamlTypes == null)
{
_simpleXamlTypes = new Dictionary<Tuple<string, string>, XamlType>();
foreach (var type in _simpleTypes)
{
string asmName = type.GetTypeInfo().Assembly.GetName().Name;
string ns = $"clr-namespace:{type.Namespace};assembly={asmName}";
var xamlType = XamlLanguage.AllTypes.First(t => t.UnderlyingType == type);
_simpleXamlTypes.Add(new Tuple<string, string>(ns, type.Name), xamlType);
}
}
XamlType result;
var key = new Tuple<string, string>(xmlNamespace, xmlLocalName);
_simpleXamlTypes.TryGetValue(key, out result);
return result;
}
#endregion Workaround for bug in Portablexaml system types like double,int etc ...
protected internal override ICustomAttributeProvider GetCustomAttributeProvider(Type type)
=> new AvaloniaTypeAttributeProvider(type);
protected internal override ICustomAttributeProvider GetCustomAttributeProvider(MemberInfo member)
=> new AvaloniaMemberAttributeProvider(member);
public override XamlType GetXamlType(Type type)
{
XamlType result = null;
if (_cachedTypes.TryGetValue(type, out result))
{
return result;
}
_cachedTypes[type] = result = GetAvaloniaXamlType(type) ?? base.GetXamlType(type);
return result;
}
private static readonly Dictionary<Type, Type> _wellKnownExtensionTypes = new Dictionary<Type, Type>()
{
{ typeof(Binding), typeof(BindingExtension) },
{ typeof(StyleInclude), typeof(StyleIncludeExtension) },
};
private XamlType GetAvaloniaXamlType(Type type)
{
//if type is extension get the original type to check
var origType = _wellKnownExtensionTypes.FirstOrDefault(v => v.Value == type).Key;
if (typeof(IBinding).GetTypeInfo().IsAssignableFrom((origType ?? type).GetTypeInfo()))
{
return new BindingXamlType(type, this);
}
if (origType != null ||
typeof(AvaloniaObject).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
{
return new AvaloniaXamlType(type, this);
}
return null;
}
protected internal override XamlMember GetAttachableProperty(string attachablePropertyName, MethodInfo getter, MethodInfo setter)
{
var key = MemberKey.Create(getter ?? setter, attachablePropertyName, "a");
XamlMember result;
if (_cachedMembers.TryGetValue(key, out result))
{
return result;
}
var type = (getter ?? setter).DeclaringType;
var prop = AvaloniaPropertyRegistry.Instance.FindRegistered(type, attachablePropertyName);
if (prop != null)
{
result = new AvaloniaAttachedPropertyXamlMember(
prop, attachablePropertyName,
getter, setter, this);
}
if (result == null)
{
result = base.GetAttachableProperty(attachablePropertyName, getter, setter);
}
return _cachedMembers[key] = result;
}
protected internal override XamlMember GetProperty(PropertyInfo pi)
{
Type objType = pi.DeclaringType;
string name = pi.Name;
XamlMember result;
var key = MemberKey.Create(pi, "p");
if (_cachedMembers.TryGetValue(key, out result))
{
return result;
}
var avProp = AvaloniaPropertyRegistry.Instance.FindRegistered(objType, name);
if (avProp != null)
{
result = new AvaloniaPropertyXamlMember(avProp, pi, this);
}
if (result == null)
{
result = new PropertyXamlMember(pi, this);
}
return _cachedMembers[key] = result;
}
private Dictionary<Type, XamlType> _cachedTypes = new Dictionary<Type, XamlType>();
private Dictionary<MemberKey, XamlMember> _cachedMembers = new Dictionary<MemberKey, XamlMember>();
private struct MemberKey
{
public static MemberKey Create(MemberInfo m, string name, string memberType)
{
return new MemberKey(m.DeclaringType, name, memberType);
}
public static MemberKey Create(MemberInfo m, string memberType)
{
return Create(m, m.Name, memberType);
}
public MemberKey(Type type, object member, string memberType)
{
Type = type;
Member = member;
MemberType = memberType;
}
public Type Type { get; }
public object Member { get; }
public string MemberType { get; }
public override string ToString()
{
return $"{MemberType}:{Type.Namespace}:{Type.Name}.{Member}";
}
}
public override bool TryGetCompatibleXamlNamespace(string xamlNamespace, out string compatibleNamespace)
{
//Forces XamlXmlReader to not ignore our namespace in design mode if mc:Ignorable is set
if (IsDesignMode &&
xamlNamespace == "http://schemas.microsoft.com/expression/blend/2008")
{
compatibleNamespace = xamlNamespace;
return true;
}
return base.TryGetCompatibleXamlNamespace(xamlNamespace, out compatibleNamespace);
}
}
}

388
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs

@ -1,388 +0,0 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Data;
using Avalonia.Metadata;
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.Schema;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Xml.Serialization;
namespace Avalonia.Markup.Xaml.PortableXaml
{
using Converters;
using PropertyKey = Tuple<Type, string>;
public class AvaloniaXamlType : XamlType
{
static readonly AvaloniaPropertyTypeConverter propertyTypeConverter = new AvaloniaPropertyTypeConverter();
public AvaloniaXamlType(Type underlyingType, XamlSchemaContext schemaContext) :
base(underlyingType, schemaContext)
{
}
protected override XamlMember LookupAttachableMember(string name)
{
var m = base.LookupAttachableMember(name);
if (m == null)
{
// Might be an AddOwnered attached property.
var avProp = AvaloniaPropertyRegistry.Instance.FindRegistered(UnderlyingType, name);
if (avProp?.IsAttached == true)
{
return new AvaloniaPropertyXamlMember(avProp, this);
}
}
return m;
}
protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
{
var m = base.LookupMember(name, skipReadOnlyCheck);
if (m == null && !name.Contains("."))
{
//so far Portable.xaml haven't found the member/property
//but what if we have AvaloniaProperty
//without setter and/or without getter
//let's try to find the AvaloniaProperty as a fallback
var avProp = AvaloniaPropertyRegistry.Instance.FindRegistered(UnderlyingType, name);
if (avProp != null && !(skipReadOnlyCheck && avProp.IsReadOnly))
{
m = new AvaloniaPropertyXamlMember(avProp, this);
}
}
return m;
}
}
public class BindingXamlType : XamlType
{
private static List<Type> _notAssignable =
new List<Type>
{
typeof (IXmlSerializable)
};
public BindingXamlType(Type underlyingType, XamlSchemaContext schemaContext) :
base(underlyingType, schemaContext)
{
}
public override bool CanAssignTo(XamlType xamlType)
{
return !_notAssignable.Contains(xamlType.UnderlyingType);
}
}
public class PropertyXamlMember : XamlMember
{
public PropertyXamlMember(PropertyInfo propertyInfo, XamlSchemaContext schemaContext)
: base(propertyInfo, schemaContext)
{
}
protected PropertyXamlMember(string attachablePropertyName,
MethodInfo getter, MethodInfo setter, XamlSchemaContext schemaContext)
: base(attachablePropertyName, getter, setter, schemaContext)
{
}
protected PropertyXamlMember(string name, XamlType declaringType, bool isAttachable)
: base(name, declaringType, isAttachable)
{
}
private bool IsReadOnlyCollectionProperty
{
get
{
//Collection properties like:
//MultiBinding.Bindings, Panel.Children, Control.Styles,
//need to be readonly for Portable.Xaml
//Collection properties like:
//Grid.RowDefinitions, Grid.ColumnDefinitions
//need to be set only once, and subsequent changes to be
//added to collection
//TODO: investigate is this good enough as solution ???
//We can add some ReadOnyXamlPropertyCollectionAttribute to cover this
return Type.IsCollection;
}
}
private bool HasCollectionTypeConverter
{
get
{
return Type.IsCollection && Type.TypeConverter != null;
}
}
protected override MethodInfo LookupUnderlyingSetter()
{
//if we have content property a list
//we have some issues in portable.xaml
//but if the list is read only, this is solving the problem
if (IsReadOnlyCollectionProperty &&
!HasCollectionTypeConverter)
{
return null;
}
return base.LookupUnderlyingSetter();
}
protected override XamlMemberInvoker LookupInvoker()
{
//if we have a IList property and it has TypeConverter
//Portable.xaml need to be able to set the value
//but instead directly set new value we'll sync the lists
bool updateListInsteadSet = HasCollectionTypeConverter;
return new PropertyInvoker(this)
{
UpdateListInsteadSet = updateListInsteadSet
};
}
protected override bool LookupIsUnknown() => false;
protected override XamlType LookupType()
{
var propType = GetPropertyType();
if (propType != null)
{
if (propType == typeof(IEnumerable))
{
//TODO: Portable.xaml is not handling well IEnumerable
//let's threat IEnumerable property as list
//revisit this when smarter solution is found
propType = typeof(IList);
}
return DeclaringType.SchemaContext.GetXamlType(propType);
}
return base.LookupType();
}
protected virtual Type GetPropertyType()
{
return (UnderlyingMember as PropertyInfo)?.PropertyType;
}
private IList<XamlMember> _dependsOn;
protected override IList<XamlMember> LookupDependsOn()
{
if (_dependsOn == null)
{
var attrib = UnderlyingMember.GetCustomAttribute<DependsOnAttribute>(true);
if (attrib != null)
{
var member = DeclaringType.GetMember(attrib.Name);
_dependsOn = new XamlMember[] { member };
}
else
{
_dependsOn = base.LookupDependsOn();
}
}
return _dependsOn;
}
private PropertyKey PropertyKey()
=> new PropertyKey(DeclaringType.UnderlyingType, Name);
private class PropertyInvoker : XamlMemberInvoker
{
public bool UpdateListInsteadSet { get; set; } = false;
public PropertyInvoker(XamlMember member) : base(member)
{
}
public override void SetValue(object instance, object value)
{
//can't make it work to assign TypeConverter to Setter.Value
//so we need it hard coded
//TODO: try to assosiate TypeConverter with Setter.Value
//and remove this lines
if (instance is Setter &&
Member.Name == nameof(Setter.Value) &&
value is string)
{
value = SetterValueTypeConverter.ConvertSetterValue(null,
Member.DeclaringType.SchemaContext, CultureInfo.InvariantCulture,
instance as Setter,
value);
}
if (UpdateListInsteadSet &&
value != null &&
UpdateListInsteadSetValue(instance, value))
{
return;
}
base.SetValue(instance, value);
}
private bool UpdateListInsteadSetValue(object instance, object value)
{
object old = GetValue(instance);
if (Equals(old, value))
{
//don't set the same collection value
return true;
}
else if (old is IList && value is IList)
{
var oldList = (IList)old;
var curList = (IList)value;
oldList.Clear();
foreach (object item in curList)
{
oldList.Add(item);
}
return true;
}
return false;
}
}
}
public class AvaloniaPropertyXamlMember : PropertyXamlMember
{
private bool? _assignBinding;
public bool AssignBinding => (bool)(_assignBinding ?? (_assignBinding = UnderlyingMember?.GetCustomAttribute<AssignBindingAttribute>() != null));
public AvaloniaProperty Property { get; }
public AvaloniaPropertyXamlMember(AvaloniaProperty property,
PropertyInfo propertyInfo,
XamlSchemaContext schemaContext) :
base(propertyInfo, schemaContext)
{
Property = property;
}
public AvaloniaPropertyXamlMember(AvaloniaProperty property, XamlType type) :
base(property.Name, type, false)
{
Property = property;
}
protected AvaloniaPropertyXamlMember(AvaloniaProperty property,
string attachablePropertyName,
MethodInfo getter, MethodInfo setter, XamlSchemaContext schemaContext)
: base(attachablePropertyName, getter, setter, schemaContext)
{
Property = property;
}
protected override XamlMemberInvoker LookupInvoker()
{
return new AvaloniaPropertyInvoker(this);
}
protected override bool LookupIsReadOnly()
{
return Property.IsReadOnly;
}
protected override Type GetPropertyType()
{
return Property.PropertyType;
}
private class AvaloniaPropertyInvoker : XamlMemberInvoker
{
public AvaloniaPropertyInvoker(XamlMember member) : base(member)
{
}
public override void SetValue(object instance, object value)
{
if (Property != null)
{
var obj = ((IAvaloniaObject)instance);
if (value is IBinding)
{
if (!Member.AssignBinding)
ApplyBinding(obj, (IBinding)value);
else
obj.SetValue(Property, value);
}
else
{
obj.SetValue(Property, value);
}
}
else
{
base.SetValue(instance, value);
}
}
public override object GetValue(object instance)
{
if (Property != null && !Property.IsAttached)
{
return ((IAvaloniaObject)instance).GetValue(Property);
}
else
{
return base.GetValue(instance);
}
}
private void ApplyBinding(IAvaloniaObject obj, IBinding binding)
{
var control = obj as IControl;
var property = Property;
if (control != null && property != Control.DataContextProperty)
DelayedBinding.Add(control, property, binding);
else
obj.Bind(property, binding);
}
private AvaloniaProperty Property => Member.Property;
private new AvaloniaPropertyXamlMember Member =>
(AvaloniaPropertyXamlMember)base.Member;
}
}
public class AvaloniaAttachedPropertyXamlMember : AvaloniaPropertyXamlMember
{
public AvaloniaAttachedPropertyXamlMember(AvaloniaProperty property,
string attachablePropertyName,
MethodInfo getter, MethodInfo setter,
XamlSchemaContext schemaContext)
: base(property, attachablePropertyName, getter, setter, schemaContext)
{
}
}
}

101
src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs

@ -1,101 +0,0 @@
using Avalonia.Markup.Xaml.PortableXaml;
using Portable.Xaml.Markup;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.ComponentModel;
namespace Portable.Xaml.ComponentModel
{
internal static class TypeDescriptorExtensions
{
/// <summary>
/// Gets the service from ITypeDescriptorContext
/// usually in TypeConverter in xaml reader context
/// examples:
/// context.GetService&lt;IXamlTypeResolver&gt;()
/// context.GetService&lt;IXamlNamespaceResolver&gt;()
/// context.GetService&lt;IXamlNameProvider&gt;()
/// context.GetService&lt;INamespacePrefixLookup&gt;()
/// context.GetService&lt;IXamlSchemaContextProvider&gt;()
/// context.GetService&lt;IRootObjectProvider&gt;()
/// context.GetService&lt;IProvideValueTarget&gt;()
/// </summary>
/// <typeparam name="T">Service Type</typeparam>
/// <param name="ctx">The TypeDescriptor context.</param>
/// <returns></returns>
public static T GetService<T>(this ITypeDescriptorContext ctx) where T : class
{
return ctx.GetService(typeof(T)) as T;
}
public static Type ResolveType(this ITypeDescriptorContext ctx, string namespacePrefix, string type)
{
var tr = ctx.GetService<IXamlTypeResolver>();
string name = string.IsNullOrEmpty(namespacePrefix) ? type : $"{namespacePrefix}:{type}";
return tr?.Resolve(name);
}
public static T GetFirstAmbientValue<T>(this ITypeDescriptorContext ctx) where T : class
{
var amb = ctx.GetService<IAmbientProvider>();
var sc = ctx.GetService<IXamlSchemaContextProvider>().SchemaContext;
// Because GetFirstParent uses XamlType.CanAssignTo it returns values that
// aren't actually of the correct type. Use GetAllAmbientValues instead.
return amb.GetAllAmbientValues(sc.GetXamlType(typeof(T))).OfType<T>().FirstOrDefault();
}
public static T GetLastOrDefaultAmbientValue<T>(this ITypeDescriptorContext ctx) where T : class
{
return ctx.GetAllAmbientValues<T>().LastOrDefault() as T;
}
public static IEnumerable<T> GetAllAmbientValues<T>(this ITypeDescriptorContext ctx) where T : class
{
var amb = ctx.GetService<IAmbientProvider>();
var sc = ctx.GetService<IXamlSchemaContextProvider>().SchemaContext;
return amb.GetAllAmbientValues(sc.GetXamlType(typeof(T))).OfType<T>();
}
public static Uri GetBaseUri(this ITypeDescriptorContext ctx)
{
return ctx.GetWriterSettings()?.Context?.BaseUri;
}
public static Assembly GetLocalAssembly(this ITypeDescriptorContext ctx)
{
return ctx.GetWriterSettings()?.Context?.LocalAssembly;
}
public static AvaloniaXamlContext GetAvaloniaXamlContext(this ITypeDescriptorContext ctx)
{
return ctx.GetWriterSettings()?.Context;
}
public static XamlObjectWriterSettings WithContext(this XamlObjectWriterSettings settings, AvaloniaXamlContext context)
{
return new AvaloniaXamlObjectWriterSettings(settings, context);
}
private static AvaloniaXamlObjectWriterSettings GetWriterSettings(this ITypeDescriptorContext ctx)
{
return ctx.GetService<IXamlObjectWriterFactory>().GetParentSettings() as AvaloniaXamlObjectWriterSettings;
}
private class AvaloniaXamlObjectWriterSettings : XamlObjectWriterSettings
{
public AvaloniaXamlObjectWriterSettings(XamlObjectWriterSettings settings, AvaloniaXamlContext context)
: base(settings)
{
Context = context;
}
public AvaloniaXamlContext Context { get; }
}
}
}

1
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@ -1 +0,0 @@
Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975

32
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@ -7,42 +7,16 @@ using System.Collections.Generic;
namespace Avalonia.Markup.Xaml.Templates namespace Avalonia.Markup.Xaml.Templates
{ {
using Portable.Xaml;
public static class TemplateContent
public class TemplateContent
{ {
public TemplateContent(IEnumerable<NamespaceDeclaration> namespaces, XamlReader reader,
IAmbientProvider ambientProvider)
{
ParentAmbientProvider = ambientProvider;
List = new XamlNodeList(reader.SchemaContext);
//we need to rpeserve all namespace and prefixes to writer
//otherwise they are lost. a bug in Portable.xaml or by design ??
foreach (var ns in namespaces)
{
List.Writer.WriteNamespace(ns);
}
XamlServices.Transform(reader, List.Writer);
}
public XamlNodeList List { get; }
private IAmbientProvider ParentAmbientProvider { get; }
public IControl Load()
{
return (IControl)AvaloniaXamlLoader.LoadFromReader(List.GetReader(), parentAmbientProvider: ParentAmbientProvider);
}
public static IControl Load(object templateContent) public static IControl Load(object templateContent)
{ {
if (templateContent is Func<IServiceProvider, object> direct) if (templateContent is Func<IServiceProvider, object> direct)
{ {
return (IControl)direct(null); return (IControl)direct(null);
} }
return ((TemplateContent)templateContent).Load(); throw new ArgumentException(nameof(templateContent));
} }
} }
} }

26
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs

@ -1,26 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Markup.Xaml.Templates
{
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using System;
public class TemplateLoader : XamlDeferringLoader
{
public override object Load(XamlReader xamlReader, IServiceProvider serviceProvider)
{
var tdc = (ITypeDescriptorContext)serviceProvider;
var ns = tdc.GetService<IXamlNamespaceResolver>();
var ambientProvider = tdc.GetService<IAmbientProvider>();
return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader, ambientProvider);
}
public override XamlReader Save(object value, IServiceProvider serviceProvider)
{
return ((TemplateContent)value).List.GetReader();
}
}
}

11
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -28,15 +28,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XmlnsAttributes = XmlnsAttributes =
{ {
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"), typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"),
typeSystem.FindType("Portable.Xaml.Markup.XmlnsDefinitionAttribute")
}, },
ContentAttributes = ContentAttributes =
{ {
typeSystem.GetType("Avalonia.Metadata.ContentAttribute") typeSystem.GetType("Avalonia.Metadata.ContentAttribute")
}, },
ProvideValueTarget = typeSystem.GetType("Portable.Xaml.Markup.IProvideValueTarget"), ProvideValueTarget = typeSystem.GetType("Avalonia.Markup.Xaml.IProvideValueTarget"),
RootObjectProvider = typeSystem.GetType("Portable.Xaml.IRootObjectProvider"), RootObjectProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IRootObjectProvider"),
UriContextProvider = typeSystem.GetType("Portable.Xaml.Markup.IUriContext"), UriContextProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IUriContext"),
ParentStackProvider = ParentStackProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlParentStackProvider"), typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlParentStackProvider"),
@ -47,7 +46,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV1"), runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV1"),
UsableDuringInitializationAttributes = UsableDuringInitializationAttributes =
{ {
typeSystem.GetType("Portable.Xaml.Markup.UsableDuringInitializationAttribute"),
typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"), typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"),
}, },
InnerServiceProviderFactoryMethod = InnerServiceProviderFactoryMethod =
@ -79,15 +77,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
void Add(string type, string conv) void Add(string type, string conv)
=> AddType(typeSystem.GetType(type), typeSystem.GetType(conv)); => AddType(typeSystem.GetType(type), typeSystem.GetType(conv));
//Add("Avalonia.AvaloniaProperty","Avalonia.Markup.Xaml.Converters.AvaloniaPropertyTypeConverter");
Add("Avalonia.Media.Imaging.IBitmap","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter"); Add("Avalonia.Media.Imaging.IBitmap","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter");
var ilist = typeSystem.GetType("System.Collections.Generic.IList`1"); var ilist = typeSystem.GetType("System.Collections.Generic.IList`1");
AddType(ilist.MakeGenericType(typeSystem.GetType("Avalonia.Point")), AddType(ilist.MakeGenericType(typeSystem.GetType("Avalonia.Point")),
typeSystem.GetType("Avalonia.Markup.Xaml.Converters.PointsListTypeConverter")); typeSystem.GetType("Avalonia.Markup.Xaml.Converters.PointsListTypeConverter"));
Add("Avalonia.Controls.Templates.IMemberSelector", Add("Avalonia.Controls.Templates.IMemberSelector",
"Avalonia.Markup.Xaml.Converters.MemberSelectorTypeConverter"); "Avalonia.Markup.Xaml.Converters.MemberSelectorTypeConverter");
Add("Avalonia.Styling.Selector","Avalonia.Markup.Xaml.Converters.SelectorTypeConverter");
Add("Avalonia.Controls.WindowIcon","Avalonia.Markup.Xaml.Converters.IconTypeConverter"); Add("Avalonia.Controls.WindowIcon","Avalonia.Markup.Xaml.Converters.IconTypeConverter");
Add("System.Globalization.CultureInfo", "System.ComponentModel.CultureInfoConverter"); Add("System.Globalization.CultureInfo", "System.ComponentModel.CultureInfoConverter");
Add("System.Uri", "Avalonia.Markup.Xaml.Converters.AvaloniaUriTypeConverter"); Add("System.Uri", "Avalonia.Markup.Xaml.Converters.AvaloniaUriTypeConverter");

7
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@ -16,8 +16,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (!(node is XamlIlAstObjectNode on if (!(node is XamlIlAstObjectNode on
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter")) && on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node; return node;
var parent = context.ParentNodes().OfType<XamlIlAstObjectNode>() var parent = context.ParentNodes().OfType<XamlIlAstObjectNode>()
.FirstOrDefault(x => x.Type.GetClrType().FullName == "Avalonia.Styling.Style"); .FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style");
if (parent == null) if (parent == null)
throw new XamlIlParseException( throw new XamlIlParseException(
"Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node); "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node);
@ -53,8 +55,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value"); .OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value");
if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode) if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode)
{ {
var propType = avaloniaPropertyNode.Property.Getter?.ReturnType var propType = avaloniaPropertyNode.AvaloniaPropertyType;
?? avaloniaPropertyNode.Property.Setters.First().Parameters[0];
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0], if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
propType, out var converted)) propType, out var converted))
throw new XamlIlParseException( throw new XamlIlParseException(

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -10,6 +10,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlIlType BindingPriority { get; } public IXamlIlType BindingPriority { get; }
public IXamlIlType AvaloniaObjectExtensions { get; } public IXamlIlType AvaloniaObjectExtensions { get; }
public IXamlIlType AvaloniaProperty { get; } public IXamlIlType AvaloniaProperty { get; }
public IXamlIlType AvaloniaPropertyT { get; }
public IXamlIlType IBinding { get; } public IXamlIlType IBinding { get; }
public IXamlIlMethod AvaloniaObjectBindMethod { get; } public IXamlIlMethod AvaloniaObjectBindMethod { get; }
public IXamlIlMethod AvaloniaObjectSetValueMethod { get; } public IXamlIlMethod AvaloniaObjectSetValueMethod { get; }
@ -26,6 +27,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject"); IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject");
AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions"); AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
AvaloniaProperty = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"); AvaloniaProperty = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty");
AvaloniaPropertyT = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty`1");
BindingPriority = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.BindingPriority"); BindingPriority = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.BindingPriority");
IBinding = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.IBinding"); IBinding = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.IBinding");
IDisposable = ctx.Configuration.TypeSystem.GetType("System.IDisposable"); IDisposable = ctx.Configuration.TypeSystem.GetType("System.IDisposable");

61
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs

@ -44,7 +44,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return true; return true;
} }
public static XamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context, public static IXamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context,
string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo) string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo)
{ {
XamlIlAstNamePropertyReference forgedReference; XamlIlAstNamePropertyReference forgedReference;
@ -63,8 +63,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
xmlOwner += parsedPropertyName.owner; xmlOwner += parsedPropertyName.owner;
var tref = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true); var tref = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true);
forgedReference = new XamlIlAstNamePropertyReference(lineInfo,
tref, parsedPropertyName.name, tref); var propertyFieldName = parsedPropertyName.name + "Property";
var found = tref.Type.GetAllFields()
.FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName);
if (found == null)
throw new XamlIlParseException(
$"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo);
return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
} }
var clrProperty = var clrProperty =
@ -75,13 +81,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
clrProperty); clrProperty);
} }
} }
interface IXamlIlAvaloniaPropertyNode : IXamlIlAstValueNode
{
IXamlIlType AvaloniaPropertyType { get; }
}
class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
{ {
public XamlIlAvaloniaPropertyNode(IXamlIlLineInfo lineInfo, IXamlIlType type, XamlIlAstClrProperty property) : base(lineInfo) public XamlIlAvaloniaPropertyNode(IXamlIlLineInfo lineInfo, IXamlIlType type, XamlIlAstClrProperty property) : base(lineInfo)
{ {
Type = new XamlIlAstClrTypeReference(this, type, false); Type = new XamlIlAstClrTypeReference(this, type, false);
Property = property; Property = property;
AvaloniaPropertyType = Property.Getter?.ReturnType
?? Property.Setters.First().Parameters[0];
} }
public XamlIlAstClrProperty Property { get; } public XamlIlAstClrProperty Property { get; }
@ -93,6 +106,46 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this); throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType()); return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
} }
public IXamlIlType AvaloniaPropertyType { get; }
}
class XamlIlAvaloniaPropertyFieldNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
{
private readonly IXamlIlField _field;
public XamlIlAvaloniaPropertyFieldNode(AvaloniaXamlIlWellKnownTypes types,
IXamlIlLineInfo lineInfo, IXamlIlField field) : base(lineInfo)
{
_field = field;
var avaloniaPropertyType = field.FieldType;
while (avaloniaPropertyType != null)
{
if (avaloniaPropertyType.GenericTypeDefinition?.Equals(types.AvaloniaPropertyT) == true)
{
AvaloniaPropertyType = avaloniaPropertyType.GenericArguments[0];
return;
}
avaloniaPropertyType = avaloniaPropertyType.BaseType;
}
throw new XamlIlParseException(
$"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty<T>, make sure to use typed properties",
lineInfo);
}
public IXamlIlAstTypeReference Type => new XamlIlAstClrTypeReference(this, _field.FieldType, false);
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
codeGen.Ldsfld(_field);
return XamlIlNodeEmitResult.Type(0, _field.FieldType);
}
public IXamlIlType AvaloniaPropertyType { get; }
} }
interface IXamlIlAvaloniaProperty interface IXamlIlAvaloniaProperty

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -4,8 +4,6 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Portable.Xaml;
using Portable.Xaml.Markup;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global // ReSharper disable UnusedParameter.Global

34
src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs

@ -0,0 +1,34 @@
using System;
namespace Avalonia.Markup.Xaml
{
public interface IProvideValueTarget
{
object TargetObject { get; }
object TargetProperty { get; }
}
public interface IRootObjectProvider
{
object RootObject { get; }
}
public interface IUriContext
{
Uri BaseUri { get; set; }
}
public interface IXamlTypeResolver
{
Type Resolve (string qualifiedTypeName);
}
public class ConstructorArgumentAttribute : Attribute
{
public ConstructorArgumentAttribute(string name)
{
}
}
}

4
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -28,7 +28,7 @@ namespace Avalonia.Skia
// Replace 0 characters with zero-width spaces (200B) // Replace 0 characters with zero-width spaces (200B)
Text = Text.Replace((char)0, (char)0x200B); Text = Text.Replace((char)0, (char)0x200B);
SKTypeface skiaTypeface = TypefaceCache.Default; SKTypeface skiaTypeface = null;
if (typeface.FontFamily.Key != null) if (typeface.FontFamily.Key != null)
{ {
@ -45,7 +45,7 @@ namespace Avalonia.Skia
familyName, familyName,
typeface.Style, typeface.Style,
typeface.Weight); typeface.Weight);
if (skiaTypeface != TypefaceCache.Default) break; if (skiaTypeface.FamilyName != TypefaceCache.DefaultFamilyName) break;
} }
} }
else else

2
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -47,7 +47,7 @@ namespace Avalonia.Skia
if (!_fontFamilies.TryGetValue(typeface.FontFamily.Name, out var fontFamily)) if (!_fontFamilies.TryGetValue(typeface.FontFamily.Name, out var fontFamily))
{ {
return TypefaceCache.Default; return TypefaceCache.GetTypeface(TypefaceCache.DefaultFamilyName, typeface.Style, typeface.Weight);
} }
var weight = (SKFontStyleWeight)typeface.Weight; var weight = (SKFontStyleWeight)typeface.Weight;

23
src/Skia/Avalonia.Skia/TypefaceCache.cs

@ -12,8 +12,10 @@ namespace Avalonia.Skia
/// </summary> /// </summary>
internal static class TypefaceCache internal static class TypefaceCache
{ {
public static SKTypeface Default = CreateDefaultTypeface(); public static readonly string DefaultFamilyName = CreateDefaultFamilyName();
static readonly Dictionary<string, Dictionary<FontKey, SKTypeface>> Cache = new Dictionary<string, Dictionary<FontKey, SKTypeface>>();
private static readonly Dictionary<string, Dictionary<FontKey, SKTypeface>> s_cache =
new Dictionary<string, Dictionary<FontKey, SKTypeface>>();
struct FontKey struct FontKey
{ {
@ -49,26 +51,26 @@ namespace Avalonia.Skia
// Equals and GetHashCode ommitted // Equals and GetHashCode ommitted
} }
private static SKTypeface CreateDefaultTypeface() private static string CreateDefaultFamilyName()
{ {
var defaultTypeface = SKTypeface.FromFamilyName(FontFamily.Default.Name) ?? SKTypeface.FromFamilyName(null); var defaultTypeface = SKTypeface.CreateDefault();
return defaultTypeface; return defaultTypeface.FamilyName;
} }
private static SKTypeface GetTypeface(string name, FontKey key) private static SKTypeface GetTypeface(string name, FontKey key)
{ {
var familyKey = name; var familyKey = name;
if (!Cache.TryGetValue(familyKey, out var entry)) if (!s_cache.TryGetValue(familyKey, out var entry))
{ {
Cache[familyKey] = entry = new Dictionary<FontKey, SKTypeface>(); s_cache[familyKey] = entry = new Dictionary<FontKey, SKTypeface>();
} }
if (!entry.TryGetValue(key, out var typeface)) if (!entry.TryGetValue(key, out var typeface))
{ {
typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant) typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant) ??
?? Default; GetTypeface(DefaultFamilyName, key);
entry[key] = typeface; entry[key] = typeface;
} }
@ -78,7 +80,7 @@ namespace Avalonia.Skia
public static SKTypeface GetTypeface(string name, FontStyle style, FontWeight weight) public static SKTypeface GetTypeface(string name, FontStyle style, FontWeight weight)
{ {
SKFontStyleSlant skStyle = SKFontStyleSlant.Upright; var skStyle = SKFontStyleSlant.Upright;
switch (style) switch (style)
{ {
@ -93,6 +95,5 @@ namespace Avalonia.Skia
return GetTypeface(name, new FontKey((SKFontStyleWeight)weight, skStyle)); return GetTypeface(name, new FontKey((SKFontStyleWeight)weight, skStyle));
} }
} }
} }

44
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@ -26,11 +26,26 @@ namespace Avalonia.Controls.UnitTests
}; };
var root = new TestRoot { Child = target }; var root = new TestRoot { Child = target };
Assert.False(target.IsEnabled); Assert.False(target.IsEffectivelyEnabled);
command.IsEnabled = true; command.IsEnabled = true;
Assert.True(target.IsEnabled); Assert.True(target.IsEffectivelyEnabled);
command.IsEnabled = false; command.IsEnabled = false;
Assert.False(target.IsEnabled); Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
public void Button_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False()
{
var command = new TestCommand(true);
var target = new Button
{
IsEnabled = false,
Command = command,
};
var root = new TestRoot { Child = target };
Assert.False(((IInputElement)target).IsEffectivelyEnabled);
} }
[Fact] [Fact]
@ -41,7 +56,8 @@ namespace Avalonia.Controls.UnitTests
[!Button.CommandProperty] = new Binding("Command"), [!Button.CommandProperty] = new Binding("Command"),
}; };
Assert.False(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
} }
[Fact] [Fact]
@ -59,8 +75,12 @@ namespace Avalonia.Controls.UnitTests
}; };
Assert.True(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
target.DataContext = null; target.DataContext = null;
Assert.False(target.IsEnabled);
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
} }
[Fact] [Fact]
@ -77,9 +97,13 @@ namespace Avalonia.Controls.UnitTests
[!Button.CommandProperty] = new Binding("Command"), [!Button.CommandProperty] = new Binding("Command"),
}; };
Assert.False(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel; target.DataContext = viewModel;
Assert.True(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
} }
[Fact] [Fact]
@ -96,9 +120,13 @@ namespace Avalonia.Controls.UnitTests
[!Button.CommandProperty] = new Binding("Command"), [!Button.CommandProperty] = new Binding("Command"),
}; };
Assert.False(target.IsEnabled); Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel; target.DataContext = viewModel;
Assert.False(target.IsEnabled);
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
} }
[Fact] [Fact]

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

@ -2,6 +2,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
@ -25,6 +27,103 @@ namespace Avalonia.Controls.UnitTests
Assert.False(target.Focusable); Assert.False(target.Focusable);
} }
[Fact]
public void MenuItem_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False()
{
var command = new TestCommand(true);
var target = new MenuItem
{
IsEnabled = false,
Command = command,
};
var root = new TestRoot { Child = target };
Assert.False(((IInputElement)target).IsEffectivelyEnabled);
}
[Fact]
public void MenuItem_Is_Disabled_When_Bound_Command_Doesnt_Exist()
{
var target = new MenuItem
{
[!MenuItem.CommandProperty] = new Binding("Command"),
};
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
public void MenuItem_Is_Disabled_When_Bound_Command_Is_Removed()
{
var viewModel = new
{
Command = new TestCommand(true),
};
var target = new MenuItem
{
DataContext = viewModel,
[!MenuItem.CommandProperty] = new Binding("Command"),
};
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
target.DataContext = null;
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
public void MenuItem_Is_Enabled_When_Bound_Command_Is_Added()
{
var viewModel = new
{
Command = new TestCommand(true),
};
var target = new MenuItem
{
DataContext = new object(),
[!MenuItem.CommandProperty] = new Binding("Command"),
};
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel;
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
}
[Fact]
public void MenuItem_Is_Disabled_When_Disabled_Bound_Command_Is_Added()
{
var viewModel = new
{
Command = new TestCommand(false),
};
var target = new MenuItem
{
DataContext = new object(),
[!MenuItem.CommandProperty] = new Binding("Command"),
};
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel;
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact] [Fact]
public void MenuItem_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree() public void MenuItem_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree()
{ {
@ -60,8 +159,14 @@ namespace Avalonia.Controls.UnitTests
private class TestCommand : ICommand private class TestCommand : ICommand
{ {
private bool _enabled;
private EventHandler _canExecuteChanged; private EventHandler _canExecuteChanged;
public TestCommand(bool enabled = true)
{
_enabled = enabled;
}
public int SubscriptionCount { get; private set; } public int SubscriptionCount { get; private set; }
public event EventHandler CanExecuteChanged public event EventHandler CanExecuteChanged
@ -70,7 +175,7 @@ namespace Avalonia.Controls.UnitTests
remove { _canExecuteChanged -= value; --SubscriptionCount; } remove { _canExecuteChanged -= value; --SubscriptionCount; }
} }
public bool CanExecute(object parameter) => true; public bool CanExecute(object parameter) => _enabled;
public void Execute(object parameter) public void Execute(object parameter)
{ {

183
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs

@ -15,6 +15,7 @@ using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
@ -756,6 +757,80 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Same(target.Panel.Children[9].DataContext, last); Assert.Same(target.Panel.Children[9].DataContext, last);
} }
[Fact]
public void Scrolling_Less_Than_A_Page_Should_Move_Recycled_Items()
{
var target = CreateTarget();
var items = (IList<string>)target.Items;
target.ApplyTemplate();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
var containers = target.Panel.Children.ToList();
var scroller = (ScrollContentPresenter)target.Parent;
scroller.Offset = new Vector(0, 5);
var scrolledContainers = containers
.Skip(5)
.Take(5)
.Concat(containers.Take(5)).ToList();
Assert.Equal(new Vector(0, 5), ((ILogicalScrollable)target).Offset);
Assert.Equal(scrolledContainers, target.Panel.Children);
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext);
}
scroller.Offset = new Vector(0, 0);
Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset);
Assert.Equal(containers, target.Panel.Children);
var dcs = target.Panel.Children.Select(x => x.DataContext).ToList();
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i], target.Panel.Children[i].DataContext);
}
}
[Fact]
public void Scrolling_More_Than_A_Page_Should_Recycle_Items()
{
var target = CreateTarget(itemCount: 50);
var items = (IList<string>)target.Items;
target.ApplyTemplate();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
var containers = target.Panel.Children.ToList();
var scroller = (ScrollContentPresenter)target.Parent;
scroller.Offset = new Vector(0, 20);
Assert.Equal(new Vector(0, 20), ((ILogicalScrollable)target).Offset);
Assert.Equal(containers, target.Panel.Children);
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i + 20], target.Panel.Children[i].DataContext);
}
scroller.Offset = new Vector(0, 0);
Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset);
Assert.Equal(containers, target.Panel.Children);
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i], target.Panel.Children[i].DataContext);
}
}
public class Vertical public class Vertical
{ {
[Fact] [Fact]
@ -941,86 +1016,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
} }
} }
public class WithContainers
{
[Fact]
public void Scrolling_Less_Than_A_Page_Should_Move_Recycled_Items()
{
var target = CreateTarget();
var items = (IList<string>)target.Items;
target.ApplyTemplate();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
var containers = target.Panel.Children.ToList();
var scroller = (ScrollContentPresenter)target.Parent;
scroller.Offset = new Vector(0, 5);
var scrolledContainers = containers
.Skip(5)
.Take(5)
.Concat(containers.Take(5)).ToList();
Assert.Equal(new Vector(0, 5), ((ILogicalScrollable)target).Offset);
Assert.Equal(scrolledContainers, target.Panel.Children);
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext);
}
scroller.Offset = new Vector(0, 0);
Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset);
Assert.Equal(containers, target.Panel.Children);
var dcs = target.Panel.Children.Select(x => x.DataContext).ToList();
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i], target.Panel.Children[i].DataContext);
}
}
[Fact]
public void Scrolling_More_Than_A_Page_Should_Recycle_Items()
{
var target = CreateTarget(itemCount: 50);
var items = (IList<string>)target.Items;
target.ApplyTemplate();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
var containers = target.Panel.Children.ToList();
var scroller = (ScrollContentPresenter)target.Parent;
scroller.Offset = new Vector(0, 20);
Assert.Equal(new Vector(0, 20), ((ILogicalScrollable)target).Offset);
Assert.Equal(containers, target.Panel.Children);
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i + 20], target.Panel.Children[i].DataContext);
}
scroller.Offset = new Vector(0, 0);
Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset);
Assert.Equal(containers, target.Panel.Children);
for (var i = 0; i < target.Panel.Children.Count; ++i)
{
Assert.Equal(items[i], target.Panel.Children[i].DataContext);
}
}
}
private static ItemsPresenter CreateTarget( private static ItemsPresenter CreateTarget(
Orientation orientation = Orientation.Vertical, Orientation orientation = Orientation.Vertical,
bool useContainers = true,
int itemCount = 20, int itemCount = 20,
bool useAvaloniaList = false) bool useAvaloniaList = false)
{ {
@ -1034,11 +1031,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
CanHorizontallyScroll = true, CanHorizontallyScroll = true,
CanVerticallyScroll = true, CanVerticallyScroll = true,
Content = result = new TestItemsPresenter(useContainers) Content = result = new TestItemsPresenter
{ {
Items = items, Items = items,
ItemsPanel = VirtualizingPanelTemplate(orientation), ItemsPanel = VirtualizingPanelTemplate(orientation),
ItemTemplate = ItemTemplate(), DataTemplates = { StringDataTemplate() },
VirtualizationMode = ItemVirtualizationMode.Simple, VirtualizationMode = ItemVirtualizationMode.Simple,
} }
}; };
@ -1047,7 +1044,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
return result; return result;
} }
private static IDataTemplate ItemTemplate() private static IDataTemplate StringDataTemplate()
{ {
return new FuncDataTemplate<string>(x => new Canvas return new FuncDataTemplate<string>(x => new Canvas
{ {
@ -1065,7 +1062,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
}); });
} }
private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot, IStyleRoot
{ {
public IRenderer Renderer { get; } public IRenderer Renderer { get; }
public Size ClientSize { get; } public Size ClientSize { get; }
@ -1085,18 +1082,12 @@ namespace Avalonia.Controls.UnitTests.Presenters
private class TestItemsPresenter : ItemsPresenter private class TestItemsPresenter : ItemsPresenter
{ {
private bool _useContainers;
public TestItemsPresenter(bool useContainers)
{
_useContainers = useContainers;
}
protected override IItemContainerGenerator CreateItemContainerGenerator() protected override IItemContainerGenerator CreateItemContainerGenerator()
{ {
return _useContainers ? return new ItemContainerGenerator<TestContainer>(
new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) : this,
new ItemContainerGenerator(this); TestContainer.ContentProperty,
null);
} }
} }
@ -1104,8 +1095,12 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
public TestContainer() public TestContainer()
{ {
Width = 10; Template = new FuncControlTemplate<TestContainer>(parent => new ContentPresenter
Height = 10; {
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty],
});
} }
} }
} }

50
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -401,6 +401,56 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void TextBox_GotFocus_And_LostFocus_Work_Properly()
{
using (UnitTestApplication.Start(FocusServices))
{
var target1 = new TextBox
{
Template = CreateTemplate(),
Text = "1234"
};
var target2 = new TextBox
{
Template = CreateTemplate(),
Text = "5678"
};
var sp = new StackPanel();
sp.Children.Add(target1);
sp.Children.Add(target2);
target1.ApplyTemplate();
target2.ApplyTemplate();
var root = new TestRoot { Child = sp };
var gfcount = 0;
var lfcount = 0;
target1.GotFocus += (s, e) => gfcount++;
target2.LostFocus += (s, e) => lfcount++;
target2.Focus();
Assert.False(target1.IsFocused);
Assert.True(target2.IsFocused);
target1.Focus();
Assert.False(target2.IsFocused);
Assert.True(target1.IsFocused);
Assert.Equal(1, gfcount);
Assert.Equal(1, lfcount);
}
}
private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager(),
standardCursorFactory: Mock.Of<IStandardCursorFactory>());
private static TestServices Services => TestServices.MockThreadingInterface.With( private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>()); standardCursorFactory: Mock.Of<IStandardCursorFactory>());

6
tests/Avalonia.DesignerSupport.TestApp/App.xaml

@ -1,6 +1,8 @@
<Application xmlns="https://github.com/avaloniaui"> <Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Avalonia.DesignerSupport.TestApp.App">
<Application.Styles> <Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
</Application.Styles> </Application.Styles>
</Application> </Application>

2
tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj

@ -31,6 +31,6 @@
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\Serilog.props" /> <Import Project="..\..\build\Serilog.props" />
</Project> </Project>

6
tests/Avalonia.DesignerSupport.TestApp/MainWindow.xaml

@ -1,5 +1,7 @@
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:pages="clr-namespace:ControlCatalog.Pages;assembly=ControlCatalog" xmlns:pages="clr-namespace:ControlCatalog.Pages;assembly=ControlCatalog"
Title="TESTAPP"> Title="TESTAPP"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Avalonia.DesignerSupport.TestApp.MainWindow">
<Button/> <Button/>
</Window> </Window>

17
tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs

@ -118,10 +118,21 @@ namespace Avalonia.DesignerSupport.Tests
cancelled = true; cancelled = true;
} }
Assert.True(cancelled, $"Message Not Received."); try
Assert.NotEqual(0, handle); {
proc.Kill(); proc.Kill();
}
catch
{
//
}
proc.WaitForExit(); proc.WaitForExit();
Assert.True(cancelled,
$"Message Not Received.\n" + proc.StandardOutput.ReadToEnd() + "\n" +
proc.StandardError.ReadToEnd());
Assert.NotEqual(0, handle);
} }
} }
} }

101
tests/Avalonia.Input.UnitTests/InputElement_Enabled.cs

@ -0,0 +1,101 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Xunit;
namespace Avalonia.Input.UnitTests
{
public class InputElement_Enabled
{
[Fact]
public void IsEffectivelyEnabled_Follows_IsEnabled()
{
var target = new Decorator();
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
target.IsEnabled = false;
Assert.False(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
public void IsEffectivelyEnabled_Follows_Ancestor_IsEnabled()
{
Decorator child;
Decorator grandchild;
var target = new Decorator
{
Child = child = new Decorator
{
Child = grandchild = new Decorator(),
}
};
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
Assert.True(child.IsEnabled);
Assert.True(child.IsEffectivelyEnabled);
Assert.True(grandchild.IsEnabled);
Assert.True(grandchild.IsEffectivelyEnabled);
target.IsEnabled = false;
Assert.False(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
Assert.True(child.IsEnabled);
Assert.False(child.IsEffectivelyEnabled);
Assert.True(grandchild.IsEnabled);
Assert.False(grandchild.IsEffectivelyEnabled);
}
[Fact]
public void Disabled_Pseudoclass_Follows_IsEffectivelyEnabled()
{
Decorator child;
var target = new Decorator
{
Child = child = new Decorator()
};
Assert.DoesNotContain(":disabled", child.Classes);
target.IsEnabled = false;
Assert.Contains(":disabled", child.Classes);
}
[Fact]
public void IsEffectivelyEnabled_Respects_IsEnabledCore()
{
Decorator child;
var target = new TestControl
{
Child = child = new Decorator()
};
target.ShouldEnable = false;
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
Assert.True(child.IsEnabled);
Assert.False(child.IsEffectivelyEnabled);
}
private class TestControl : Decorator
{
private bool _shouldEnable;
public bool ShouldEnable
{
get => _shouldEnable;
set { _shouldEnable = value; UpdateIsEffectivelyEnabled(); }
}
protected override bool IsEnabledCore => IsEnabled && _shouldEnable;
}
}
}

26
tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs

@ -8,9 +8,7 @@ using Avalonia.Markup.Xaml.Converters;
using Avalonia.Styling; using Avalonia.Styling;
using Xunit; using Xunit;
using System.ComponentModel; using System.ComponentModel;
using Portable.Xaml; using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Portable.Xaml.Markup;
using Avalonia.Controls;
namespace Avalonia.Markup.Xaml.UnitTests.Converters namespace Avalonia.Markup.Xaml.UnitTests.Converters
{ {
@ -91,27 +89,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
Assert.Equal("Could not find property 'AttachedOwner.NonExistent'.", ex.Message); Assert.Equal("Could not find property 'AttachedOwner.NonExistent'.", ex.Message);
} }
private ITypeDescriptorContext CreateContext(Style style = null) private ITypeDescriptorContext CreateContext(Style style = null)
{ {
var tdMock = new Mock<ITypeDescriptorContext>(); var tdMock = new Mock<ITypeDescriptorContext>();
var xsc = new Mock<IXamlSchemaContextProvider>();
var sc = Mock.Of<XamlSchemaContext>();
var amb = new Mock<IAmbientProvider>();
var tr = new Mock<IXamlTypeResolver>(); var tr = new Mock<IXamlTypeResolver>();
var ps = new Mock<IAvaloniaXamlIlParentStackProvider>();
tdMock.Setup(d => d.GetService(typeof(IAmbientProvider)))
.Returns(amb.Object);
tdMock.Setup(d => d.GetService(typeof(IXamlSchemaContextProvider)))
.Returns(xsc.Object);
tdMock.Setup(d => d.GetService(typeof(IXamlTypeResolver))) tdMock.Setup(d => d.GetService(typeof(IXamlTypeResolver)))
.Returns(tr.Object); .Returns(tr.Object);
xsc.SetupGet(v => v.SchemaContext) tdMock.Setup(d => d.GetService(typeof(IAvaloniaXamlIlParentStackProvider)))
.Returns(sc); .Returns(ps.Object);
amb.Setup(v => v.GetFirstAmbientValue(It.IsAny<Portable.Xaml.XamlType>()))
.Returns(style); ps.SetupGet(v => v.Parents)
amb.Setup(v => v.GetAllAmbientValues(It.IsAny<Portable.Xaml.XamlType>())) .Returns(new object[] {style});
.Returns(new object[] { style });
tr.Setup(v => v.Resolve(nameof(Class1))) tr.Setup(v => v.Resolve(nameof(Class1)))
.Returns(typeof(Class1)); .Returns(typeof(Class1));
tr.Setup(v => v.Resolve(nameof(AttachedOwner))) tr.Setup(v => v.Resolve(nameof(AttachedOwner)))

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

@ -14,7 +14,6 @@ using Avalonia.Media;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Portable.Xaml;
using System.Collections; using System.Collections;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
@ -47,37 +46,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal("Foo", target.Content); Assert.Equal("Foo", target.Content);
} }
[Fact]
public void AvaloniaProperty_Without_Getter_And_Setter_Is_Set()
{
// It's not possible to know in compile time if a read-only property has a magic way of being not read-only
if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
var xaml =
@"<local:NonControl xmlns='https://github.com/avaloniaui'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'
Foo='55' />";
var target = AvaloniaXamlLoader.Parse<NonControl>(xaml);
Assert.Equal(55, target.GetValue(NonControl.FooProperty));
}
[Fact]
public void AvaloniaProperty_With_Getter_And_No_Setter_Is_Set()
{
if(!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
var xaml =
@"<local:NonControl xmlns='https://github.com/avaloniaui'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'
Bar='bar' />";
var target = AvaloniaXamlLoader.Parse<NonControl>(xaml);
Assert.Equal("bar", target.Bar);
}
[Fact] [Fact]
public void Attached_Property_Is_Set() public void Attached_Property_Is_Set()
{ {
@ -159,19 +127,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml)); XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
} }
[Fact]
public void Non_Attached_Property_With_Attached_Property_Syntax_Throws()
{
// 1) It has been allowed in AvaloniaObject.SetValue for ages
// 2) There is no way to know if AddOwner was called in compile-time
if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui' TextBlock.Text='foo'/>";
XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
}
[Fact] [Fact]
public void ContentControl_ContentTemplate_Is_Functional() public void ContentControl_ContentTemplate_Is_Functional()
{ {
@ -595,31 +550,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
} }
} }
[Fact]
public void Xaml_Binding_Is_Delayed()
{
if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui' Content='{Binding}'/>";
var target = AvaloniaXamlLoader.Parse<ContentControl>(xaml);
Assert.Null(target.Content);
target.DataContext = "Foo";
Assert.Null(target.Content);
DelayedBinding.ApplyBindings(target);
Assert.Equal("Foo", target.Content);
}
}
[Fact] [Fact]
public void Double_Xaml_Binding_Is_Operational() public void Double_Xaml_Binding_Is_Operational()
{ {

14
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs

@ -5,7 +5,6 @@ using System;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Portable.Xaml;
using Xunit; using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml namespace Avalonia.Markup.Xaml.UnitTests.Xaml
@ -35,19 +34,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
XamlTestHelpers.AssertThrowsXamlException(() => loader.Load(xaml, rootInstance: target)); XamlTestHelpers.AssertThrowsXamlException(() => loader.Load(xaml, rootInstance: target));
} }
[Fact]
public void Exception_Is_Not_Thrown_If_Event_Not_Found_In_Design_Mode()
{
// Runtime compiler should properly understand x:Class
if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
var xaml = @"<Button xmlns='https://github.com/avaloniaui' Click='NotFound'/>";
var loader = new AvaloniaXamlLoader { IsDesignMode = true };
var target = new MyButton();
loader.Load(xaml, rootInstance: target);
}
private void RaiseClick(MyButton target) private void RaiseClick(MyButton target)
{ {
target.RaiseEvent(new KeyEventArgs target.RaiseEvent(new KeyEventArgs

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

Loading…
Cancel
Save