Browse Source

Merge branch 'master' into feature/ApplicationOnStartup

pull/2442/head
Benedikt Schroeder 7 years ago
committed by GitHub
parent
commit
e4db0504c7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  2. 4
      samples/ControlCatalog/SideBar.xaml
  3. 2
      samples/interop/WindowsInteropTest/Program.cs
  4. 5
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  5. 4
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  6. 6
      src/Avalonia.Base/Data/BindingOperations.cs
  7. 2
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  8. 10
      src/Avalonia.Controls/Control.cs
  9. 13
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  10. 113
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  11. 125
      src/Avalonia.Controls/Presenters/ItemContainerSync.cs
  12. 48
      src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs
  13. 9
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  14. 11
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  15. 10
      src/Avalonia.Styling/Styling/IRequiresTemplateInSetter.cs
  16. 13
      src/Avalonia.Styling/Styling/ISetterValue.cs
  17. 8
      src/Avalonia.Styling/Styling/Setter.cs
  18. 10
      src/Avalonia.Themes.Default/TabItem.xaml
  19. 110
      src/Avalonia.Visuals/Media/FontFamily.cs
  20. 35
      src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
  21. 2
      src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
  22. 13
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  23. 6
      src/Avalonia.X11/XIStructs.cs
  24. 17
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  25. 61
      tests/Avalonia.Controls.UnitTests/CarouselTests.cs
  26. 17
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  27. 753
      tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs
  28. 33
      tests/Avalonia.LeakTests/ControlTests.cs
  29. 22
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
  30. 29
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
  31. 14
      tests/Avalonia.Styling.UnitTests/SetterTests.cs
  32. 48
      tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs
  33. 11
      tests/Avalonia.Visuals.UnitTests/Media/Fonts/FamilyNameCollectionTests.cs

4
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -32,7 +32,7 @@
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi;armeabi-v7a;x86</AndroidSupportedAbis>
<AndroidSupportedAbis>armeabi-v7a;x86</AndroidSupportedAbis>
<Debugger>Xamarin</Debugger>
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
<AotAssemblies>False</AotAssemblies>
@ -51,7 +51,7 @@
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidSupportedAbis>armeabi-v7a,x86</AndroidSupportedAbis>
<Debugger>Xamarin</Debugger>
<AotAssemblies>False</AotAssemblies>
<EnableLLVM>False</EnableLLVM>

4
samples/ControlCatalog/SideBar.xaml

@ -63,13 +63,13 @@
<Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected">
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabControl.sidebar > TabItem:selected">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
</Style>
</Styles>

2
samples/interop/WindowsInteropTest/Program.cs

@ -15,7 +15,7 @@ namespace WindowsInteropTest
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().SetupWithoutStarting();
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().UseDataGrid().SetupWithoutStarting();
System.Windows.Forms.Application.Run(new SelectorForm());
}
}

5
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@ -104,6 +104,10 @@
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Avalonia.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj">
<Project>{3278f3a9-9509-4a3f-a15b-bdc8b5bff632}</Project>
<Name>Avalonia.Controls.DataGrid</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Avalonia.Controls</Name>
@ -185,6 +189,5 @@
</ItemGroup>
<Import Project="..\..\..\build\Rx.props" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\build\SkiaSharp.props" />
<Import Project="..\..\..\build\LegacyProject.targets" />
</Project>

4
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -32,7 +32,7 @@
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi;armeabi-v7a;x86</AndroidSupportedAbis>
<AndroidSupportedAbis>armeabi-v7a;x86</AndroidSupportedAbis>
<Debugger>Xamarin</Debugger>
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
<DevInstrumentationEnabled>True</DevInstrumentationEnabled>
@ -52,7 +52,7 @@
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidSupportedAbis>armeabi-v7a,x86</AndroidSupportedAbis>
<Debugger>Xamarin</Debugger>
<AotAssemblies>False</AotAssemblies>
<EnableLLVM>False</EnableLLVM>

6
src/Avalonia.Base/Data/BindingOperations.cs

@ -65,7 +65,11 @@ namespace Avalonia.Data
return Disposable.Empty;
}
case BindingMode.OneWayToSource:
return target.GetObservable(property).Subscribe(binding.Subject);
return Observable.CombineLatest(
binding.Observable,
target.GetObservable(property),
(_, v) => v)
.Subscribe(x => binding.Subject.OnNext(x));
default:
throw new ArgumentException("Invalid binding mode.");
}

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

@ -59,8 +59,8 @@ namespace Avalonia.Data.Core
$"Could not find a matching property accessor for {PropertyName}.");
}
accessor.Subscribe(ValueChanged);
_accessor = accessor;
accessor.Subscribe(ValueChanged);
}
protected override void StopListeningCore()

10
src/Avalonia.Controls/Control.cs

@ -1,6 +1,7 @@
// 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.ComponentModel;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -20,7 +21,7 @@ namespace Avalonia.Controls
///
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// </remarks>
public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, IRequiresTemplateInSetter
public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, ISetterValue
{
/// <summary>
/// Defines the <see cref="FocusAdorner"/> property.
@ -90,6 +91,13 @@ namespace Avalonia.Controls
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter)
{
throw new InvalidOperationException(
"Cannot use a control as a Setter value. Wrap the control in a <Template>.");
}
/// <inheritdoc/>
void IVisualBrushInitialize.EnsureInitialized()
{

13
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -336,7 +336,18 @@ namespace Avalonia.Controls.Platform
if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == true)
{
Open(item, false);
if (item.IsSubMenuOpen)
{
if (item.IsTopLevel)
{
CloseMenu(item);
}
}
else
{
Open(item, false);
}
e.Handled = true;
}
}

