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"]
path = nukebuild/Numerge
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\SharpDX.props = build\SharpDX.props
build\SkiaSharp.props = build\SkiaSharp.props
build\Splat.props = build\Splat.props
build\System.Memory.props = build\System.Memory.props
build\XUnit.props = build\XUnit.props
EndProjectSection

4
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -26,6 +26,10 @@
<TextBox Width="200" Text="Left aligned text" TextAlignment="Left" />
<TextBox Width="200" Text="Center aligned text" TextAlignment="Center" />
<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 Orientation="Vertical" Spacing="8">

3
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

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

4
samples/VirtualizationDemo/MainWindow.xaml

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

7
samples/VirtualizationDemo/ViewModels/ItemViewModel.cs

@ -10,6 +10,7 @@ namespace VirtualizationDemo.ViewModels
{
private string _prefix;
private int _index;
private double _height = double.NaN;
public ItemViewModel(int index, string prefix = "Item")
{
@ -18,5 +19,11 @@ namespace VirtualizationDemo.ViewModels
}
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 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)
{
if (Items == null)

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

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks
@ -11,8 +12,14 @@ namespace Avalonia.Build.Tasks
{
if (args.Length != 3)
{
Console.Error.WriteLine("input references output");
return 1;
if (args.Length == 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()

23
src/Avalonia.Controls/Button.cs

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

4
src/Avalonia.Controls/ColumnDefinitions.cs

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

323
src/Avalonia.Controls/DefinitionBase.cs

@ -20,49 +20,15 @@ namespace Avalonia.Controls
/// </summary>
public abstract class DefinitionBase : AvaloniaObject
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/* internal DefinitionBase(bool isColumnDefinition)
{
_isColumnDefinition = isColumnDefinition;
_parentIndex = -1;
}*/
#endregion Constructors
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
/// <summary>
/// SharedSizeGroup property.
/// </summary>
public string SharedSizeGroup
{
get { return (string) GetValue(SharedSizeGroupProperty); }
get { return (string)GetValue(SharedSizeGroupProperty); }
set { SetValue(SharedSizeGroupProperty, value); }
}
#endregion Public Properties
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
/// <summary>
/// Callback to notify about entering model tree.
/// </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>
/// Callback to notify about exitting model tree.
/// </summary>
@ -139,119 +96,14 @@ namespace Avalonia.Controls
_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>
/// This method reflects Grid.SharedScopeProperty state by setting / clearing
/// dynamic property PrivateSharedSizeScopeProperty. Value of PrivateSharedSizeScopeProperty
/// 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>
internal static void OnIsSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
// is it possible to optimize here something like this:
// if ((bool)d.GetValue(Grid.IsSharedSizeScopeProperty) == (d.GetLocalValue(PrivateSharedSizeScopeProperty) != null)
// { /* do nothing */ }
if ((bool) e.NewValue)
if ((bool)e.NewValue)
{
SharedSizeScope sharedStatesCollection = new SharedSizeScope();
d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection);
@ -262,16 +114,6 @@ namespace Avalonia.Controls
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
/// <summary>
/// Returns <c>true</c> if this definition is a part of shared group.
/// </summary>
@ -349,8 +191,8 @@ namespace Avalonia.Controls
get
{
double preferredSize = MinSize;
if ( _sizeType != Grid.LayoutTimeSizeType.Auto
&& preferredSize < _measureSize )
if (_sizeType != Grid.LayoutTimeSizeType.Auto
&& preferredSize < _measureSize)
{
preferredSize = _measureSize;
}
@ -375,9 +217,9 @@ namespace Avalonia.Controls
get
{
double minSize = _minSize;
if ( UseSharedMinimum
&& _sharedState != null
&& minSize < _sharedState.MinSize )
if (UseSharedMinimum
&& _sharedState != null
&& minSize < _sharedState.MinSize)
{
minSize = _sharedState.MinSize;
}
@ -393,9 +235,9 @@ namespace Avalonia.Controls
get
{
double minSize = _minSize;
if ( _sharedState != null
&& (UseSharedMinimum || !LayoutWasUpdated)
&& minSize < _sharedState.MinSize )
if (_sharedState != null
&& (UseSharedMinimum || !LayoutWasUpdated)
&& minSize < _sharedState.MinSize)
{
minSize = _sharedState.MinSize;
}
@ -426,27 +268,9 @@ namespace Avalonia.Controls
/// Internal helper to access up-to-date UserMaxSize property value.
/// </summary>
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; }
#endregion Internal Properties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
/// <summary>
/// SetFlags is used to set or unset one or multiple
/// flags on the object.
@ -465,16 +289,13 @@ namespace Avalonia.Controls
return ((_flags & flags) == flags);
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
DefinitionBase definition = (DefinitionBase) d;
if (definition.InParentLogicalTree)
DefinitionBase definition = (DefinitionBase)d;
if (definition.Parent != null)
{
string sharedSizeGroupId = (string) e.NewValue;
string sharedSizeGroupId = (string)e.NewValue;
if (definition._sharedState != null)
{
@ -483,7 +304,7 @@ namespace Avalonia.Controls
definition._sharedState.RemoveMember(definition);
definition._sharedState = null;
}
if ((definition._sharedState == null) && (sharedSizeGroupId != null))
{
SharedSizeScope privateSharedSizeScope = definition.PrivateSharedSizeScope;
@ -498,9 +319,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// <see cref="AvaloniaProperty.ValidateValueCallback"/>
/// </summary>
/// <remarks>
/// Verifies that Shared Size Group Property string
/// a) not empty.
@ -520,10 +338,10 @@ namespace Avalonia.Controls
{
bool isDigit = Char.IsDigit(id[i]);
if ( (i == 0 && isDigit)
|| !( isDigit
|| Char.IsLetter(id[i])
|| '_' == id[i] ) )
if ((i == 0 && isDigit)
|| !(isDigit
|| Char.IsLetter(id[i])
|| '_' == id[i]))
{
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>
/// OnPrivateSharedSizeScopePropertyChanged is called when new scope enters or
/// 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;
if (definition.InParentLogicalTree)
if (definition.Parent != null)
{
SharedSizeScope privateSharedSizeScope = (SharedSizeScope) e.NewValue;
SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue;
if (definition._sharedState != null)
{
@ -576,22 +391,12 @@ namespace Avalonia.Controls
}
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
#region Private Properties
/// <summary>
/// Private getter of shared state collection dynamic property.
/// </summary>
private SharedSizeScope PrivateSharedSizeScope
{
get { return (SharedSizeScope) GetValue(PrivateSharedSizeScopeProperty); }
get { return (SharedSizeScope)GetValue(PrivateSharedSizeScopeProperty); }
}
/// <summary>
@ -612,16 +417,6 @@ namespace Avalonia.Controls
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
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 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]
private enum Flags : byte
@ -653,8 +435,8 @@ namespace Avalonia.Controls
//
// bool flags
//
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
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
}
/// <summary>
@ -799,8 +581,8 @@ namespace Avalonia.Controls
for (int i = 0, count = _registry.Count; i < count; ++i)
{
Debug.Assert( _userSize.GridUnitType == GridUnitType.Auto
|| _userSize.GridUnitType == GridUnitType.Pixel );
Debug.Assert(_userSize.GridUnitType == GridUnitType.Auto
|| _userSize.GridUnitType == GridUnitType.Pixel);
GridLength currentGridLength = _registry[i].UserSizeValueCache;
if (currentGridLength.GridUnitType == GridUnitType.Pixel)
@ -845,7 +627,7 @@ namespace Avalonia.Controls
{
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 (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize))
@ -857,7 +639,7 @@ namespace Avalonia.Controls
else
{
definitionBase.UseSharedMinimum = false;
// if measure is valid then also need to check arrange.
// Note: definitionBase.SizeCache is volatile but at this point
// it contains up-to-date final size
@ -879,27 +661,34 @@ namespace Avalonia.Controls
_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;
//------------------------------------------------------
//
// Properties
//
//------------------------------------------------------
// Instance event handler for layout updated event
private readonly EventHandler _layoutUpdated;
#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>
/// 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 =
AvaloniaProperty.RegisterAttached<DefinitionBase, Control, string>(
"SharedSizeGroup",
validate:SharedSizeGroupPropertyValueValid);
validate: SharedSizeGroupPropertyValueValid);
/// <summary>
/// Static ctor. Used for static registration of properties.
@ -941,7 +730,5 @@ namespace Avalonia.Controls
SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged);
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 DefinitionList()
{
ResetBehavior = ResetBehavior.Remove;
CollectionChanged += OnCollectionChanged;
}
internal bool IsDirty = true;
private Grid _parent;
@ -19,7 +25,6 @@ namespace Avalonia.Controls
set => SetParent(value);
}
private void SetParent(Grid 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.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
@ -20,8 +21,6 @@ namespace Avalonia.Controls
/// </summary>
public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable
{
private ICommand _command;
/// <summary>
/// Defines the <see cref="Command"/> property.
/// </summary>
@ -91,9 +90,8 @@ namespace Avalonia.Controls
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel());
/// <summary>
/// The submenu popup.
/// </summary>
private ICommand _command;
private bool _commandCanExecute = true;
private Popup _popup;
/// <summary>
@ -231,6 +229,8 @@ namespace Avalonia.Controls
/// <inheritdoc/>
IMenuElement IMenuItem.Parent => Parent as IMenuElement;
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
/// <inheritdoc/>
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>
/// Closes all submenus of the menu item.
/// </summary>
@ -437,9 +453,13 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
private void CanExecuteChanged(object sender, EventArgs e)
{
// HACK: Just set the IsEnabled property for the moment. This needs to be changed to
// use IsEnabledCore etc. but it will do for now.
IsEnabled = Command == null || Command.CanExecute(CommandParameter);
var canExecute = Command == null || Command.CanExecute(CommandParameter);
if (canExecute != _commandCanExecute)
{
_commandCanExecute = canExecute;
UpdateIsEffectivelyEnabled();
}
}
/// <summary>

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

@ -20,6 +20,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
internal class ItemVirtualizerSimple : ItemVirtualizer
{
private int _anchor;
/// <summary>
/// Initializes a new instance of the <see cref="ItemVirtualizerSimple"/> class.
/// </summary>
@ -362,7 +364,10 @@ namespace Avalonia.Controls.Presenters
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.
if (container != null && layoutManager != null)
{
_anchor = index;
layoutManager.ExecuteLayoutPass();
_anchor = -1;
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 =
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 =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>(
o => o.SelectionStart,
@ -34,11 +43,12 @@ namespace Avalonia.Controls.Presenters
private int _selectionStart;
private int _selectionEnd;
private bool _caretBlink;
private IBrush _highlightBrush;
static TextPresenter()
{
AffectsRender<TextPresenter>(PasswordCharProperty);
AffectsRender<TextPresenter>(PasswordCharProperty,
SelectionBrushProperty, SelectionForegroundBrushProperty,
SelectionStartProperty, SelectionEndProperty);
}
public TextPresenter()
@ -79,6 +89,24 @@ namespace Avalonia.Controls.Presenters
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
{
get
@ -129,14 +157,9 @@ namespace Avalonia.Controls.Presenters
var rects = FormattedText.HitTestTextRange(start, length);
if (_highlightBrush == null)
{
_highlightBrush = (IBrush)this.FindResource("HighlightBrush");
}
foreach (var rect in rects)
{
context.FillRectangle(_highlightBrush, rect);
context.FillRectangle(SelectionBrush, rect);
}
}
@ -144,16 +167,21 @@ namespace Avalonia.Controls.Presenters
if (selectionStart == selectionEnd)
{
var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color;
var caretBrush = Brushes.Black;
var caretBrush = CaretBrush;
if (backgroundColor.HasValue)
if (caretBrush is null)
{
byte red = (byte)~(backgroundColor.Value.R);
byte green = (byte)~(backgroundColor.Value.G);
byte blue = (byte)~(backgroundColor.Value.B);
caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue));
var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color;
if (backgroundColor.HasValue)
{
byte red = (byte)~(backgroundColor.Value.R);
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)
@ -252,7 +280,7 @@ namespace Avalonia.Controls.Presenters
{
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.
/// </summary>
public static readonly StyledProperty<double> SmallChangeProperty =
AvaloniaProperty.Register<RangeBase, double>(nameof(SmallChange), 0.1);
AvaloniaProperty.Register<RangeBase, double>(nameof(SmallChange), 1);
/// <summary>
/// Defines the <see cref="LargeChange"/> property.
/// </summary>
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 _maximum = 100.0;

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

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

4
src/Avalonia.Controls/RowDefinitions.cs

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

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

@ -14,7 +14,7 @@ namespace Avalonia.Controls.Shapes
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);
}

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

@ -14,7 +14,7 @@ namespace Avalonia.Controls.Shapes
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);
}

2
src/Avalonia.Controls/Slider.cs

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

38
src/Avalonia.Controls/TextBox.cs

@ -38,6 +38,15 @@ namespace Avalonia.Controls
public static readonly StyledProperty<char> PasswordCharProperty =
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 =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(SelectionStart),
@ -169,6 +178,24 @@ namespace Avalonia.Controls
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
{
get
@ -287,16 +314,11 @@ namespace Avalonia.Controls
{
DecideCaretVisibility();
}
e.Handled = true;
}
private void DecideCaretVisibility()
{
if (!IsReadOnly)
_presenter?.ShowCaret();
else
_presenter?.HideCaret();
_presenter.ShowCaret();
}
protected override void OnLostFocus(RoutedEventArgs e)
@ -456,7 +478,7 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfLine))
{
@ -485,7 +507,7 @@ namespace Avalonia.Controls
movement = true;
selection = true;
handled = true;
}
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.Viewport;
using Avalonia.Threading;
using Portable.Xaml;
namespace Avalonia.DesignerSupport.Remote
{
@ -206,7 +205,6 @@ namespace Avalonia.DesignerSupport.Remote
}
catch (Exception e)
{
var xamlException = e as XamlException;
var xmlException = e as XmlException;
s_transport.Send(new UpdateXamlResultMessage
@ -216,8 +214,8 @@ namespace Avalonia.DesignerSupport.Remote
{
ExceptionType = e.GetType().FullName,
Message = e.Message.ToString(),
LineNumber = xamlException?.LineNumber ?? xmlException?.LineNumber,
LinePosition = xamlException?.LinePosition ?? xmlException?.LinePosition,
LineNumber = xmlException?.LineNumber,
LinePosition = xmlException?.LinePosition,
}
});
}

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

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

10
src/Avalonia.Input/DragDropDevice.cs

@ -23,7 +23,13 @@ namespace Avalonia.Input
{
if (target == null)
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,
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)
{
point = _target.TranslatePoint(_targetLocation, relativeTo);
point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point;
}
return point;
}
@ -41,4 +42,4 @@ namespace Avalonia.Input
}
}
}
}

8
src/Avalonia.Input/FocusManager.cs

@ -23,7 +23,7 @@ namespace Avalonia.Input
/// <summary>
/// Initializes a new instance of the <see cref="FocusManager"/> class.
/// </summary>
public FocusManager()
static FocusManager()
{
InputElement.PointerPressedEvent.AddClassHandler(
typeof(IInputElement),
@ -146,7 +146,7 @@ namespace Avalonia.Input
/// </summary>
/// <param name="e">The element.</param>
/// <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>
/// Gets the focus scope ancestors of the specified control, traversing popups.
@ -174,7 +174,7 @@ namespace Avalonia.Input
/// </summary>
/// <param name="sender">The event sender.</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;
@ -191,7 +191,7 @@ namespace Avalonia.Input
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; }
/// <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>
/// <remarks>
/// 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.
/// </remarks>
bool IsEnabledCore { get; }
bool IsEffectivelyEnabled { get; }
/// <summary>
/// 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);
/// <summary>
/// Defines the <see cref="IsEnabledCore"/> property.
/// Defines the <see cref="IsEffectivelyEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsEnabledCoreProperty =
AvaloniaProperty.Register<InputElement, bool>(nameof(IsEnabledCore), true);
public static readonly DirectProperty<InputElement, bool> IsEffectivelyEnabledProperty =
AvaloniaProperty.RegisterDirect<InputElement, bool>(
nameof(IsEffectivelyEnabled),
o => o.IsEffectivelyEnabled);
/// <summary>
/// Gets or sets associated mouse cursor.
@ -155,6 +157,7 @@ namespace Avalonia.Input
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> DoubleTappedEvent = Gestures.DoubleTappedEvent;
private bool _isEffectivelyEnabled = true;
private bool _isFocused;
private bool _isPointerOver;
private GestureRecognizerCollection _gestureRecognizers;
@ -179,7 +182,7 @@ namespace Avalonia.Input
PointerCaptureLostEvent.AddClassHandler<InputElement>(x => x.OnPointerCaptureLost);
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>(IsPointerOverProperty, ":pointerover");
}
@ -365,31 +368,25 @@ namespace Avalonia.Input
internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); }
}
/// <summary>
/// Gets a value indicating whether the control is effectively enabled for user interaction.
/// </summary>
/// <remarks>
/// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual
/// controls. The <see cref="IsEnabledCore"/> property takes into account the
/// <see cref="IsEnabled"/> value of this control and its parent controls.
/// </remarks>
bool IInputElement.IsEnabledCore => IsEnabledCore;
/// <inheritdoc/>
public bool IsEffectivelyEnabled
{
get => _isEffectivelyEnabled;
private set => SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
}
public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
/// <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>
/// <remarks>
/// The <see cref="IsEnabled"/> property is used to toggle the enabled state for individual
/// controls. The <see cref="IsEnabledCore"/> property takes into account the
/// <see cref="IsEnabled"/> value of this control and its parent controls.
/// Derived controls may wish to disable the enabled state of the control without overwriting the
/// user-supplied <see cref="IsEnabled"/> setting. This can be done by overriding this property
/// 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>
protected bool IsEnabledCore
{
get { return GetValue(IsEnabledCoreProperty); }
set { SetValue(IsEnabledCoreProperty, value); }
}
public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
protected virtual bool IsEnabledCore => IsEnabled;
public GestureRecognizerCollection GestureRecognizers
=> _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this));
@ -417,7 +414,7 @@ namespace Avalonia.Input
protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTreeCore(e);
UpdateIsEnabledCore();
UpdateIsEffectivelyEnabled();
}
/// <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)
{
((InputElement)e.Sender).UpdateIsEnabledCore();
((InputElement)e.Sender).UpdateIsEffectivelyEnabled();
}
/// <summary>
@ -551,32 +557,17 @@ namespace Avalonia.Input
}
/// <summary>
/// Updates the <see cref="IsEnabledCore"/> property value.
/// </summary>
private void UpdateIsEnabledCore()
{
UpdateIsEnabledCore(this.GetVisualParent<InputElement>());
}
/// <summary>
/// Updates the <see cref="IsEnabledCore"/> property based on the parent's
/// <see cref="IsEnabledCore"/>.
/// Updates the <see cref="IsEffectivelyEnabled"/> property based on the parent's
/// <see cref="IsEffectivelyEnabled"/>.
/// </summary>
/// <param name="parent">The parent control.</param>
private void UpdateIsEnabledCore(InputElement parent)
private void UpdateIsEffectivelyEnabled(InputElement parent)
{
if (parent != null)
{
IsEnabledCore = IsEnabled && parent.IsEnabledCore;
}
else
{
IsEnabledCore = IsEnabled;
}
IsEffectivelyEnabled = IsEnabledCore && (parent?.IsEffectivelyEnabled ?? true);
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 &&
element.IsVisible &&
element.IsHitTestVisible &&
element.IsEnabledCore &&
element.IsEffectivelyEnabled &&
element.IsAttachedToVisualTree;
}
}

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

