Browse Source

Merge branch 'master' into prs/layoutissue

pull/2297/head
Jumar Macato 7 years ago
committed by GitHub
parent
commit
29a1f8e68c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  2. 2
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  3. 22
      src/Avalonia.Base/ISupportInitialize.cs
  4. 2
      src/Avalonia.Controls/AutoCompleteBox.cs
  5. 2
      src/Avalonia.Controls/Button.cs
  6. 1
      src/Avalonia.Controls/Control.cs
  7. 76
      src/Avalonia.Controls/DropDown.cs
  8. 1
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  9. 1
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  10. 17
      src/Avalonia.Controls/Image.cs
  11. 2
      src/Avalonia.Controls/ItemsControl.cs
  12. 4
      src/Avalonia.Controls/MenuItem.cs
  13. 2
      src/Avalonia.Controls/PixelPointEventArgs.cs
  14. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  15. 1
      src/Avalonia.Controls/WindowBase.cs
  16. 2
      src/Avalonia.Native/ScreenImpl.cs
  17. 224
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  18. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  19. 3
      src/Avalonia.X11/X11Screens.cs
  20. 3
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  21. 35
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs
  22. 5
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs
  23. 56
      tests/Avalonia.Controls.UnitTests/ImageTests.cs
  24. 1
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  25. 3
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs
  26. 104
      tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
  27. 1
      tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

12
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -13,8 +13,8 @@ namespace Avalonia
/// </summary> /// </summary>
public class AvaloniaPropertyRegistry public class AvaloniaPropertyRegistry
{ {
private readonly List<AvaloniaProperty> _properties = private readonly Dictionary<int, AvaloniaProperty> _properties =
new List<AvaloniaProperty>(); new Dictionary<int, AvaloniaProperty>();
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered = private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>(); new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached = private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
@ -33,7 +33,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets a list of all registered properties. /// Gets a list of all registered properties.
/// </summary> /// </summary>
internal IReadOnlyList<AvaloniaProperty> Properties => _properties; internal IReadOnlyCollection<AvaloniaProperty> Properties => _properties.Values;
/// <summary> /// <summary>
/// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type. /// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
@ -220,7 +220,11 @@ namespace Avalonia
inner.Add(property.Id, property); inner.Add(property.Id, property);
} }
_properties.Add(property); if (!_properties.ContainsKey(property.Id))
{
_properties.Add(property.Id, property);
}
_registeredCache.Clear(); _registeredCache.Clear();
} }

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