113
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@ -38,7 +38,6 @@ namespace Avalonia.Controls.Presenters
Carousel.PageTransitionProperty.AddOwner<CarouselPresenter>();
private int _selectedIndex = -1;
// private Task _current;
private Task _currentTransition;
private int _queuedTransitionIndex = -1;
@ -47,6 +46,7 @@ namespace Avalonia.Controls.Presenters
/// </summary>
static CarouselPresenter()
{
IsVirtualizedProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.IsVirtualizedChanged);
SelectedIndexProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.SelectedIndexChanged);
}
@ -94,58 +94,67 @@ namespace Avalonia.Controls.Presenters
set { SetValue(PageTransitionProperty, value); }
}
/// <inheritdoc/>
protected override void PanelCreated(IPanel panel)
{
#pragma warning disable 4014
MoveToPage(-1, SelectedIndex);
#pragma warning restore 4014
}
/// <inheritdoc/>
protected override void ItemsChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
if (!IsVirtualized)
{
case NotifyCollectionChangedAction.Remove:
if (!IsVirtualized)
{
var generator = ItemContainerGenerator;
var containers = generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count);
Panel.Children.RemoveAll(containers.Select(x => x.ContainerControl));
base.ItemsChanged(e);
#pragma warning disable 4014
MoveToPage(-1, SelectedIndex);
#pragma warning restore 4014
}
break;
case NotifyCollectionChangedAction.Reset:
{
var generator = ItemContainerGenerator;
var containers = generator.Containers.ToList();
generator.Clear();
Panel.Children.RemoveAll(containers.Select(x => x.ContainerControl));
#pragma warning disable 4014
var newIndex = SelectedIndex;
if (Items == null || SelectedIndex >= Items.Count())
{
SelectedIndex = Items.Count() - 1;
}
if(SelectedIndex < 0)
foreach (var c in ItemContainerGenerator.Containers)
{
c.ContainerControl.IsVisible = c.Index == SelectedIndex;
}
}
else if (SelectedIndex != -1 && Panel != null)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewStartingIndex > SelectedIndex)
{
if(Items != null && Items.Count() > 0)
{
newIndex = 0;
}
else
{
newIndex = -1;
}
return;
}
MoveToPage(-1, newIndex);
#pragma warning restore 4014
}
break;
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldStartingIndex > SelectedIndex)
{
return;
}
break;
case NotifyCollectionChangedAction.Replace:
if (e.OldStartingIndex > SelectedIndex ||
e.OldStartingIndex + e.OldItems.Count - 1 < SelectedIndex)
{
return;
}
break;
case NotifyCollectionChangedAction.Move:
if (e.OldStartingIndex > SelectedIndex &&
e.NewStartingIndex > SelectedIndex)
{
return;
}
break;
}
if (Items == null || SelectedIndex >= Items.Count())
{
SelectedIndex = Items.Count() - 1;
}
Panel.Children.Clear();
ItemContainerGenerator.Clear();
if (SelectedIndex != -1)
{
GetOrCreateContainer(SelectedIndex);
}
}
}
@ -201,7 +210,7 @@ namespace Avalonia.Controls.Presenters
{
var container = ItemContainerGenerator.ContainerFromIndex(index);
if (container == null)
if (container == null && IsVirtualized)
{
var item = Items.Cast<object>().ElementAt(index);
var materialized = ItemContainerGenerator.Materialize(index, item, MemberSelector);
@ -212,6 +221,18 @@ namespace Avalonia.Controls.Presenters
return container;
}
/// <summary>
/// Called when the <see cref="IsVirtualized"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private void IsVirtualizedChanged(AvaloniaPropertyChangedEventArgs e)
{
if (Panel != null)
{
ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
/// <summary>
/// Called when the <see cref="SelectedIndex"/> property changes.
/// </summary>

125
src/Avalonia.Controls/Presenters/ItemContainerSync.cs

@ -0,0 +1,125 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Utils;
namespace Avalonia.Controls.Presenters
{
internal static class ItemContainerSync
{
public static void ItemsChanged(
ItemsPresenterBase owner,
IEnumerable items,
NotifyCollectionChangedEventArgs e)
{
var generator = owner.ItemContainerGenerator;
var panel = owner.Panel;
if (panel == null)
{
return;
}
void Add()
{
if (e.NewStartingIndex + e.NewItems.Count < items.Count())
{
generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count);
}
AddContainers(owner, e.NewStartingIndex, e.NewItems);
}
void Remove()
{
RemoveContainers(panel, generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count));
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
Add();
break;
case NotifyCollectionChangedAction.Remove:
Remove();
break;
case NotifyCollectionChangedAction.Replace:
RemoveContainers(panel, generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count));
var containers = AddContainers(owner, e.NewStartingIndex, e.NewItems);
var i = e.NewStartingIndex;
foreach (var container in containers)
{
panel.Children[i++] = container.ContainerControl;
}
break;
case NotifyCollectionChangedAction.Move:
Remove();
Add();
break;
case NotifyCollectionChangedAction.Reset:
RemoveContainers(panel, generator.Clear());
if (items != null)
{
AddContainers(owner, 0, items);
}
break;
}
}
private static IList<ItemContainerInfo> AddContainers(
ItemsPresenterBase owner,
int index,
IEnumerable items)
{
var generator = owner.ItemContainerGenerator;
var result = new List<ItemContainerInfo>();
var panel = owner.Panel;
foreach (var item in items)
{
var i = generator.Materialize(index++, item, owner.MemberSelector);
if (i.ContainerControl != null)
{
if (i.Index < panel.Children.Count)
{
// TODO: This will insert at the wrong place when there are null items.
panel.Children.Insert(i.Index, i.ContainerControl);
}
else
{
panel.Children.Add(i.ContainerControl);
}
}
result.Add(i);
}
return result;
}
private static void RemoveContainers(
IPanel panel,
IEnumerable<ItemContainerInfo> items)
{
foreach (var i in items)
{
if (i.ContainerControl != null)
{
panel.Children.Remove(i.ContainerControl);
}
}
}
}
}

48
src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs

@ -60,53 +60,7 @@ namespace Avalonia.Controls.Presenters
public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e)
{
base.ItemsChanged(items, e);
var generator = Owner.ItemContainerGenerator;
var panel = Owner.Panel;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewStartingIndex + e.NewItems.Count < Items.Count())
{
generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count);
}
AddContainers(e.NewStartingIndex, e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveContainers(generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count));
break;
case NotifyCollectionChangedAction.Replace:
RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count));
var containers = AddContainers(e.NewStartingIndex, e.NewItems);
var i = e.NewStartingIndex;
foreach (var container in containers)
{
panel.Children[i++] = container.ContainerControl;
}
break;
case NotifyCollectionChangedAction.Move:
// TODO: Handle move in a more efficient manner. At the moment we just
// drop through to Reset to recreate all the containers.
case NotifyCollectionChangedAction.Reset:
RemoveContainers(generator.Clear());
if (Items != null)
{
AddContainers(0, Items);
}
break;
}
ItemContainerSync.ItemsChanged(Owner, items, e);
Owner.InvalidateMeasure();
}

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

