diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index ba2c7c91d8..f887b987e0 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -38,7 +38,6 @@ namespace Avalonia.Controls.Presenters Carousel.PageTransitionProperty.AddOwner(); private int _selectedIndex = -1; - // private Task _current; private Task _currentTransition; private int _queuedTransitionIndex = -1; @@ -47,6 +46,7 @@ namespace Avalonia.Controls.Presenters /// static CarouselPresenter() { + IsVirtualizedProperty.Changed.AddClassHandler(x => x.IsVirtualizedChanged); SelectedIndexProperty.Changed.AddClassHandler(x => x.SelectedIndexChanged); } @@ -94,58 +94,67 @@ namespace Avalonia.Controls.Presenters set { SetValue(PageTransitionProperty, value); } } - /// - protected override void PanelCreated(IPanel panel) - { -#pragma warning disable 4014 - MoveToPage(-1, SelectedIndex); -#pragma warning restore 4014 - } - /// 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); + } } } @@ -212,6 +221,18 @@ namespace Avalonia.Controls.Presenters return container; } + /// + /// Called when the property changes. + /// + /// The event args. + private void IsVirtualizedChanged(AvaloniaPropertyChangedEventArgs e) + { + if (Panel != null) + { + ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + /// /// Called when the property changes. /// diff --git a/src/Avalonia.Controls/Presenters/ItemContainerSync.cs b/src/Avalonia.Controls/Presenters/ItemContainerSync.cs new file mode 100644 index 0000000000..035d404dec --- /dev/null +++ b/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 AddContainers( + ItemsPresenterBase owner, + int index, + IEnumerable items) + { + var generator = owner.ItemContainerGenerator; + var result = new List(); + 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 items) + { + foreach (var i in items) + { + if (i.ContainerControl != null) + { + panel.Children.Remove(i.ContainerControl); + } + } + } + } +} diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs index 8947b30e63..413855bcc6 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs +++ b/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(); } diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index e9dc75a236..b4b792139d 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/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. /// /// A description of the change. - protected abstract void ItemsChanged(NotifyCollectionChangedEventArgs e); + /// + /// The panel is guaranteed to be created when this method is called. + /// + protected virtual void ItemsChanged(NotifyCollectionChangedEventArgs e) + { + ItemContainerSync.ItemsChanged(this, Items, e); + } /// /// Creates the when is called for the first diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index a64dbe0546..280c3ad93a 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/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().Any()) + + if (AlwaysSelected && Items != null && Items.Cast().Any()) { - SelectedIndex = 0; + newIndex = 0; } + + SelectedIndex = newIndex; } } diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index 5c152cd8a0..a486723d86 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -11,46 +11,49 @@ namespace Avalonia.Media { public class FontFamily { + /// /// /// Initializes a new instance of the class. /// - /// The name of the . - /// name - public FontFamily(string name) + /// The name of the . + public FontFamily(string name) : this(null, name) { - Contract.Requires(name != null); - - FamilyNames = new FamilyNameCollection(new[] { name }); } /// /// Initializes a new instance of the class. /// - /// The names of the . - /// name - public FontFamily(IEnumerable names) + /// Specifies the base uri that is used to resolve font family assets. + /// The name of the . + /// Base uri must be an absolute uri. + public FontFamily(Uri baseUri, string name) { - Contract.Requires(names != null); + if (string.IsNullOrEmpty(name)) + { + FamilyNames = new FamilyNameCollection(string.Empty); - FamilyNames = new FamilyNameCollection(names); - } + return; + } - /// - /// - /// Initializes a new instance of the class. - /// - /// The name of the . - /// The source of font resources. - /// - 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); } /// /// Represents the default font family /// - public static FontFamily Default => new FontFamily(String.Empty); + public static FontFamily Default => new FontFamily(string.Empty); /// /// Represents all font families in the system. This can be an expensive call depending on platform implementation. @@ -88,46 +91,40 @@ namespace Avalonia.Media /// public static implicit operator FontFamily(string s) { - return Parse(s); + return new FontFamily(s); } - /// - /// Parses a string. - /// - /// The string. - /// - /// - /// - /// Specified family is not supported. - /// - 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 } } + /// + /// Parses a string. + /// + /// The string. + /// Specifies the base uri that is used to resolve font family assets. + /// + /// + /// Specified family is not supported. + /// + 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); + } + /// /// Returns a that represents this instance. /// diff --git a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs b/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs index 50511d2fb7..acf0bbdb11 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs +++ b/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 - { + { /// /// Initializes a new instance of the class. /// /// The family names. /// familyNames - public FamilyNameCollection(IEnumerable familyNames) + public FamilyNameCollection(string familyNames) { - Contract.Requires(familyNames != null); - - var names = new List(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(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 /// /// The names. /// - internal ReadOnlyCollection Names { get; } + internal IReadOnlyList Names { get; } /// /// @@ -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 /// 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()); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index 76ee2b7aad..7733dd7d2a 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/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(); diff --git a/src/Avalonia.X11/XIStructs.cs b/src/Avalonia.X11/XIStructs.cs index 772bd1989c..ef49a72c43 100644 --- a/src/Avalonia.X11/XIStructs.cs +++ b/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 */ diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs index f36e5864f6..2070b4b66a 100644 --- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs +++ b/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(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(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(child); Assert.Equal("Bar", ((TextBlock)child).Text); @@ -146,7 +127,8 @@ namespace Avalonia.Controls.UnitTests var target = new Carousel { Template = new FuncControlTemplate(CreateTemplate), - Items = items + Items = items, + IsVirtualized = true, }; target.ApplyTemplate(); @@ -190,9 +172,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(child); Assert.Equal("Foo", ((TextBlock)child).Text); @@ -254,16 +236,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(child); Assert.Equal("Foo", ((TextBlock)child).Text); items.RemoveAt(0); - child = target.GetLogicalChildren().Single(); + child = target.GetLogicalChildren().First(); Assert.IsType(child); Assert.Equal("Bar", ((TextBlock)child).Text); diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs index c5ff64b0e3..2cc9965740 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs +++ b/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>(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(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(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 items = new ObservableCollection(); - - 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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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(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 items = new ObservableCollection(); + [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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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 + { + "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(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 diff --git a/tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs index 4c7a89cbda..75ae43a1fa 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/FontFamilyTests.cs +++ b/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(() => new FontFamily((string)null)); - } - - [Fact] - public void Exception_Should_Be_Thrown_If_Names_Is_Null() - { - Assert.Throws(() => new FontFamily((IEnumerable)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); + } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FamilyNameCollectionTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FamilyNameCollectionTests.cs index 14daf5bd8b..e7aa9daab3 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FamilyNameCollectionTests.cs +++ b/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(() => new FamilyNameCollection(null)); } - [Fact] - public void Exception_Should_Be_Thrown_If_Names_Is_Empty() - { - Assert.Throws(() => new FamilyNameCollection(Enumerable.Empty())); - } - [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); } } }