@ -177,7 +177,7 @@ namespace Avalonia.Data.Core
protected override void Subscribed(IObserver<object> observer, bool first) protected override void Subscribed(IObserver<object> observer, bool first)
{ {
if (!first && _value != null && _value.TryGetTarget(out var val) == true) if (!first && _value != null && _value.TryGetTarget(out var val))
{ {
observer.OnNext(val); observer.OnNext(val);
} }

22
src/Avalonia.Base/ISupportInitialize.cs

@ -1,22 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia
{
/// <summary>
/// Specifies that this object supports a simple, transacted notification for batch
/// initialization.
/// </summary>
public interface ISupportInitialize
{
/// <summary>
/// Signals the object that initialization is starting.
/// </summary>
void BeginInit();
/// <summary>
/// Signals the object that initialization is complete.
/// </summary>
void EndInit();
}
}

2
src/Avalonia.Controls/AutoCompleteBox.cs

@ -1893,7 +1893,7 @@ namespace Avalonia.Controls
{ {
bool callTextChanged = false; bool callTextChanged = false;
// Update the Text dependency property // Update the Text dependency property
if ((userInitiated == null || userInitiated == true) && Text != value) if ((userInitiated ?? true) && Text != value)
{ {
_ignoreTextPropertyChange++; _ignoreTextPropertyChange++;
Text = value; Text = value;

2
src/Avalonia.Controls/Button.cs

@ -217,7 +217,7 @@ namespace Avalonia.Controls
var e = new RoutedEventArgs(ClickEvent); var e = new RoutedEventArgs(ClickEvent);
RaiseEvent(e); RaiseEvent(e);
if (Command != null) if (!e.Handled && Command?.CanExecute(CommandParameter) == true)
{ {
Command.Execute(CommandParameter); Command.Execute(CommandParameter);
e.Handled = true; e.Handled = true;

1
src/Avalonia.Controls/Control.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.ComponentModel;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;

76
src/Avalonia.Controls/DropDown.cs

@ -56,6 +56,7 @@ namespace Avalonia.Controls
private bool _isDropDownOpen; private bool _isDropDownOpen;
private Popup _popup; private Popup _popup;
private object _selectionBoxItem; private object _selectionBoxItem;
private IDisposable _subscriptionsOnOpen;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="DropDown"/> class. /// Initializes static members of the <see cref="DropDown"/> class.
@ -149,16 +150,12 @@ namespace Avalonia.Controls
{ {
if (e.Key == Key.Down) if (e.Key == Key.Down)
{ {
if (++SelectedIndex >= ItemCount) SelectNext();
SelectedIndex = 0;
e.Handled = true; e.Handled = true;
} }
else if (e.Key == Key.Up) else if (e.Key == Key.Up)
{ {
if (--SelectedIndex < 0) SelectPrev();
SelectedIndex = ItemCount - 1;
e.Handled = true; e.Handled = true;
} }
} }
@ -174,6 +171,32 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc/>
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
base.OnPointerWheelChanged(e);
if (!e.Handled)
{
if (!IsDropDownOpen)
{
if (IsFocused)
{
if (e.Delta.Y < 0)
SelectNext();
else
SelectPrev();
e.Handled = true;
}
}
else
{
e.Handled = true;
}
}
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e) protected override void OnPointerPressed(PointerPressedEventArgs e)
{ {
@ -223,6 +246,9 @@ namespace Avalonia.Controls
private void PopupClosed(object sender, EventArgs e) private void PopupClosed(object sender, EventArgs e)
{ {
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
if (CanFocus(this)) if (CanFocus(this))
{ {
Focus(); Focus();
@ -232,6 +258,22 @@ namespace Avalonia.Controls
private void PopupOpened(object sender, EventArgs e) private void PopupOpened(object sender, EventArgs e)
{ {
TryFocusSelectedItem(); TryFocusSelectedItem();
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
var toplevel = this.GetVisualRoot() as TopLevel;
if (toplevel != null)
{
_subscriptionsOnOpen = toplevel.AddHandler(PointerWheelChangedEvent, (s, ev) =>
{
//eat wheel scroll event outside dropdown popup while it's open
if (IsDropDownOpen && (ev.Source as IVisual).GetVisualRoot() == toplevel)
{
ev.Handled = true;
}
}, Interactivity.RoutingStrategies.Tunnel);
}
} }
private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e) private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e)
@ -247,7 +289,7 @@ namespace Avalonia.Controls
{ {
var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
if(container == null && SelectedItems.Count > 0) if (container == null && SelectedItems.Count > 0)
{ {
ScrollIntoView(SelectedItems[0]); ScrollIntoView(SelectedItems[0]);
container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
@ -307,5 +349,25 @@ namespace Avalonia.Controls
} }
} }
} }
private void SelectNext()
{
int next = SelectedIndex + 1;
if (next >= ItemCount)
next = 0;
SelectedIndex = next;
}
private void SelectPrev()
{
int prev = SelectedIndex - 1;
if (prev < 0)
prev = ItemCount - 1;
SelectedIndex = prev;
}
} }
} }

1
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Platform; using Avalonia.Platform;

1
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using Avalonia.Styling; using Avalonia.Styling;
namespace Avalonia.Controls.Embedding.Offscreen namespace Avalonia.Controls.Embedding.Offscreen

17
src/Avalonia.Controls/Image.cs

@ -99,5 +99,22 @@ namespace Avalonia.Controls
return new Size(); return new Size();
} }
} }
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
var source = Source;
if (source != null)
{
var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
var result = Stretch.CalculateSize(finalSize, sourceSize);
return result;
}
else
{
return new Size();
}
}
} }
} }

2
src/Avalonia.Controls/ItemsControl.cs