@ -7,6 +7,7 @@ using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Styling;
namespace Avalonia.Controls.Presenters
@ -205,7 +206,13 @@ namespace Avalonia.Controls.Presenters
/// has been set, the items collection has been modified, or the panel has been created.
/// </summary>
/// <param name="e">A description of the change.</param>
protected abstract void ItemsChanged(NotifyCollectionChangedEventArgs e);
/// <remarks>
/// The panel is guaranteed to be created when this method is called.
/// </remarks>
protected virtual void ItemsChanged(NotifyCollectionChangedEventArgs e)
{
ItemContainerSync.ItemsChanged(this, Items, e);
}
/// <summary>
/// Creates the <see cref="Panel"/> when <see cref="ApplyTemplate"/> is called for the first

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

@ -325,14 +325,19 @@ namespace Avalonia.Controls.Primitives
if (_updateCount == 0)
{
var newIndex = -1;
if (SelectedIndex != -1)
{
SelectedIndex = IndexOf((IEnumerable)e.NewValue, SelectedItem);
newIndex = IndexOf((IEnumerable)e.NewValue, SelectedItem);
}
else if (AlwaysSelected && Items != null && Items.Cast<object>().Any())
if (AlwaysSelected && Items != null && Items.Cast<object>().Any())
{
SelectedIndex = 0;
newIndex = 0;
}
SelectedIndex = newIndex;
}
}

10
src/Avalonia.Styling/Styling/IRequiresTemplateInSetter.cs

@ -1,10 +0,0 @@
namespace Avalonia.Styling
{
/// <summary>
/// This is an interface for advanced scenarios to assist users in correct style development.
/// You as a user will not need to use this interface directly.
/// </summary>
public interface IRequiresTemplateInSetter
{
}
}

13
src/Avalonia.Styling/Styling/ISetterValue.cs

@ -0,0 +1,13 @@
namespace Avalonia.Styling
{
/// <summary>
/// Customizes the behavior of a class when added as a value to an <see cref="ISetter"/>.
/// </summary>
public interface ISetterValue
{
/// <summary>
/// Notifies that the object has been added as a setter value.
/// </summary>
void Initialize(ISetter setter);
}
}

8
src/Avalonia.Styling/Styling/Setter.cs

@ -65,13 +65,7 @@ namespace Avalonia.Styling
set
{
if (value is IRequiresTemplateInSetter)
{
throw new ArgumentException(
"Cannot assign a control to Setter.Value. Wrap the control in a <Template>.",
nameof(value));
}
(value as ISetterValue)?.Initialize(this);
_value = value;
}
}

10
src/Avalonia.Themes.Default/TabItem.xaml

@ -24,19 +24,19 @@
<Style Selector="TabItem:disabled">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style>
<Style Selector="TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabItem:pointerover">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
</Style>
<Style Selector="TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabItem:selected">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
</Style>
<Style Selector="TabItem:selected:focus /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabItem:selected:focus">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
</Style>
<Style Selector="TabItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabItem:selected:pointerover">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
</Style>
<Style Selector="TabItem:selected:focus:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabItem:selected:focus:pointerover">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
</Style>
<Style Selector="TabItem[TabStripPlacement=Right]">

110
src/Avalonia.Visuals/Media/FontFamily.cs