@ -13,13 +13,13 @@ namespace Avalonia.Input.Navigation
/// </summary>
/// <param name="e">The element.</param>
/// <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>
/// Checks if descendants of the specified element can be focused.
/// </summary>
/// <param name="e">The element.</param>
/// <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>
<IsPackable>false</IsPackable>
@ -7,8 +7,6 @@
<CastXmlPath Condition="Exists('/usr/bin/castxml')">/usr/bin/castxml</CastXmlPath>
<CastXmlPath Condition="Exists('/usr/local/bin/castxml')">/usr/local/bin/castxml</CastXmlPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- This is needed because Rider doesn't see generated files in obj for some reason -->
<SharpGenGeneratedCodeFolder>$(MSBuildThisFileDirectory)/Generated</SharpGenGeneratedCodeFolder>
</PropertyGroup>
<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="HighlightColor">#FF119EDA</Color>
<Color x:Key="HighlightForegroundColor">#FFFFFFFF</Color>
<Color x:Key="ErrorColor">#FFFF0000</Color>
<Color x:Key="ErrorLowColor">#10FF0000</Color>
@ -39,6 +40,7 @@
<SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="{DynamicResource ThemeForegroundLowColor}"></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="ThemeAccentBrush2" Color="{DynamicResource ThemeAccentColor2}"></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="HighlightColor">#FF086F9E</Color>
<Color x:Key="HighlightForegroundColor">#FFFFFFFF</Color>
<Color x:Key="ErrorColor">#FFFF0000</Color>
<Color x:Key="ErrorLowColor">#10FF0000</Color>
@ -39,6 +40,7 @@
<SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="{DynamicResource ThemeForegroundLowColor}"></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="ThemeAccentBrush2" Color="{DynamicResource ThemeAccentColor2}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush3" Color="{DynamicResource ThemeAccentColor3}"></SolidColorBrush>

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

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

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