@ -64,6 +64,7 @@ namespace Avalonia.Controls
static ItemsControl() static ItemsControl()
{ {
ItemsProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemsChanged); ItemsProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemsChanged);
ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemTemplateChanged);
} }
/// <summary> /// <summary>
@ -73,7 +74,6 @@ namespace Avalonia.Controls
{ {
PseudoClasses.Add(":empty"); PseudoClasses.Add(":empty");
SubscribeToItems(_items); SubscribeToItems(_items);
ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemTemplateChanged);
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls/MenuItem.cs

@ -287,7 +287,7 @@ namespace Avalonia.Controls
/// <param name="e">The click event args.</param> /// <param name="e">The click event args.</param>
protected virtual void OnClick(RoutedEventArgs e) protected virtual void OnClick(RoutedEventArgs e)
{ {
if (Command != null) if (!e.Handled && Command?.CanExecute(CommandParameter) == true)
{ {
Command.Execute(CommandParameter); Command.Execute(CommandParameter);
e.Handled = true; e.Handled = true;
@ -421,7 +421,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Called when the <see cref="Header"/> property changes. /// Called when the <see cref="HeaderedSelectingItemsControl.Header"/> property changes.
/// </summary> /// </summary>
/// <param name="e">The property change event.</param> /// <param name="e">The property change event.</param>
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e) private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)

2
src/Avalonia.Controls/PixelPointEventArgs.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PixelPointEventArgs"/> class. /// Initializes a new instance of the <see cref="PixelPointEventArgs"/> class.
/// </summary> /// </summary>
/// <param name="point">The <see cref=PixelPoint"/> data.</param> /// <param name="point">The <see cref="PixelPoint"/> data.</param>
public PixelPointEventArgs(PixelPoint point) public PixelPointEventArgs(PixelPoint point)
{ {
Point = point; Point = point;

2
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -285,7 +285,7 @@ namespace Avalonia.Controls.Presenters
{ {
scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable); scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
if (scrollable.IsLogicalScrollEnabled == true) if (scrollable.IsLogicalScrollEnabled)
{ {
_logicalScrollSubscription = new CompositeDisposable( _logicalScrollSubscription = new CompositeDisposable(
this.GetObservable(CanHorizontallyScrollProperty) this.GetObservable(CanHorizontallyScrollProperty)

1
src/Avalonia.Controls/WindowBase.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;

2
src/Avalonia.Native/ScreenImpl.cs

@ -41,7 +41,7 @@ namespace Avalonia.Native
public void Dispose () public void Dispose ()
{ {
_native.Dispose(); _native?.Dispose();
_native = null; _native = null;
} }
} }

224
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -0,0 +1,224 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
using ReactiveUI;
using Splat;
namespace Avalonia
{
/// <summary>
/// This control hosts the View associated with ReactiveUI RoutingState,
/// and will display the View and wire up the ViewModel whenever a new
/// ViewModel is navigated to. Nested routing is also supported.
/// </summary>
/// <remarks>
/// <para>
/// ReactiveUI routing consists of an IScreen that contains current
/// RoutingState, several IRoutableViewModels, and a platform-specific
/// XAML control called RoutedViewHost.
/// </para>
/// <para>
/// RoutingState manages the ViewModel navigation stack and allows
/// ViewModels to navigate to other ViewModels. IScreen is the root of
/// a navigation stack; despite the name, its views don't have to occupy
/// the whole screen. RoutedViewHost monitors an instance of RoutingState,
/// responding to any changes in the navigation stack by creating and
/// embedding the appropriate view.
/// </para>
/// <para>
/// Place this control to a view containing your ViewModel that implements
/// IScreen, and bind IScreen.Router property to RoutedViewHost.Router property.
/// <code>
/// <![CDATA[
/// <rxui:RoutedViewHost
/// HorizontalAlignment="Stretch"
/// VerticalAlignment="Stretch"
/// Router="{Binding Router}">
/// <rxui:RoutedViewHost.DefaultContent>
/// <TextBlock Text="Default Content"/>
/// </rxui:RoutedViewHost.DefaultContent>
/// </rxui:RoutedViewHost>
/// ]]>
/// </code>
/// </para>
/// <para>
/// See <see href="https://reactiveui.net/docs/handbook/routing/">
/// ReactiveUI routing documentation website</see> for more info.
/// </para>
/// </remarks>
public class RoutedViewHost : UserControl, IActivatable, IEnableLogger
{
/// <summary>
/// The router dependency property.
/// </summary>
public static readonly AvaloniaProperty<RoutingState> RouterProperty =
AvaloniaProperty.Register<RoutedViewHost, RoutingState>(nameof(Router));
/// <summary>
/// The default content property.
/// </summary>
public static readonly AvaloniaProperty<object> DefaultContentProperty =
AvaloniaProperty.Register<RoutedViewHost, object>(nameof(DefaultContent));
/// <summary>
/// Fade in animation property.
/// </summary>
public static readonly AvaloniaProperty<IAnimation> FadeInAnimationProperty =
AvaloniaProperty.Register<RoutedViewHost, IAnimation>(nameof(DefaultContent),
CreateOpacityAnimation(0d, 1d, TimeSpan.FromSeconds(0.25)));
/// <summary>
/// Fade out animation property.
/// </summary>
public static readonly AvaloniaProperty<IAnimation> FadeOutAnimationProperty =
AvaloniaProperty.Register<RoutedViewHost, IAnimation>(nameof(DefaultContent),
CreateOpacityAnimation(1d, 0d, TimeSpan.FromSeconds(0.25)));
/// <summary>
/// Initializes a new instance of the <see cref="RoutedViewHost"/> class.
/// </summary>
public RoutedViewHost()
{
this.WhenActivated(disposables =>
{
this.WhenAnyObservable(x => x.Router.CurrentViewModel)
.DistinctUntilChanged()
.Subscribe(NavigateToViewModel)
.DisposeWith(disposables);
});
}
/// <summary>
/// Gets or sets the <see cref="RoutingState"/> of the view model stack.
/// </summary>
public RoutingState Router
{
get => GetValue(RouterProperty);
set => SetValue(RouterProperty, value);
}
/// <summary>
/// Gets or sets the content displayed whenever there is no page currently routed.
/// </summary>
public object DefaultContent
{
get => GetValue(DefaultContentProperty);
set => SetValue(DefaultContentProperty, value);
}
/// <summary>
/// Gets or sets the animation played when page appears.
/// </summary>
public IAnimation FadeInAnimation
{
get => GetValue(FadeInAnimationProperty);
set => SetValue(FadeInAnimationProperty, value);
}
/// <summary>
/// Gets or sets the animation played when page disappears.
/// </summary>
public IAnimation FadeOutAnimation
{
get => GetValue(FadeOutAnimationProperty);
set => SetValue(FadeOutAnimationProperty, value);
}
/// <summary>
/// Duplicates the Content property with a private setter.
/// </summary>
public new object Content
{
get => base.Content;
private set => base.Content = value;
}
/// <summary>
/// Gets or sets the ReactiveUI view locator used by this router.
/// </summary>
public IViewLocator ViewLocator { get; set; }
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
/// <exception cref="Exception">
/// Thrown when ViewLocator is unable to find the appropriate view.
/// </exception>
private void NavigateToViewModel(IRoutableViewModel viewModel)
{
if (viewModel == null)
{
this.Log().Info("ViewModel is null, falling back to default content.");
UpdateContent(DefaultContent);
return;
}
var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current;
var view = viewLocator.ResolveView(viewModel);
if (view == null) throw new Exception($"Couldn't find view for '{viewModel}'. Is it registered?");
this.Log().Info($"Ready to show {view} with autowired {viewModel}.");
view.ViewModel = viewModel;
UpdateContent(view);
}
/// <summary>
/// Updates the content with transitions.
/// </summary>
/// <param name="newContent">New content to set.</param>
private async void UpdateContent(object newContent)
{
if (FadeOutAnimation != null)
await FadeOutAnimation.RunAsync(this, Clock);
Content = newContent;
if (FadeInAnimation != null)
await FadeInAnimation.RunAsync(this, Clock);
}
/// <summary>
/// Creates opacity animation for this routed view host.
/// </summary>
/// <param name="from">Opacity to start from.</param>
/// <param name="to">Opacity to finish with.</param>
/// <param name="duration">Duration of the animation.</param>
/// <returns>Animation object instance.</returns>
private static IAnimation CreateOpacityAnimation(double from, double to, TimeSpan duration)
{
return new Avalonia.Animation.Animation
{
Duration = duration,
Children =
{
new KeyFrame
{
Setters =
{
new Setter
{
Property = OpacityProperty,
Value = from
}
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter
{
Property = OpacityProperty,
Value = to
}
},
Cue = new Cue(1d)
}
}
};
}
}
}

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

@ -236,7 +236,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
foreach (var operation in DrawOperations) foreach (var operation in DrawOperations)
{ {
if (operation.Item.HitTest(p) == true) if (operation.Item.HitTest(p))
{ {
return true; return true;
} }

3
src/Avalonia.X11/X11Screens.cs

@ -218,6 +218,7 @@ namespace Avalonia.X11
class X11Screen class X11Screen
{ {
private const int FullHDWidth = 1920;
public bool Primary { get; } public bool Primary { get; }
public string Name { get; set; } public string Name { get; set; }
public PixelRect Bounds { get; set; } public PixelRect Bounds { get; set; }
@ -247,6 +248,6 @@ namespace Avalonia.X11
} }
public static double GuessPixelDensity(double pixelWidth, double mmWidth) public static double GuessPixelDensity(double pixelWidth, double mmWidth)
=> Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96)); => pixelWidth <= FullHDWidth ? 1 : Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96));
} }
} }

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

@ -8,6 +8,7 @@ using Avalonia.Platform;
using Portable.Xaml; using Portable.Xaml;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.Serialization; using System.Runtime.Serialization;
@ -250,7 +251,7 @@ namespace Avalonia.Markup.Xaml
.ToDictionary(entry =>entry.Element(arrayNs + "Key").Value, .ToDictionary(entry =>entry.Element(arrayNs + "Key").Value,
entry => entry.Element(arrayNs + "Value").Value); entry => entry.Element(arrayNs + "Value").Value);
if (xamlInfo.TryGetValue(typeName, out var rv) == true) if (xamlInfo.TryGetValue(typeName, out var rv))
{ {
yield return new Uri($"avares://{asm}{rv}"); yield return new Uri($"avares://{asm}{rv}");
yield break; yield break;

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

@ -77,40 +77,15 @@ namespace Avalonia.Markup.Xaml.PortableXaml
_delayedValuesHelper.ApplyAll(); _delayedValuesHelper.ApplyAll();
} }
protected internal override void OnAfterBeginInit(object value)
{
//not called for avalonia objects
//as it's called inly for
//Portable.Xaml.ComponentModel.ISupportInitialize
base.OnAfterBeginInit(value);
}
protected internal override void OnAfterEndInit(object value)
{
//not called for avalonia objects
//as it's called inly for
//Portable.Xaml.ComponentModel.ISupportInitialize
base.OnAfterEndInit(value);
}
protected internal override void OnAfterProperties(object value) protected internal override void OnAfterProperties(object value)
{ {
_delayedValuesHelper.EndInit(value); _delayedValuesHelper.EndInit(value);
base.OnAfterProperties(value); base.OnAfterProperties(value);
//AfterEndInit is not called as it supports only
//Portable.Xaml.ComponentModel.ISupportInitialize
//and we have Avalonia.ISupportInitialize so we need some hacks
HandleEndEdit(value);
} }
protected internal override void OnBeforeProperties(object value) protected internal override void OnBeforeProperties(object value)
{ {
//OnAfterBeginInit is not called as it supports only
//Portable.Xaml.ComponentModel.ISupportInitialize
//and we have Avalonia.ISupportInitialize so we need some hacks
HandleBeginInit(value);
if (value != null) if (value != null)
_delayedValuesHelper.BeginInit(value); _delayedValuesHelper.BeginInit(value);
@ -127,16 +102,6 @@ namespace Avalonia.Markup.Xaml.PortableXaml
return base.OnSetValue(target, member, value); return base.OnSetValue(target, member, value);
} }
private void HandleBeginInit(object value)
{
(value as Avalonia.ISupportInitialize)?.BeginInit();
}
private void HandleEndEdit(object value)
{
(value as Avalonia.ISupportInitialize)?.EndInit();
}
public override void WriteStartMember(XamlMember property) public override void WriteStartMember(XamlMember property)
{ {
foreach(var d in DesignDirectives) foreach(var d in DesignDirectives)

5
tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs

@ -27,6 +27,7 @@ namespace Avalonia.Base.UnitTests
var property = new AttachedProperty<int>("test", typeof(object), metadata, true); var property = new AttachedProperty<int>("test", typeof(object), metadata, true);
registry.Register(typeof(object), property); registry.Register(typeof(object), property);
registry.RegisterAttached(typeof(AvaloniaPropertyRegistryTests), property); registry.RegisterAttached(typeof(AvaloniaPropertyRegistryTests), property);
property.AddOwner<Class4>();
Assert.Equal(1, registry.Properties.Count); Assert.Equal(1, registry.Properties.Count);
} }
@ -150,5 +151,9 @@ namespace Avalonia.Base.UnitTests
private class AttachedOwner2 : AttachedOwner private class AttachedOwner2 : AttachedOwner
{ {
} }
private class Class4 : AvaloniaObject
{
}
} }
} }