@ -11,46 +11,49 @@ namespace Avalonia.Media
{
public class FontFamily
{
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:Avalonia.Media.FontFamily" /> class.
/// </summary>
/// <param name="name">The name of the <see cref="FontFamily"/>.</param>
/// <exception cref="T:System.ArgumentNullException">name</exception>
public FontFamily(string name)
/// <param name="name">The name of the <see cref="T:Avalonia.Media.FontFamily" />.</param>
public FontFamily(string name) : this(null, name)
{
Contract.Requires<ArgumentNullException>(name != null);
FamilyNames = new FamilyNameCollection(new[] { name });
}
/// <summary>
/// Initializes a new instance of the <see cref="T:Avalonia.Media.FontFamily" /> class.
/// </summary>
/// <param name="names">The names of the <see cref="FontFamily"/>.</param>
/// <exception cref="T:System.ArgumentNullException">name</exception>
public FontFamily(IEnumerable<string> names)
/// <param name="baseUri">Specifies the base uri that is used to resolve font family assets.</param>
/// <param name="name">The name of the <see cref="T:Avalonia.Media.FontFamily" />.</param>
/// <exception cref="T:System.ArgumentException">Base uri must be an absolute uri.</exception>
public FontFamily(Uri baseUri, string name)
{
Contract.Requires<ArgumentNullException>(names != null);
if (string.IsNullOrEmpty(name))
{
FamilyNames = new FamilyNameCollection(string.Empty);
FamilyNames = new FamilyNameCollection(names);
}
return;
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:Avalonia.Media.FontFamily" /> class.
/// </summary>
/// <param name="name">The name of the <see cref="T:Avalonia.Media.FontFamily" />.</param>
/// <param name="source">The source of font resources.</param>
/// <param name="baseUri"></param>
public FontFamily(string name, Uri source, Uri baseUri = null) : this(name)
{
Key = new FontFamilyKey(source, baseUri);
var fontFamilySegment = GetFontFamilyIdentifier(name);
if (fontFamilySegment.Source != null)
{
if (baseUri != null && !baseUri.IsAbsoluteUri)
{
throw new ArgumentException("Base uri must be an absolute uri.", nameof(baseUri));
}
Key = new FontFamilyKey(fontFamilySegment.Source, baseUri);
}
FamilyNames = new FamilyNameCollection(fontFamilySegment.Name);
}
/// <summary>
/// Represents the default font family
/// </summary>
public static FontFamily Default => new FontFamily(String.Empty);
public static FontFamily Default => new FontFamily(string.Empty);
/// <summary>
/// Represents all font families in the system. This can be an expensive call depending on platform implementation.
@ -88,46 +91,40 @@ namespace Avalonia.Media
/// <param name="s"></param>
public static implicit operator FontFamily(string s)
{
return Parse(s);
return new FontFamily(s);
}
/// <summary>
/// Parses a <see cref="T:Avalonia.Media.FontFamily"/> string.
/// </summary>
/// <param name="s">The <see cref="T:Avalonia.Media.FontFamily"/> string.</param>
/// <param name="baseUri"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">
/// Specified family is not supported.
/// </exception>
public static FontFamily Parse(string s, Uri baseUri = null)
private struct FontFamilyIdentifier
{
if (string.IsNullOrEmpty(s))
public FontFamilyIdentifier(string name, Uri source)
{
throw new ArgumentException("Specified family is not supported.");
Name = name;
Source = source;
}
var segments = s.Split('#');
public string Name { get; }
public Uri Source { get; }
}
private static FontFamilyIdentifier GetFontFamilyIdentifier(string name)
{
var segments = name.Split('#');
switch (segments.Length)
{
case 1:
{
var names = segments[0].Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x));
return new FontFamily(names);
return new FontFamilyIdentifier(segments[0], null);
}
case 2:
{
var uri = segments[0].StartsWith("/")
? new Uri(segments[0], UriKind.Relative)
: new Uri(segments[0], UriKind.RelativeOrAbsolute);
var source = segments[0].StartsWith("/")
? new Uri(segments[0], UriKind.Relative)
: new Uri(segments[0], UriKind.RelativeOrAbsolute);
return uri.IsAbsoluteUri
? new FontFamily(segments[1], uri)
: new FontFamily(segments[1], uri, baseUri);
return new FontFamilyIdentifier(segments[1], source);
}
default:
@ -137,6 +134,25 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Parses a <see cref="T:Avalonia.Media.FontFamily"/> string.
/// </summary>
/// <param name="s">The <see cref="T:Avalonia.Media.FontFamily"/> string.</param>
/// <param name="baseUri">Specifies the base uri that is used to resolve font family assets.</param>
/// <returns></returns>
/// <exception cref="ArgumentException">
/// Specified family is not supported.
/// </exception>
public static FontFamily Parse(string s, Uri baseUri = null)
{
if (string.IsNullOrEmpty(s))
{
throw new ArgumentException("Specified family is not supported.", nameof(s));
}
return new FontFamily(baseUri, s);
}
/// <summary>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>

35
src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs

@ -4,31 +4,28 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
namespace Avalonia.Media.Fonts
{
using System.Text;
public class FamilyNameCollection : IEnumerable<string>
{
{
/// <summary>
/// Initializes a new instance of the <see cref="FamilyNameCollection"/> class.
/// </summary>
/// <param name="familyNames">The family names.</param>
/// <exception cref="ArgumentException">familyNames</exception>
public FamilyNameCollection(IEnumerable<string> familyNames)
public FamilyNameCollection(string familyNames)
{
Contract.Requires<ArgumentNullException>(familyNames != null);
var names = new List<string>(familyNames);
if (names.Count == 0) throw new ArgumentException($"{nameof(familyNames)} must not be empty.");
if (familyNames == null)
{
throw new ArgumentNullException(nameof(familyNames));
}
Names = new ReadOnlyCollection<string>(names);
Names = familyNames.Split(',').Select(x => x.Trim()).ToArray();
PrimaryFamilyName = Names.First();
PrimaryFamilyName = Names[0];
HasFallbacks = Names.Count > 1;
}
@ -55,7 +52,7 @@ namespace Avalonia.Media.Fonts
/// <value>
/// The names.
/// </value>
internal ReadOnlyCollection<string> Names { get; }
internal IReadOnlyList<string> Names { get; }
/// <inheritdoc />
/// <summary>
@ -95,7 +92,10 @@ namespace Avalonia.Media.Fonts
{
builder.Append(Names[index]);
if (index == Names.Count - 1) break;
if (index == Names.Count - 1)
{
break;
}
builder.Append(", ");
}
@ -123,9 +123,12 @@ namespace Avalonia.Media.Fonts
/// </returns>
public override bool Equals(object obj)
{
if (!(obj is FamilyNameCollection other)) return false;
if (!(obj is FamilyNameCollection other))
{
return false;
}
return other.ToString().Equals(ToString());
}
}
}
}

2
src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs

@ -95,7 +95,7 @@ namespace Avalonia.Media.Fonts
{
if (!Source.IsAbsoluteUri && BaseUri != null)
{
return BaseUri.Authority + Source;
return BaseUri.AbsoluteUri + Source.OriginalString;
}
return Source.ToString();

13
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
@ -13,7 +12,6 @@ using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using System.Threading.Tasks;
namespace Avalonia.Rendering
{
@ -33,7 +31,6 @@ namespace Avalonia.Rendering
private volatile IRef<Scene> _scene;
private DirtyVisuals _dirty;
private IRef<IRenderTargetBitmapImpl> _overlay;
private object _rendering = new object();
private int _lastSceneId = -1;
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IRef<IDrawOperation> _currentDraw;
@ -267,7 +264,7 @@ namespace Avalonia.Rendering
RenderOverlay(scene.Item, GetContext());
if (updated || forceComposite || overlay)
RenderComposite(scene.Item, GetContext());
}
}
}
}
finally
@ -321,15 +318,15 @@ namespace Avalonia.Rendering
var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true);
return (rs, true);
}
// Indicate that we have updated the layers
return (sceneRef.Clone(), true);
}
// Just return scene, layers weren't updated
return (sceneRef.Clone(), false);
}
}
@ -456,7 +453,7 @@ namespace Avalonia.Rendering
private void RenderComposite(Scene scene, IDrawingContextImpl context)
{
context.Clear(Colors.Transparent);
var clientRect = new Rect(scene.Size);
foreach (var layer in scene.Layers)

6
src/Avalonia.X11/XIStructs.cs

@ -197,7 +197,7 @@ namespace Avalonia.X11
unsafe struct XIDeviceChangedEvent
{
public int Type; /* GenericEvent */
public ulong Serial; /* # of last request processed by server */
public UIntPtr Serial; /* # of last request processed by server */
public Bool SendEvent; /* true if this came from a SendEvent request */
public IntPtr Display; /* Display the event was read from */
public int Extension; /* XI extension offset */
@ -214,7 +214,7 @@ namespace Avalonia.X11
struct XIDeviceEvent
{
public XEventName type; /* GenericEvent */
public ulong serial; /* # of last request processed by server */
public UIntPtr serial; /* # of last request processed by server */
public Bool send_event; /* true if this came from a SendEvent request */
public IntPtr display; /* Display the event was read from */
public int extension; /* XI extension offset */
@ -241,7 +241,7 @@ namespace Avalonia.X11
unsafe struct XIEvent
{
public int type; /* GenericEvent */
public ulong serial; /* # of last request processed by server */
public UIntPtr serial; /* # of last request processed by server */
public Bool send_event; /* true if this came from a SendEvent request */
public IntPtr display; /* Display the event was read from */
public int extension; /* XI extension offset */

17
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@ -3,6 +3,7 @@ using System.Globalization;
using System.Reactive.Subjects;
using Avalonia.Data.Converters;
using Avalonia.Reactive;
using Avalonia.Styling;
namespace Avalonia.Data
{
@ -12,8 +13,10 @@ namespace Avalonia.Data
public class TemplateBinding : SingleSubscriberObservableBase<object>,
IBinding,
IDescription,
ISubject<object>
ISubject<object>,
ISetterValue
{
private bool _isSetterValue;
private IStyledElement _target;
private Type _targetType;
@ -35,10 +38,11 @@ namespace Avalonia.Data
{
// Usually each `TemplateBinding` will only be instantiated once; in this case we can
// use the `TemplateBinding` object itself as the instanced binding in order to save
// allocating a new object. If the binding *is* instantiated more than once (which can
// happen if it appears in a `Setter` for example, then just make a clone and instantiate
// that.
if (_target == null)
// allocating a new object.
//
// If the binding appears in a `Setter`, then make a clone and instantiate that because
// because the setter can outlive the control and cause a leak.
if (_target == null && !_isSetterValue)
{
_target = (IStyledElement)target;
_targetType = targetProperty?.PropertyType;
@ -106,6 +110,9 @@ namespace Avalonia.Data
}
}
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter) => _isSetterValue = true;
protected override void Subscribed()
{
TemplatedParentChanged();

61
tests/Avalonia.Controls.UnitTests/CarouselTests.cs

@ -76,25 +76,6 @@ namespace Avalonia.Controls.UnitTests
Assert.Single(target.ItemContainerGenerator.Containers);
}
[Fact]
public void Should_Not_Remove_NonCurrent_Page_When_IsVirtualized_False()
{
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = new[] { "foo", "bar" },
IsVirtualized = false,
SelectedIndex = 0,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Single(target.ItemContainerGenerator.Containers);
target.SelectedIndex = 1;
Assert.Equal(2, target.ItemContainerGenerator.Containers.Count());
}
[Fact]
public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes()
{
@ -115,9 +96,9 @@ namespace Avalonia.Controls.UnitTests
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Single(target.GetLogicalChildren());
Assert.Equal(3, target.GetLogicalChildren().Count());
var child = target.GetLogicalChildren().Single();
var child = target.GetLogicalChildren().First();
Assert.IsType<TextBlock>(child);
Assert.Equal("Foo", ((TextBlock)child).Text);
@ -127,7 +108,7 @@ namespace Avalonia.Controls.UnitTests
target.Items = newItems;
child = target.GetLogicalChildren().Single();
child = target.GetLogicalChildren().First();
Assert.IsType<TextBlock>(child);
Assert.Equal("Bar", ((TextBlock)child).Text);
@ -146,7 +127,8 @@ namespace Avalonia.Controls.UnitTests
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items
Items = items,
IsVirtualized = true,
};
target.ApplyTemplate();
@ -170,6 +152,29 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal("Bar", ((TextBlock)child).Text);
}
[Fact]
public void Selected_Item_Changes_To_First_Item_When_Item_Added()
{
var items = new ObservableCollection<string>();
var target = new Carousel
{
Template = new FuncControlTemplate<Carousel>(CreateTemplate),
Items = items,
IsVirtualized = false
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Equal(-1, target.SelectedIndex);
Assert.Empty(target.GetLogicalChildren());
items.Add("Foo");
Assert.Equal(0, target.SelectedIndex);
Assert.Single(target.GetLogicalChildren());
}
[Fact]
public void Selected_Index_Changes_To_When_Items_Assigned_Null()
{
@ -190,9 +195,9 @@ namespace Avalonia.Controls.UnitTests
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Single(target.GetLogicalChildren());
Assert.Equal(3, target.GetLogicalChildren().Count());
var child = target.GetLogicalChildren().Single();
var child = target.GetLogicalChildren().First();
Assert.IsType<TextBlock>(child);
Assert.Equal("Foo", ((TextBlock)child).Text);
@ -254,16 +259,16 @@ namespace Avalonia.Controls.UnitTests
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Single(target.GetLogicalChildren());
Assert.Equal(3, target.GetLogicalChildren().Count());
var child = target.GetLogicalChildren().Single();
var child = target.GetLogicalChildren().First();
Assert.IsType<TextBlock>(child);
Assert.Equal("Foo", ((TextBlock)child).Text);
items.RemoveAt(0);
child = target.GetLogicalChildren().Single();
child = target.GetLogicalChildren().First();
Assert.IsType<TextBlock>(child);
Assert.Equal("Bar", ((TextBlock)child).Text);

17
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@ -1,6 +1,7 @@
using System;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Interactivity;
using Moq;
using Xunit;
@ -110,6 +111,22 @@ namespace Avalonia.Controls.UnitTests.Platform
Assert.True(e.Handled);
}
[Fact]
public void Click_On_Open_TopLevel_Menu_Closes_Menu()
{
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var item = Mock.Of<IMenuItem>(x =>
x.IsSubMenuOpen == true &&
x.IsTopLevel == true &&
x.HasSubMenu == true &&
x.Parent == menu);
var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
target.PointerPressed(item, e);
Mock.Get(menu).Verify(x => x.Close());
}
[Fact]
public void PointerEnter_Opens_Item_When_Old_Item_Is_Open()
{

753
tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs

@ -8,6 +8,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Xunit;
using System.Collections.ObjectModel;
using System.Collections;
namespace Avalonia.Controls.UnitTests.Presenters
{
@ -49,148 +50,672 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.IsType<ItemContainerGenerator<TestItem>>(target.ItemContainerGenerator);
}
[Fact]
public void Setting_SelectedIndex_Should_Show_Page()
public class Virtualized
{
var target = new CarouselPresenter
[Fact]
public void Should_Initially_Materialize_Selected_Container()
{
Items = new[] { "foo", "bar" },
SelectedIndex = 0,
};
var target = new CarouselPresenter
{
Items = new[] { "foo", "bar" },
SelectedIndex = 0,
IsVirtualized = true,
};
target.ApplyTemplate();
target.ApplyTemplate();
Assert.IsType<ContentPresenter>(target.Panel.Children[0]);
Assert.Equal("foo", ((ContentPresenter)target.Panel.Children[0]).Content);
}
AssertSingle(target);
}
[Fact]
public void Changing_SelectedIndex_Should_Show_Page()
{
var target = new CarouselPresenter
[Fact]
public void Should_Initially_Materialize_Nothing_If_No_Selected_Container()
{
Items = new[] { "foo", "bar" },
SelectedIndex = 0,
};
var target = new CarouselPresenter
{
Items = new[] { "foo", "bar" },
IsVirtualized = true,
};
target.ApplyTemplate();
target.SelectedIndex = 1;
target.ApplyTemplate();
Assert.IsType<ContentPresenter>(target.Panel.Children[0]);
Assert.Equal("bar", ((ContentPresenter)target.Panel.Children[0]).Content);
}
Assert.Empty(target.Panel.Children);
Assert.Empty(target.ItemContainerGenerator.Containers);
}
[Fact]
public void Should_Remove_NonCurrent_Page_When_IsVirtualized_True()
{
var target = new CarouselPresenter
[Fact]
public void Switching_To_Virtualized_Should_Reset_Containers()
{
Items = new[] { "foo", "bar" },
IsVirtualized = true,
SelectedIndex = 0,
};
var target = new CarouselPresenter
{
Items = new[] { "foo", "bar" },
SelectedIndex = 0,
IsVirtualized = false,
};
target.ApplyTemplate();
Assert.Single(target.ItemContainerGenerator.Containers);
target.SelectedIndex = 1;
Assert.Single(target.ItemContainerGenerator.Containers);
}
target.ApplyTemplate();
target.IsVirtualized = true;
[Fact]
public void Should_Not_Remove_NonCurrent_Page_When_IsVirtualized_False()
{
var target = new CarouselPresenter
AssertSingle(target);
}
[Fact]
public void Changing_SelectedIndex_Should_Show_Page()
{
Items = new[] { "foo", "bar" },
IsVirtualized = false,
SelectedIndex = 0,
};
var target = new CarouselPresenter
{
Items = new[] { "foo", "bar" },
SelectedIndex = 0,
IsVirtualized = true,
};
target.ApplyTemplate();
AssertSingle(target);
target.SelectedIndex = 1;
AssertSingle(target);
}
target.ApplyTemplate();
Assert.Single(target.ItemContainerGenerator.Containers);
Assert.Single(target.Panel.Children);
target.SelectedIndex = 1;
Assert.Equal(2, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(2, target.Panel.Children.Count);
target.SelectedIndex = 0;
Assert.Equal(2, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(2, target.Panel.Children.Count);
}
[Fact]
public void Should_Remove_NonCurrent_Page()
{
var target = new CarouselPresenter
{
Items = new[] { "foo", "bar" },
IsVirtualized = true,
SelectedIndex = 0,
};
[Fact]
public void Should_Remove_Controls_When_IsVirtualized_Is_False()
{
ObservableCollection<string> items = new ObservableCollection<string>();
var target = new CarouselPresenter
target.ApplyTemplate();
AssertSingle(target);
target.SelectedIndex = 1;
AssertSingle(target);
}
[Fact]
public void Should_Handle_Inserting_Item_At_SelectedItem()
{
Items = items,
SelectedIndex = 0,
IsVirtualized = false,
};
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = true,
};
target.ApplyTemplate();
items.Insert(1, "item1a");
AssertSingle(target);
}
target.ApplyTemplate();
target.SelectedIndex = 0;
items.Add("foo");
target.SelectedIndex = 0;
[Fact]
public void Should_Handle_Inserting_Item_Before_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 2,
IsVirtualized = true,
};
target.ApplyTemplate();
items.Insert(1, "item1a");
AssertSingle(target);
}
[Fact]
public void Should_Do_Nothing_When_Inserting_Item_After_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = true,
};
target.ApplyTemplate();
var child = AssertSingle(target);
items.Insert(2, "after");
Assert.Same(child, AssertSingle(target));
}
Assert.Single(target.ItemContainerGenerator.Containers);
Assert.Single(target.Panel.Children);
[Fact]
public void Should_Handle_Removing_Item_At_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = true,
};
target.ApplyTemplate();
items.RemoveAt(1);
AssertSingle(target);
}
items.Add("bar");
Assert.Single(target.ItemContainerGenerator.Containers);
Assert.Single(target.Panel.Children);
[Fact]
public void Should_Handle_Removing_Item_Before_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = true,
};
target.ApplyTemplate();
items.RemoveAt(0);
AssertSingle(target);
}
[Fact]
public void Should_Do_Nothing_When_Removing_Item_After_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = true,
};
target.ApplyTemplate();
var child = AssertSingle(target);
items.RemoveAt(2);
Assert.Same(child, AssertSingle(target));
}
target.SelectedIndex = 1;
Assert.Equal(2, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(2, target.Panel.Children.Count);
[Fact]
public void Should_Handle_Removing_SelectedItem_When_Its_Last()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 2,
IsVirtualized = true,
};
target.ApplyTemplate();
items.RemoveAt(2);
Assert.Equal(1, target.SelectedIndex);
AssertSingle(target);
}
items.Remove(items[0]);
Assert.Single(target.ItemContainerGenerator.Containers);
Assert.Single(target.Panel.Children);
[Fact]
public void Should_Handle_Removing_Last_Item()
{
var items = new ObservableCollection<string>
{
"item0",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 0,
IsVirtualized = true,
};
target.ApplyTemplate();
items.RemoveAt(0);
Assert.Empty(target.Panel.Children);
Assert.Empty(target.ItemContainerGenerator.Containers);
}
items.Remove(items[0]);
Assert.Empty(target.ItemContainerGenerator.Containers);
Assert.Empty(target.Panel.Children);
[Fact]
public void Should_Handle_Replacing_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = true,
};
target.ApplyTemplate();
items[1] = "replaced";
AssertSingle(target);
}
[Fact]
public void Should_Do_Nothing_When_Replacing_Non_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = true,
};
target.ApplyTemplate();
var child = AssertSingle(target);
items[0] = "replaced";
Assert.Same(child, AssertSingle(target));
}
[Fact]
public void Should_Handle_Moving_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = true,
};
target.ApplyTemplate();
items.Move(1, 0);
AssertSingle(target);
}
private static IControl AssertSingle(CarouselPresenter target)
{
var items = (IList)target.Items;
var index = target.SelectedIndex;
var content = items[index];
var child = Assert.Single(target.Panel.Children);
var presenter = Assert.IsType<ContentPresenter>(child);
var container = Assert.Single(target.ItemContainerGenerator.Containers);
var visible = Assert.Single(target.Panel.Children.Where(x => x.IsVisible));
Assert.Same(child, container.ContainerControl);
Assert.Same(child, visible);
Assert.Equal(content, presenter.Content);
Assert.Equal(content, container.Item);
Assert.Equal(index, container.Index);
return child;
}
}
[Fact]
public void Should_Have_Correct_ItemsContainer_Index()
public class NonVirtualized
{
ObservableCollection<string> items = new ObservableCollection<string>();
[Fact]
public void Should_Initially_Materialize_All_Containers()
{
var target = new CarouselPresenter
{
Items = new[] { "foo", "bar" },
IsVirtualized = false,
};
target.ApplyTemplate();
AssertAll(target);
}
var target = new CarouselPresenter
[Fact]
public void Should_Initially_Show_Selected_Item()
{
Items = items,
SelectedIndex = 0,
IsVirtualized = false,
};
var target = new CarouselPresenter
{
Items = new[] { "foo", "bar" },
SelectedIndex = 1,
IsVirtualized = false,
};
target.ApplyTemplate();
AssertAll(target);
}
target.ApplyTemplate();
target.SelectedIndex = 0;
items.Add("foo");
target.SelectedIndex = 0;
Assert.Single(target.ItemContainerGenerator.Containers);
Assert.Single(target.Panel.Children);
items.Add("bar");
Assert.Single(target.ItemContainerGenerator.Containers);
Assert.Single(target.Panel.Children);
target.SelectedIndex = 1;
Assert.Equal(2, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(2, target.Panel.Children.Count);
Assert.Equal(0, target.ItemContainerGenerator.Containers.First().Index);
items.Remove(items[0]);
Assert.Single(target.ItemContainerGenerator.Containers);
Assert.Single(target.Panel.Children);
Assert.Equal(0, target.ItemContainerGenerator.Containers.First().Index);
items.Remove(items[0]);
Assert.Empty(target.ItemContainerGenerator.Containers);
Assert.Empty(target.Panel.Children);
[Fact]
public void Switching_To_Non_Virtualized_Should_Reset_Containers()
{
var target = new CarouselPresenter
{
Items = new[] { "foo", "bar" },
SelectedIndex = 0,
IsVirtualized = true,
};
target.ApplyTemplate();
target.IsVirtualized = false;
AssertAll(target);
}
[Fact]
public void Changing_SelectedIndex_Should_Show_Page()
{
var target = new CarouselPresenter
{
Items = new[] { "foo", "bar" },
SelectedIndex = 0,
IsVirtualized = false,
};
target.ApplyTemplate();
AssertAll(target);
target.SelectedIndex = 1;
AssertAll(target);
}
[Fact]
public void Should_Handle_Inserting_Item_At_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = false,
};
target.ApplyTemplate();
items.Insert(1, "item1a");
AssertAll(target);
}
[Fact]
public void Should_Handle_Inserting_Item_Before_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 2,
IsVirtualized = false,
};
target.ApplyTemplate();
items.Insert(1, "item1a");
AssertAll(target);
}
[Fact]
public void Should_Do_Handle_Inserting_Item_After_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = false,
};
target.ApplyTemplate();
items.Insert(2, "after");
AssertAll(target);
}
[Fact]
public void Should_Handle_Removing_Item_At_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = false,
};
target.ApplyTemplate();
items.RemoveAt(1);
AssertAll(target);
}
[Fact]
public void Should_Handle_Removing_Item_Before_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = false,
};
target.ApplyTemplate();
items.RemoveAt(0);
AssertAll(target);
}
[Fact]
public void Should_Handle_Removing_Item_After_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = false,
};
target.ApplyTemplate();
items.RemoveAt(2);
AssertAll(target);
}
[Fact]
public void Should_Handle_Removing_SelectedItem_When_Its_Last()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 2,
IsVirtualized = false,
};
target.ApplyTemplate();
items.RemoveAt(2);
Assert.Equal(1, target.SelectedIndex);
AssertAll(target);
}
[Fact]
public void Should_Handle_Removing_Last_Item()
{
var items = new ObservableCollection<string>
{
"item0",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 0,
IsVirtualized = false,
};
target.ApplyTemplate();
items.RemoveAt(0);
Assert.Empty(target.Panel.Children);
Assert.Empty(target.ItemContainerGenerator.Containers);
}
[Fact]
public void Should_Handle_Replacing_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = false,
};
target.ApplyTemplate();
items[1] = "replaced";
AssertAll(target);
}
[Fact]
public void Should_Handle_Moving_SelectedItem()
{
var items = new ObservableCollection<string>
{
"item0",
"item1",
"item2",
};
var target = new CarouselPresenter
{
Items = items,
SelectedIndex = 1,
IsVirtualized = false,
};
target.ApplyTemplate();
items.Move(1, 0);
AssertAll(target);
}
private static void AssertAll(CarouselPresenter target)
{
var items = (IList)target.Items;
Assert.Equal(items?.Count ?? 0, target.Panel.Children.Count);
Assert.Equal(items?.Count ?? 0, target.ItemContainerGenerator.Containers.Count());
for (var i = 0; i < items?.Count; ++i)
{
var content = items[i];
var child = target.Panel.Children[i];
var presenter = Assert.IsType<ContentPresenter>(child);
var container = target.ItemContainerGenerator.ContainerFromIndex(i);
Assert.Same(child, container);
Assert.Equal(i == target.SelectedIndex, child.IsVisible);
Assert.Equal(content, presenter.Content);
Assert.Equal(i, target.ItemContainerGenerator.IndexFromContainer(container));
}
}
}
private class TestItem : ContentControl