@ -3,6 +3,8 @@
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<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="Template">
<ControlTemplate>
@ -44,7 +46,10 @@
SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
PasswordChar="{TemplateBinding PasswordChar}"/>
PasswordChar="{TemplateBinding PasswordChar}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}"/>
</Panel>
</ScrollViewer>
</DataValidationErrors>

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

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

12
src/Avalonia.Visuals/Rect.cs

@ -256,7 +256,7 @@ namespace Avalonia
/// <summary>
/// Inflates the rectangle.
/// </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>
public Rect Inflate(double thickness)
{
@ -266,7 +266,7 @@ namespace Avalonia
/// <summary>
/// Inflates the rectangle.
/// </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>
public Rect Inflate(Thickness thickness)
{
@ -278,20 +278,18 @@ namespace Avalonia
/// <summary>
/// Deflates the rectangle.
/// </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>
/// <remarks>The deflated rectangle size cannot be less than 0.</remarks>
public Rect Deflate(double thickness)
{
return Deflate(new Thickness(thickness / 2));
return Deflate(new Thickness(thickness));
}
/// <summary>
/// Deflates the rectangle by a <see cref="Thickness"/>.
/// </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>
/// <remarks>The deflated rectangle size cannot be less than 0.</remarks>
public Rect Deflate(Thickness thickness)
{
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/>
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)
{
var controlPoint = Root.Visual.TranslatePoint(p, node.Visual);
clipped = !node.GeometryClip.FillContains(controlPoint);
clipped = !node.GeometryClip.FillContains(controlPoint.Value);
}
if (!clipped)

62
src/Avalonia.Visuals/Visual.cs

@ -291,29 +291,6 @@ namespace Avalonia
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>
/// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be
/// 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>
/// Called when a visual's <see cref="RenderTransform"/> changes.
/// </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.
using System;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia
@ -20,10 +19,8 @@ namespace Avalonia
/// <returns>The point in client coordinates.</returns>
public static Point PointToClient(this IVisual visual, PixelPoint point)
{
var (root, offset) = GetRootAndPosition(visual);
var screenOffset = PixelPoint.FromPoint((Point)offset, root.RenderScaling);
var screenPoint = new PixelPoint(point.X - screenOffset.X, point.Y - screenOffset.Y);
return root.PointToClient(screenPoint);
var rootPoint = visual.VisualRoot.PointToClient(point);
return visual.VisualRoot.TranslatePoint(rootPoint, visual).Value;
}
/// <summary>
@ -34,48 +31,93 @@ namespace Avalonia
/// <returns>The point in screen coordinates.</returns>
public static PixelPoint PointToScreen(this IVisual visual, Point point)
{
var p = GetRootAndPosition(visual);
return p.Item1.PointToScreen(point + p.Item2);
var p = visual.TranslatePoint(point, visual.VisualRoot);
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>
/// 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>
/// <param name="visual">The 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>
/// <returns>A point value, now relative to the target visual rather than this source element.</returns>
public static Point TranslatePoint(this IVisual visual, Point point, IVisual relativeTo)
/// <returns>
/// 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 relToPos = GetRootAndPosition(relativeTo);
var transform = visual.TransformToVisual(relativeTo);
return point - (relToPos.Item2 - pos.Item2);
if (transform.HasValue)
{
return point.Transform(transform.Value);
}
return null;
}
/// <summary>
/// Gets the root of the control's visual tree and the position of the control
/// in the root's coordinate space.
/// Gets a transform from an ancestor to a descendent.
/// </summary>
/// <param name="v">The visual.</param>
/// <returns>A tuple containing the root and the position of the control.</returns>
private static Tuple<IRenderRoot, Vector> GetRootAndPosition(IVisual v)
/// <param name="ancestor">The ancestor visual.</param>
/// <param name="visual">The visual.</param>
/// <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);
v = v.VisualParent;
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 (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>
/// <param name="context">The context.</param>
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);
var root = visual.GetVisualRoot();
p = visual.TranslatePoint(p, root);
return root.Renderer.HitTest(p, visual, filter);
var rootPoint = visual.TranslatePoint(p, root);
if (rootPoint.HasValue)
{
return root.Renderer.HitTest(rootPoint.Value, visual, filter);
}
return Enumerable.Empty<IVisual>();
}
/// <summary>

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

@ -11,38 +11,22 @@
<ItemGroup>
<Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\AvaloniaUriTypeConverter.cs" />
<Compile Include="Converters\AvaloniaEventConverter.cs" />
<Compile Include="Converters\FontFamilyTypeConverter.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="Extensions.cs" />
<Compile Include="MarkupExtension.cs" />
<Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
<Compile Include="MarkupExtensions\ResourceInclude.cs" />
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
<Compile Include="MarkupExtensions\StyleIncludeExtension.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\IconTypeConverter.cs" />
<Compile Include="Converters\AvaloniaPropertyTypeConverter.cs" />
<Compile Include="Converters\PointsListTypeConverter.cs" />
<Compile Include="Converters\SelectorTypeConverter.cs" />
<Compile Include="MarkupExtensions\BindingExtension.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="Styling\StyleInclude.cs" />
<Compile Include="Templates\ControlTemplate.cs" />
@ -52,7 +36,6 @@
<Compile Include="Templates\MemberSelector.cs" />
<Compile Include="Templates\Template.cs" />
<Compile Include="Templates\TemplateContent.cs" />
<Compile Include="Templates\TemplateLoader.cs" />
<Compile Include="Templates\TreeDataTemplate.cs" />
<Compile Include="XamlIl\AvaloniaXamlIlRuntimeCompiler.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionHackTransformer.cs" />
@ -76,11 +59,11 @@
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
<Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.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 Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs" />
<Compile Remove="XamlIl\xamlil.github\**\obj\**\*.cs" />
<Compile Include="..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" />
<Compile Include="XamlTypes.cs" />
</ItemGroup>
<ItemGroup>
<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.Xaml.PortableXaml;
using Avalonia.Platform;
using Portable.Xaml;
namespace Avalonia.Markup.Xaml
{
@ -26,71 +25,14 @@ namespace Avalonia.Markup.Xaml
{
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>
/// Loads the XAML into a Avalonia component.
/// </summary>
/// <param name="obj">The object to load the XAML into.</param>
public static void Load(object obj)
{
Contract.Requires<ArgumentNullException>(obj != null);
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);
throw new XamlLoadException(
$"No precompiled XAML found for {obj.GetType()}, make sure to specify x:Class and include your XAML file as AvaloniaResource");
}
/// <summary>
@ -100,11 +42,8 @@ namespace Avalonia.Markup.Xaml
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <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);
@ -133,7 +72,7 @@ namespace Avalonia.Markup.Xaml
using (var stream = asset.stream)
{
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 name="uri">The URI of the XAML</param>
/// <returns>The loaded object.</returns>
public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null)
{
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);
public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null)
=> AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, IsDesignMode);
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)
=> 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.Styling;
using Avalonia.Utilities;
using Portable.Xaml.ComponentModel;
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
{
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using System.ComponentModel;
public class BitmapTypeConverter : TypeConverter
{

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

@ -7,7 +7,6 @@ using System.Globalization;
using Avalonia.Media;
using Portable.Xaml.ComponentModel;
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
{
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
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.Linq;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Portable.Xaml;
using Portable.Xaml.Markup;
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 Uri GetContextBaseUri(this IServiceProvider ctx)
{
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 Uri GetContextBaseUri(this IServiceProvider ctx) => ctx.GetService<IUriContext>().BaseUri;
public static T GetFirstParent<T>(this IServiceProvider ctx) where T : class
{
var parentStack = ctx.GetService<IAvaloniaXamlIlParentStackProvider>();
if (parentStack != null)
return parentStack.Parents.OfType<T>().FirstOrDefault();
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 T GetFirstParent<T>(this IServiceProvider ctx) where T : class
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().FirstOrDefault();
public static T GetLastParent<T>(this IServiceProvider ctx) where T : class
=> ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().LastOrDefault();
public static IEnumerable<T> GetParents<T>(this IServiceProvider sp)
{
var stack = sp.GetService<IAvaloniaXamlIlParentStackProvider>();
if (stack != null)
return stack.Parents.OfType<T>();
return sp.GetService<IAvaloniaXamlIlParentStackProvider>().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)

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.Markup.Data;
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
using PortableXaml;
using System.ComponentModel;
[MarkupExtensionReturnType(typeof(IBinding))]
public class BindingExtension : MarkupExtension
public class BindingExtension
{
public BindingExtension()
{
@ -28,12 +23,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Path = path;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return ProvideTypedValue(serviceProvider);
}
public Binding ProvideTypedValue(IServiceProvider serviceProvider)
public Binding ProvideValue(IServiceProvider 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 Avalonia.Controls;
using Avalonia.Data;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class DynamicResourceExtension : MarkupExtension, IBinding
public class DynamicResourceExtension : IBinding
{
private IResourceNode _anchor;
@ -28,9 +25,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public object ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider);
public IBinding ProvideTypedValue(IServiceProvider serviceProvider)
public IBinding ProvideValue(IServiceProvider serviceProvider)
{
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();

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

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

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

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

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

@ -7,13 +7,10 @@ using System.ComponentModel;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Markup.Data;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class StaticResourceExtension : MarkupExtension
public class StaticResourceExtension
{
public StaticResourceExtension()
{
@ -26,26 +23,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
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
// to give us the resource.
foreach (var resourceProvider in serviceProvider.GetParents<IResourceNode>())
{
// We override XamlType.CanAssignTo in BindingXamlType so the results we get back
// 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))
if (resourceProvider.TryGetResource(ResourceKey, out var value))
{
return value;
}

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

@ -3,23 +3,18 @@
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using Portable.Xaml.Markup;
using System;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
[MarkupExtensionReturnType(typeof(IStyle))]
public class StyleIncludeExtension : MarkupExtension
public class StyleIncludeExtension
{
public StyleIncludeExtension()
{
}
public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider);
public IStyle ProvideTypedValue(IServiceProvider serviceProvider)
public IStyle ProvideValue(IServiceProvider serviceProvider)
{
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
{
using Portable.Xaml;
public class TemplateContent
public static 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)
{
if (templateContent is Func<IServiceProvider, object> direct)
{
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 =
{
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"),
typeSystem.FindType("Portable.Xaml.Markup.XmlnsDefinitionAttribute")
},
ContentAttributes =
{
typeSystem.GetType("Avalonia.Metadata.ContentAttribute")
},
ProvideValueTarget = typeSystem.GetType("Portable.Xaml.Markup.IProvideValueTarget"),
RootObjectProvider = typeSystem.GetType("Portable.Xaml.IRootObjectProvider"),
UriContextProvider = typeSystem.GetType("Portable.Xaml.Markup.IUriContext"),
ProvideValueTarget = typeSystem.GetType("Avalonia.Markup.Xaml.IProvideValueTarget"),
RootObjectProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IRootObjectProvider"),
UriContextProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IUriContext"),
ParentStackProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlParentStackProvider"),
@ -47,7 +46,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV1"),
UsableDuringInitializationAttributes =
{
typeSystem.GetType("Portable.Xaml.Markup.UsableDuringInitializationAttribute"),
typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"),
},
InnerServiceProviderFactoryMethod =
@ -79,15 +77,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
void Add(string type, string 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");
var ilist = typeSystem.GetType("System.Collections.Generic.IList`1");
AddType(ilist.MakeGenericType(typeSystem.GetType("Avalonia.Point")),
typeSystem.GetType("Avalonia.Markup.Xaml.Converters.PointsListTypeConverter"));
Add("Avalonia.Controls.Templates.IMemberSelector",
"Avalonia.Markup.Xaml.Converters.MemberSelectorTypeConverter");
Add("Avalonia.Styling.Selector","Avalonia.Markup.Xaml.Converters.SelectorTypeConverter");
Add("Avalonia.Controls.WindowIcon","Avalonia.Markup.Xaml.Converters.IconTypeConverter");
Add("System.Globalization.CultureInfo", "System.ComponentModel.CultureInfoConverter");
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
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node;
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)
throw new XamlIlParseException(
"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");
if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode)
{
var propType = avaloniaPropertyNode.Property.Getter?.ReturnType
?? avaloniaPropertyNode.Property.Setters.First().Parameters[0];
var propType = avaloniaPropertyNode.AvaloniaPropertyType;
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
propType, out var converted))
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 AvaloniaObjectExtensions { get; }
public IXamlIlType AvaloniaProperty { get; }
public IXamlIlType AvaloniaPropertyT { get; }
public IXamlIlType IBinding { get; }
public IXamlIlMethod AvaloniaObjectBindMethod { get; }
public IXamlIlMethod AvaloniaObjectSetValueMethod { get; }
@ -26,6 +27,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject");
AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
AvaloniaProperty = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty");
AvaloniaPropertyT = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty`1");
BindingPriority = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.BindingPriority");
IBinding = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.IBinding");
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;
}
public static XamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context,
public static IXamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context,
string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo)
{
XamlIlAstNamePropertyReference forgedReference;
@ -63,8 +63,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
xmlOwner += parsedPropertyName.owner;
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 =
@ -75,13 +81,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
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)
{
Type = new XamlIlAstClrTypeReference(this, type, false);
Property = property;
AvaloniaPropertyType = Property.Getter?.ReturnType
?? Property.Setters.First().Parameters[0];
}
public XamlIlAstClrProperty Property { get; }
@ -93,6 +106,46 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this);
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

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

@ -4,8 +4,6 @@ using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data;
using Portable.Xaml;
using Portable.Xaml.Markup;
// ReSharper disable UnusedMember.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)
Text = Text.Replace((char)0, (char)0x200B);
SKTypeface skiaTypeface = TypefaceCache.Default;
SKTypeface skiaTypeface = null;
if (typeface.FontFamily.Key != null)
{
@ -45,7 +45,7 @@ namespace Avalonia.Skia
familyName,
typeface.Style,
typeface.Weight);
if (skiaTypeface != TypefaceCache.Default) break;
if (skiaTypeface.FamilyName != TypefaceCache.DefaultFamilyName) break;
}
}
else

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

@ -47,7 +47,7 @@ namespace Avalonia.Skia
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;

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

@ -12,8 +12,10 @@ namespace Avalonia.Skia
/// </summary>
internal static class TypefaceCache
{
public static SKTypeface Default = CreateDefaultTypeface();
static readonly Dictionary<string, Dictionary<FontKey, SKTypeface>> Cache = new Dictionary<string, Dictionary<FontKey, SKTypeface>>();
public static readonly string DefaultFamilyName = CreateDefaultFamilyName();
private static readonly Dictionary<string, Dictionary<FontKey, SKTypeface>> s_cache =
new Dictionary<string, Dictionary<FontKey, SKTypeface>>();
struct FontKey
{
@ -49,26 +51,26 @@ namespace Avalonia.Skia
// 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)
{
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))
{
typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant)
?? Default;
typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant) ??
GetTypeface(DefaultFamilyName, key);
entry[key] = typeface;
}
@ -78,7 +80,7 @@ namespace Avalonia.Skia
public static SKTypeface GetTypeface(string name, FontStyle style, FontWeight weight)
{
SKFontStyleSlant skStyle = SKFontStyleSlant.Upright;
var skStyle = SKFontStyleSlant.Upright;
switch (style)
{
@ -93,6 +95,5 @@ namespace Avalonia.Skia
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 };
Assert.False(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
command.IsEnabled = true;
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
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]
@ -41,7 +56,8 @@ namespace Avalonia.Controls.UnitTests
[!Button.CommandProperty] = new Binding("Command"),
};
Assert.False(target.IsEnabled);
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
@ -59,8 +75,12 @@ namespace Avalonia.Controls.UnitTests
};
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
target.DataContext = null;
Assert.False(target.IsEnabled);
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
@ -77,9 +97,13 @@ namespace Avalonia.Controls.UnitTests
[!Button.CommandProperty] = new Binding("Command"),
};
Assert.False(target.IsEnabled);
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel;
Assert.True(target.IsEnabled);
Assert.True(target.IsEffectivelyEnabled);
}
[Fact]
@ -96,9 +120,13 @@ namespace Avalonia.Controls.UnitTests
[!Button.CommandProperty] = new Binding("Command"),
};
Assert.False(target.IsEnabled);
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
target.DataContext = viewModel;
Assert.False(target.IsEnabled);
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]

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

@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Text;
using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.UnitTests;
using Xunit;
@ -25,6 +27,103 @@ namespace Avalonia.Controls.UnitTests
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]
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 bool _enabled;
private EventHandler _canExecuteChanged;
public TestCommand(bool enabled = true)
{
_enabled = enabled;
}
public int SubscriptionCount { get; private set; }
public event EventHandler CanExecuteChanged
@ -70,7 +175,7 @@ namespace Avalonia.Controls.UnitTests
remove { _canExecuteChanged -= value; --SubscriptionCount; }
}
public bool CanExecute(object parameter) => true;
public bool CanExecute(object parameter) => _enabled;
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.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
@ -756,6 +757,80 @@ namespace Avalonia.Controls.UnitTests.Presenters
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
{
[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(
Orientation orientation = Orientation.Vertical,
bool useContainers = true,
int itemCount = 20,
bool useAvaloniaList = false)
{
@ -1034,11 +1031,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
Content = result = new TestItemsPresenter(useContainers)
Content = result = new TestItemsPresenter
{
Items = items,
ItemsPanel = VirtualizingPanelTemplate(orientation),
ItemTemplate = ItemTemplate(),
DataTemplates = { StringDataTemplate() },
VirtualizationMode = ItemVirtualizationMode.Simple,
}
};
@ -1047,7 +1044,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
return result;
}
private static IDataTemplate ItemTemplate()
private static IDataTemplate StringDataTemplate()
{
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 Size ClientSize { get; }
@ -1085,18 +1082,12 @@ namespace Avalonia.Controls.UnitTests.Presenters
private class TestItemsPresenter : ItemsPresenter
{
private bool _useContainers;
public TestItemsPresenter(bool useContainers)
{
_useContainers = useContainers;
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return _useContainers ?
new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) :
new ItemContainerGenerator(this);
return new ItemContainerGenerator<TestContainer>(
this,
TestContainer.ContentProperty,
null);
}
}
@ -1104,8 +1095,12 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
public TestContainer()
{
Width = 10;
Height = 10;
Template = new FuncControlTemplate<TestContainer>(parent => new ContentPresenter
{
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(
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>
<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"/>
</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.Desktop\Avalonia.Desktop.csproj" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\Serilog.props" />
</Project>

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

@ -1,5 +1,7 @@
<Window xmlns="https://github.com/avaloniaui"
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/>
</Window>
</Window>

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

@ -118,10 +118,21 @@ namespace Avalonia.DesignerSupport.Tests
cancelled = true;
}
Assert.True(cancelled, $"Message Not Received.");
Assert.NotEqual(0, handle);
proc.Kill();
try
{
proc.Kill();
}
catch
{
//
}
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 Xunit;
using System.ComponentModel;
using Portable.Xaml;
using Portable.Xaml.Markup;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
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);
}
private ITypeDescriptorContext CreateContext(Style style = null)
{
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 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)))
.Returns(tr.Object);
xsc.SetupGet(v => v.SchemaContext)
.Returns(sc);
amb.Setup(v => v.GetFirstAmbientValue(It.IsAny<Portable.Xaml.XamlType>()))
.Returns(style);
amb.Setup(v => v.GetAllAmbientValues(It.IsAny<Portable.Xaml.XamlType>()))
.Returns(new object[] { style });
tdMock.Setup(d => d.GetService(typeof(IAvaloniaXamlIlParentStackProvider)))
.Returns(ps.Object);
ps.SetupGet(v => v.Parents)
.Returns(new object[] {style});
tr.Setup(v => v.Resolve(nameof(Class1)))
.Returns(typeof(Class1));
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.Styling;
using Avalonia.UnitTests;
using Portable.Xaml;
using System.Collections;
using System.ComponentModel;
using System.Linq;
@ -47,37 +46,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
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]
public void Attached_Property_Is_Set()
{
@ -159,19 +127,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.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]
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]
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.Input;
using Avalonia.Interactivity;
using Portable.Xaml;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
@ -35,19 +34,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
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)
{
target.RaiseEvent(new KeyEventArgs

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

Loading…
Cancel
Save