56
tests/Avalonia.Controls.UnitTests/ImageTests.cs

@ -61,5 +61,61 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(50, 50), target.DesiredSize); Assert.Equal(new Size(50, 50), target.DesiredSize);
} }
[Fact]
public void Arrange_Should_Return_Correct_Size_For_No_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var target = new Image();
target.Stretch = Stretch.None;
target.Source = bitmap;
target.Measure(new Size(50, 50));
target.Arrange(new Rect(0, 0, 100, 400));
Assert.Equal(new Size(50, 100), target.Bounds.Size);
}
[Fact]
public void Arrange_Should_Return_Correct_Size_For_Fill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var target = new Image();
target.Stretch = Stretch.Fill;
target.Source = bitmap;
target.Measure(new Size(50, 50));
target.Arrange(new Rect(0, 0, 25, 100));
Assert.Equal(new Size(25, 100), target.Bounds.Size);
}
[Fact]
public void Arrange_Should_Return_Correct_Size_For_Uniform_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var target = new Image();
target.Stretch = Stretch.Uniform;
target.Source = bitmap;
target.Measure(new Size(50, 50));
target.Arrange(new Rect(0, 0, 25, 100));
Assert.Equal(new Size(25, 50), target.Bounds.Size);
}
[Fact]
public void Arrange_Should_Return_Correct_Size_For_UniformToFill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var target = new Image();
target.Stretch = Stretch.UniformToFill;
target.Source = bitmap;
target.Measure(new Size(50, 50));
target.Arrange(new Rect(0, 0, 25, 100));
Assert.Equal(new Size(25, 100), target.Bounds.Size);
}
} }
} }