33
tests/Avalonia.LeakTests/ControlTests.cs

@ -308,6 +308,39 @@ namespace Avalonia.LeakTests
}
[Fact]
public void Slider_Is_Freed()
{
using (Start())
{
Func<Window> run = () =>
{
var window = new Window
{
Content = new Slider()
};
window.Show();
// Do a layout and make sure that Slider gets added to visual tree.
window.LayoutManager.ExecuteInitialLayoutPass(window);
Assert.IsType<Slider>(window.Presenter.Child);
// Clear the content and ensure the Slider is removed.
window.Content = null;
window.LayoutManager.ExecuteLayoutPass();
Assert.Null(window.Presenter.Child);
return window;
};
var result = run();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Slider>()).ObjectsCount));
}
}
[Fact]
public void RendererIsDisposed()
{

22
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@ -100,6 +100,28 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal("baz", target.Text);
}
[Fact]
public void OneWayToSource_Binding_Should_React_To_DataContext_Changed()
{
var target = new TextBlock { Text = "bar" };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.OneWayToSource,
};
target.Bind(TextBox.TextProperty, binding);
var source = new Source { Foo = "foo" };
target.DataContext = source;
Assert.Equal("bar", source.Foo);
target.Text = "baz";
Assert.Equal("baz", source.Foo);
source.Foo = "quz";
Assert.Equal("baz", target.Text);
}
[Fact]
public void Default_BindingMode_Should_Be_Used()
{

29
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

@ -329,6 +329,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal("Hello world", textBlock.Text);
}
}
}
[Fact]
public void Binding_OneWayToSource_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
ShowInTaskbar='{Binding ShowInTaskbar, Mode=OneWayToSource}'>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var viewModel = new WindowViewModel();
window.DataContext = viewModel;
window.ApplyTemplate();
Assert.True(window.ShowInTaskbar);
Assert.True(viewModel.ShowInTaskbar);
}
}
private class WindowViewModel
{
public bool ShowInTaskbar { get; set; }
}
}
}