1
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;

3
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs

@ -4,6 +4,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{ {
@ -39,4 +40,4 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Order.Add($"EndInit {InitState}"); Order.Add($"EndInit {InitState}");
} }
} }
} }

104
tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs

@ -0,0 +1,104 @@
using System;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Rendering;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia;
using ReactiveUI;
using DynamicData;
using Xunit;
using Splat;
using Avalonia.Markup.Xaml;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Reactive;
namespace Avalonia
{
public class RoutedViewHostTest
{
public class FirstRoutableViewModel : ReactiveObject, IRoutableViewModel
{
public string UrlPathSegment => "first";
public IScreen HostScreen { get; set; }
}
public class FirstRoutableView : ReactiveUserControl<FirstRoutableViewModel> { }
public class SecondRoutableViewModel : ReactiveObject, IRoutableViewModel
{
public string UrlPathSegment => "second";
public IScreen HostScreen { get; set; }
}
public class SecondRoutableView : ReactiveUserControl<SecondRoutableViewModel> { }
public class ScreenViewModel : ReactiveObject, IScreen
{
public RoutingState Router { get; } = new RoutingState();
}
public RoutedViewHostTest()
{
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register(() => new FirstRoutableView(), typeof(IViewFor<FirstRoutableViewModel>));
Locator.CurrentMutable.Register(() => new SecondRoutableView(), typeof(IViewFor<SecondRoutableViewModel>));
}
[Fact]
public void RoutedViewHostShouldStayInSyncWithRoutingState()
{
var screen = new ScreenViewModel();
var defaultContent = new TextBlock();
var host = new RoutedViewHost
{
Router = screen.Router,
DefaultContent = defaultContent,
FadeOutAnimation = null,
FadeInAnimation = null
};
var root = new TestRoot
{
Child = host
};
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
screen.Router.Navigate
.Execute(new FirstRoutableViewModel())
.Subscribe();
Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstRoutableView), host.Content.GetType());
screen.Router.Navigate
.Execute(new SecondRoutableViewModel())
.Subscribe();
Assert.NotNull(host.Content);
Assert.Equal(typeof(SecondRoutableView), host.Content.GetType());
screen.Router.NavigateBack
.Execute(Unit.Default)
.Subscribe();
Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstRoutableView), host.Content.GetType());
screen.Router.NavigateBack
.Execute(Unit.Default)
.Subscribe();
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
}
}
}

1
tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

@ -10,6 +10,7 @@ using Avalonia.UnitTests;
using Xunit; using Xunit;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Controls; using Avalonia.Controls;
using System.ComponentModel;
namespace Avalonia.Styling.UnitTests namespace Avalonia.Styling.UnitTests
{ {

Loading…
Cancel
Save