14
tests/Avalonia.Styling.UnitTests/SetterTests.cs

@ -1,17 +1,15 @@
// 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 System.Reactive.Subjects;
using Moq;
using Avalonia.Controls;
using Avalonia.Data;
using Xunit;
using System;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Data;
using Avalonia.Markup;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Moq;
using Xunit;
namespace Avalonia.Styling.UnitTests
{
@ -22,7 +20,7 @@ namespace Avalonia.Styling.UnitTests
{
var target = new Setter();
Assert.Throws<ArgumentException>(() => target.Value = new Border());
Assert.Throws<InvalidOperationException>(() => target.Value = new Border());
}
[Fact]

48
tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.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 System.Collections.Generic;
using System.Linq;
using Avalonia.Media;
using Avalonia.Media.Fonts;
@ -12,18 +11,6 @@ namespace Avalonia.Visuals.UnitTests.Media
{
public class FontFamilyTests
{
[Fact]
public void Exception_Should_Be_Thrown_If_Name_Is_Null()
{
Assert.Throws<ArgumentNullException>(() => new FontFamily((string)null));
}
[Fact]
public void Exception_Should_Be_Thrown_If_Names_Is_Null()
{
Assert.Throws<ArgumentNullException>(() => new FontFamily((IEnumerable<string>)null));
}
[Fact]
public void Should_Implicitly_Convert_String_To_FontFamily()
{
@ -41,7 +28,7 @@ namespace Avalonia.Visuals.UnitTests.Media
}
[Fact]
public void Parse_Parses_FontFamily_With_Name()
public void Should_Parse_FontFamily_With_SystemFont_Name()
{
var fontFamily = FontFamily.Parse("Courier New");
@ -49,7 +36,7 @@ namespace Avalonia.Visuals.UnitTests.Media
}
[Fact]
public void Parse_Parses_FontFamily_With_Names()
public void Should_Parse_FontFamily_With_Fallbacks()
{
var fontFamily = FontFamily.Parse("Courier New, Times New Roman");
@ -61,7 +48,7 @@ namespace Avalonia.Visuals.UnitTests.Media
}
[Fact]
public void Parse_Parses_FontFamily_With_Resource_Folder()
public void Should_Parse_FontFamily_With_Resource_Folder()
{
var source = new Uri("resm:Avalonia.Visuals.UnitTests#MyFont");
@ -75,7 +62,7 @@ namespace Avalonia.Visuals.UnitTests.Media
}
[Fact]
public void Parse_Parses_FontFamily_With_Resource_Filename()
public void Should_Parse_FontFamily_With_Resource_Filename()
{
var source = new Uri("resm:Avalonia.Visuals.UnitTests.MyFont.ttf#MyFont");
@ -87,5 +74,32 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Equal(key, fontFamily.Key);
}
[Theory]
[InlineData("resm:Avalonia.Visuals.UnitTests/Assets/Fonts#MyFont")]
[InlineData("avares://Avalonia.Visuals.UnitTests/Assets/Fonts#MyFont")]
public void Should_Create_FontFamily_From_Uri(string name)
{
var fontFamily = new FontFamily(name);
Assert.Equal("MyFont", fontFamily.Name);
Assert.NotNull(fontFamily.Key);
}
[Theory]
[InlineData("resm:Avalonia.Visuals.UnitTests.Assets.Fonts", "#MyFont")]
[InlineData("avares://Avalonia.Visuals.UnitTests/Assets/Fonts", "#MyFont")]
[InlineData("avares://Avalonia.Visuals.UnitTests", "/Assets/Fonts#MyFont")]
public void Should_Create_FontFamily_From_Uri_With_Base_Uri(string @base, string name)
{
var baseUri = new Uri(@base);
var fontFamily = new FontFamily(baseUri, name);
Assert.Equal("MyFont", fontFamily.Name);
Assert.NotNull(fontFamily.Key);
}
}
}

11
tests/Avalonia.Visuals.UnitTests/Media/Fonts/FamilyNameCollectionTests.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 System.Linq;
using Avalonia.Media.Fonts;
using Xunit;
@ -16,18 +15,12 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts
Assert.Throws<ArgumentNullException>(() => new FamilyNameCollection(null));
}
[Fact]
public void Exception_Should_Be_Thrown_If_Names_Is_Empty()
{
Assert.Throws<ArgumentException>(() => new FamilyNameCollection(Enumerable.Empty<string>()));
}
[Fact]
public void Should_Be_Equal()
{
var familyNames = new FamilyNameCollection(new[] { "Arial", "Times New Roman" });
var familyNames = new FamilyNameCollection("Arial, Times New Roman");
Assert.Equal(new FamilyNameCollection(new[] { "Arial", "Times New Roman" }), familyNames);
Assert.Equal(new FamilyNameCollection("Arial, Times New Roman"), familyNames);
}
}
}

Loading…
Cancel
Save