Browse Source

Merge branch 'master' into negation-shorthand-fix

pull/1749/head
Jeremy Koritzinsky 8 years ago
committed by GitHub
parent
commit
b2d041fd09
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 51
      src/Avalonia.Base/AvaloniaObject.cs
  2. 3
      src/Avalonia.Base/IPriorityValueOwner.cs
  3. 11
      src/Avalonia.Base/PriorityValue.cs
  4. 53
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  5. 58
      src/Avalonia.Base/Utilities/SingleOrQueue.cs
  6. 22
      src/Avalonia.Base/ValueStore.cs
  7. 20
      src/Avalonia.Controls/Grid.cs
  8. 7
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  9. 19
      src/Avalonia.Controls/StackPanel.cs
  10. 2
      src/Avalonia.Controls/TextBox.cs
  11. 11
      src/Avalonia.Styling/Styling/Setter.cs
  12. 4
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  13. 2
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  14. 18
      src/Windows/Avalonia.Win32/SystemDialogImpl.cs
  15. 52
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
  16. 50
      tests/Avalonia.Base.UnitTests/Utilities/SingleOrQueueTests.cs
  17. 28
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  18. 39
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  19. 18
      tests/Avalonia.RenderTests/Media/ImageBrushTests.cs
  20. BIN
      tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NullSource.expected.png
  21. BIN
      tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_NullSource.expected.png

51
src/Avalonia.Base/AvaloniaObject.cs

@ -22,7 +22,7 @@ namespace Avalonia
/// <remarks> /// <remarks>
/// This class is analogous to DependencyObject in WPF. /// This class is analogous to DependencyObject in WPF.
/// </remarks> /// </remarks>
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IPriorityValueOwner public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
{ {
/// <summary> /// <summary>
/// The parent object that inherited values are inherited from. /// The parent object that inherited values are inherited from.
@ -45,21 +45,8 @@ namespace Avalonia
/// </summary> /// </summary>
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged; private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
private ValueStore _values; private ValueStore _values;
private ValueStore Values => _values ?? (_values = new ValueStore(this));
/// <summary>
/// Delayed setter helper for direct properties. Used to fix #855.
/// </summary>
private DeferredSetter<AvaloniaProperty, object> DirectPropertyDeferredSetter
{
get
{
return _directDeferredSetter ??
(_directDeferredSetter = new DeferredSetter<AvaloniaProperty, object>());
}
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AvaloniaObject"/> class. /// Initializes a new instance of the <see cref="AvaloniaObject"/> class.
@ -225,7 +212,7 @@ namespace Avalonia
} }
else if (_values != null) else if (_values != null)
{ {
var result = _values.GetValue(property); var result = Values.GetValue(property);
if (result == AvaloniaProperty.UnsetValue) if (result == AvaloniaProperty.UnsetValue)
{ {
@ -376,12 +363,7 @@ namespace Avalonia
description, description,
priority); priority);
if (_values == null) return Values.AddBinding(property, source, priority);
{
_values = new ValueStore(this);
}
return _values.AddBinding(property, source, priority);
} }
} }
@ -414,9 +396,8 @@ namespace Avalonia
VerifyAccess(); VerifyAccess();
_values?.Revalidate(property); _values?.Revalidate(property);
} }
/// <inheritdoc/> internal void PriorityValueChanged(AvaloniaProperty property, int priority, object oldValue, object newValue)
void IPriorityValueOwner.Changed(AvaloniaProperty property, int priority, object oldValue, object newValue)
{ {
oldValue = (oldValue == AvaloniaProperty.UnsetValue) ? oldValue = (oldValue == AvaloniaProperty.UnsetValue) ?
GetDefaultValue(property) : GetDefaultValue(property) :
@ -439,9 +420,8 @@ namespace Avalonia
(BindingPriority)priority); (BindingPriority)priority);
} }
} }
/// <inheritdoc/> internal void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
void IPriorityValueOwner.BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
{ {
UpdateDataValidation(property, notification); UpdateDataValidation(property, notification);
} }
@ -456,7 +436,7 @@ namespace Avalonia
/// Gets all priority values set on the object. /// Gets all priority values set on the object.
/// </summary> /// </summary>
/// <returns>A collection of property/value tuples.</returns> /// <returns>A collection of property/value tuples.</returns>
internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => _values?.GetSetValues(); internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => Values?.GetSetValues();
/// <summary> /// <summary>
/// Forces revalidation of properties when a property value changes. /// Forces revalidation of properties when a property value changes.
@ -566,12 +546,12 @@ namespace Avalonia
T value) T value)
{ {
Contract.Requires<ArgumentNullException>(setterCallback != null); Contract.Requires<ArgumentNullException>(setterCallback != null);
return DirectPropertyDeferredSetter.SetAndNotify( return Values.Setter.SetAndNotify(
property, property,
ref field, ref field,
(object val, ref T backing, Action<Action> notify) => (object update, ref T backing, Action<Action> notify) =>
{ {
setterCallback((T)val, ref backing, notify); setterCallback((T)update, ref backing, notify);
return true; return true;
}, },
value); value);
@ -737,13 +717,8 @@ namespace Avalonia
originalValue?.GetType().FullName ?? "(null)")); originalValue?.GetType().FullName ?? "(null)"));
} }
if (_values == null)
{
_values = new ValueStore(this);
}
LogPropertySet(property, value, priority); LogPropertySet(property, value, priority);
_values.AddValue(property, value, (int)priority); Values.AddValue(property, value, (int)priority);
} }
/// <summary> /// <summary>

3
src/Avalonia.Base/IPriorityValueOwner.cs

@ -2,6 +2,7 @@
// 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 Avalonia.Data; using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
{ {
@ -31,5 +32,7 @@ namespace Avalonia
/// Ensures that the current thread is the UI thread. /// Ensures that the current thread is the UI thread.
/// </summary> /// </summary>
void VerifyAccess(); void VerifyAccess();
DeferredSetter<object> Setter { get; }
} }
} }

11
src/Avalonia.Base/PriorityValue.cs

@ -21,7 +21,7 @@ namespace Avalonia
/// priority binding that doesn't return <see cref="AvaloniaProperty.UnsetValue"/>. Where there /// priority binding that doesn't return <see cref="AvaloniaProperty.UnsetValue"/>. Where there
/// are multiple bindings registered with the same priority, the most recently added binding /// are multiple bindings registered with the same priority, the most recently added binding
/// has a higher priority. Each time the value changes, the /// has a higher priority. Each time the value changes, the
/// <see cref="IPriorityValueOwner.Changed(PriorityValue, object, object)"/> method on the /// <see cref="IPriorityValueOwner.Changed"/> method on the
/// owner object is fired with the old and new values. /// owner object is fired with the old and new values.
/// </remarks> /// </remarks>
internal class PriorityValue internal class PriorityValue
@ -30,7 +30,6 @@ namespace Avalonia
private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>(); private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
private readonly Func<object, object> _validate; private readonly Func<object, object> _validate;
private static readonly DeferredSetter<PriorityValue, (object value, int priority)> delayedSetter = new DeferredSetter<PriorityValue, (object, int)>();
private (object value, int priority) _value; private (object value, int priority) _value;
/// <summary> /// <summary>
@ -243,12 +242,18 @@ namespace Avalonia
/// <param name="priority">The priority level that the value came from.</param> /// <param name="priority">The priority level that the value came from.</param>
private void UpdateValue(object value, int priority) private void UpdateValue(object value, int priority)
{ {
delayedSetter.SetAndNotify(this, Owner.Setter.SetAndNotify(Property,
ref _value, ref _value,
UpdateCore, UpdateCore,
(value, priority)); (value, priority));
} }
private bool UpdateCore(
object update,
ref (object value, int priority) backing,
Action<Action> notify)
=> UpdateCore(((object, int))update, ref backing, notify);
private bool UpdateCore( private bool UpdateCore(
(object value, int priority) update, (object value, int priority) update,
ref (object value, int priority) backing, ref (object value, int priority) backing,

53
src/Avalonia.Base/Utilities/DeferredSetter.cs

@ -8,11 +8,10 @@ namespace Avalonia.Utilities
{ {
/// <summary> /// <summary>
/// A utility class to enable deferring assignment until after property-changed notifications are sent. /// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// Used to fix #855.
/// </summary> /// </summary>
/// <typeparam name="TProperty">The type of the object that represents the property.</typeparam>
/// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam> /// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
class DeferredSetter<TProperty, TSetRecord> class DeferredSetter<TSetRecord>
where TProperty: class
{ {
private struct NotifyDisposable : IDisposable private struct NotifyDisposable : IDisposable
{ {
@ -37,29 +36,44 @@ namespace Avalonia.Utilities
{ {
public bool Notifying { get; set; } public bool Notifying { get; set; }
private Queue<TSetRecord> pendingValues; private SingleOrQueue<TSetRecord> pendingValues;
public Queue<TSetRecord> PendingValues public SingleOrQueue<TSetRecord> PendingValues
{ {
get get
{ {
return pendingValues ?? (pendingValues = new Queue<TSetRecord>()); return pendingValues ?? (pendingValues = new SingleOrQueue<TSetRecord>());
} }
} }
} }
private readonly ConditionalWeakTable<TProperty, SettingStatus> setRecords = new ConditionalWeakTable<TProperty, SettingStatus>(); private Dictionary<AvaloniaProperty, SettingStatus> _setRecords;
private Dictionary<AvaloniaProperty, SettingStatus> SetRecords
=> _setRecords ?? (_setRecords = new Dictionary<AvaloniaProperty, SettingStatus>());
private SettingStatus GetOrCreateStatus(AvaloniaProperty property)
{
if (!SetRecords.TryGetValue(property, out var status))
{
status = new SettingStatus();
SetRecords.Add(property, status);
}
return status;
}
/// <summary> /// <summary>
/// Mark the property as currently notifying. /// Mark the property as currently notifying.
/// </summary> /// </summary>
/// <param name="property">The property to mark as notifying.</param> /// <param name="property">The property to mark as notifying.</param>
/// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns> /// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns>
private NotifyDisposable MarkNotifying(TProperty property) private NotifyDisposable MarkNotifying(AvaloniaProperty property)
{ {
Contract.Requires<InvalidOperationException>(!IsNotifying(property)); Contract.Requires<InvalidOperationException>(!IsNotifying(property));
return new NotifyDisposable(setRecords.GetOrCreateValue(property)); SettingStatus status = GetOrCreateStatus(property);
return new NotifyDisposable(status);
} }
/// <summary> /// <summary>
@ -67,19 +81,19 @@ namespace Avalonia.Utilities
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <returns>If the property is currently notifying listeners.</returns> /// <returns>If the property is currently notifying listeners.</returns>
private bool IsNotifying(TProperty property) private bool IsNotifying(AvaloniaProperty property)
=> setRecords.TryGetValue(property, out var value) && value.Notifying; => SetRecords.TryGetValue(property, out var value) && value.Notifying;
/// <summary> /// <summary>
/// Add a pending assignment for the property. /// Add a pending assignment for the property.
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The value to assign.</param> /// <param name="value">The value to assign.</param>
private void AddPendingSet(TProperty property, TSetRecord value) private void AddPendingSet(AvaloniaProperty property, TSetRecord value)
{ {
Contract.Requires<InvalidOperationException>(IsNotifying(property)); Contract.Requires<InvalidOperationException>(IsNotifying(property));
setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value); GetOrCreateStatus(property).PendingValues.Enqueue(value);
} }
/// <summary> /// <summary>
@ -87,9 +101,9 @@ namespace Avalonia.Utilities
/// </summary> /// </summary>
/// <param name="property">The property to check.</param> /// <param name="property">The property to check.</param>
/// <returns>If the property has any pending assignments.</returns> /// <returns>If the property has any pending assignments.</returns>
private bool HasPendingSet(TProperty property) private bool HasPendingSet(AvaloniaProperty property)
{ {
return setRecords.TryGetValue(property, out var status) && status.PendingValues.Count != 0; return SetRecords.TryGetValue(property, out var status) && !status.PendingValues.Empty;
} }
/// <summary> /// <summary>
@ -97,9 +111,9 @@ namespace Avalonia.Utilities
/// </summary> /// </summary>
/// <param name="property">The property to check.</param> /// <param name="property">The property to check.</param>
/// <returns>The first pending assignment for the property.</returns> /// <returns>The first pending assignment for the property.</returns>
private TSetRecord GetFirstPendingSet(TProperty property) private TSetRecord GetFirstPendingSet(AvaloniaProperty property)
{ {
return setRecords.GetOrCreateValue(property).PendingValues.Dequeue(); return GetOrCreateStatus(property).PendingValues.Dequeue();
} }
public delegate bool SetterDelegate<TValue>(TSetRecord record, ref TValue backing, Action<Action> notifyCallback); public delegate bool SetterDelegate<TValue>(TSetRecord record, ref TValue backing, Action<Action> notifyCallback);
@ -115,7 +129,7 @@ namespace Avalonia.Utilities
/// </param> /// </param>
/// <param name="value">The value to try to set.</param> /// <param name="value">The value to try to set.</param>
public bool SetAndNotify<TValue>( public bool SetAndNotify<TValue>(
TProperty property, AvaloniaProperty property,
ref TValue backing, ref TValue backing,
SetterDelegate<TValue> setterCallback, SetterDelegate<TValue> setterCallback,
TSetRecord value) TSetRecord value)
@ -144,6 +158,7 @@ namespace Avalonia.Utilities
} }
}); });
} }
return updated; return updated;
} }
else if(!object.Equals(value, backing)) else if(!object.Equals(value, backing))

58
src/Avalonia.Base/Utilities/SingleOrQueue.cs

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Utilities
{
/// <summary>
/// FIFO Queue optimized for holding zero or one items.
/// </summary>
/// <typeparam name="T">The type of items held in the queue.</typeparam>
public class SingleOrQueue<T>
{
private T _head;
private Queue<T> _tail;
private Queue<T> Tail => _tail ?? (_tail = new Queue<T>());
private bool HasTail => _tail != null;
public bool Empty { get; private set; } = true;
public void Enqueue(T value)
{
if (Empty)
{
_head = value;
}
else
{
Tail.Enqueue(value);
}
Empty = false;
}
public T Dequeue()
{
if (Empty)
{
throw new InvalidOperationException("Cannot dequeue from an empty queue!");
}
var result = _head;
if (HasTail && Tail.Count != 0)
{
_head = Tail.Dequeue();
}
else
{
_head = default;
Empty = true;
}
return result;
}
}
}

22
src/Avalonia.Base/ValueStore.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
{ {
@ -91,12 +92,12 @@ namespace Avalonia
public void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) public void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
{ {
((IPriorityValueOwner)_owner).BindingNotificationReceived(property, notification); _owner.BindingNotificationReceived(property, notification);
} }
public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue)
{ {
((IPriorityValueOwner)_owner).Changed(property, priority, oldValue, newValue); _owner.PriorityValueChanged(property, priority, oldValue, newValue);
} }
public IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => throw new NotImplementedException(); public IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => throw new NotImplementedException();
@ -115,7 +116,7 @@ namespace Avalonia
public bool IsAnimating(AvaloniaProperty property) public bool IsAnimating(AvaloniaProperty property)
{ {
return _values.TryGetValue(property, out var value) ? (value as PriorityValue)?.IsAnimating ?? false : false; return _values.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
} }
public bool IsSet(AvaloniaProperty property) public bool IsSet(AvaloniaProperty property)
@ -148,13 +149,11 @@ namespace Avalonia
validate2 = v => validate(_owner, v); validate2 = v => validate(_owner, v);
} }
PriorityValue result = new PriorityValue( return new PriorityValue(
this, this,
property, property,
property.PropertyType, property.PropertyType,
validate2); validate2);
return result;
} }
private object Validate(AvaloniaProperty property, object value) private object Validate(AvaloniaProperty property, object value)
@ -168,5 +167,16 @@ namespace Avalonia
return value; return value;
} }
private DeferredSetter<object> _defferedSetter;
public DeferredSetter<object> Setter
{
get
{
return _defferedSetter ??
(_defferedSetter = new DeferredSetter<object>());
}
}
} }
} }

20
src/Avalonia.Controls/Grid.cs

@ -194,6 +194,16 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private GridLayout.MeasureResult _rowMeasureCache; private GridLayout.MeasureResult _rowMeasureCache;
/// <summary>
/// Gets the row layout as of the last measure.
/// </summary>
private GridLayout _rowLayoutCache;
/// <summary>
/// Gets the column layout as of the last measure.
/// </summary>
private GridLayout _columnLayoutCache;
/// <summary> /// <summary>
/// Measures the grid. /// Measures the grid.
/// </summary> /// </summary>
@ -253,6 +263,9 @@ namespace Avalonia.Controls
// Cache the measure result and return the desired size. // Cache the measure result and return the desired size.
_columnMeasureCache = columnResult; _columnMeasureCache = columnResult;
_rowMeasureCache = rowResult; _rowMeasureCache = rowResult;
_rowLayoutCache = rowLayout;
_columnLayoutCache = columnLayout;
return new Size(columnResult.DesiredLength, rowResult.DesiredLength); return new Size(columnResult.DesiredLength, rowResult.DesiredLength);
// Measure each child only once. // Measure each child only once.
@ -299,13 +312,11 @@ namespace Avalonia.Controls
// arrow back to any statements and re-run them without any side-effect. // arrow back to any statements and re-run them without any side-effect.
var (safeColumns, safeRows) = GetSafeColumnRows(); var (safeColumns, safeRows) = GetSafeColumnRows();
var columnLayout = new GridLayout(ColumnDefinitions); var columnLayout = _columnLayoutCache;
var rowLayout = new GridLayout(RowDefinitions); var rowLayout = _rowLayoutCache;
// Calculate for arrange result. // Calculate for arrange result.
var columnResult = columnLayout.Arrange(finalSize.Width, _columnMeasureCache); var columnResult = columnLayout.Arrange(finalSize.Width, _columnMeasureCache);
var rowResult = rowLayout.Arrange(finalSize.Height, _rowMeasureCache); var rowResult = rowLayout.Arrange(finalSize.Height, _rowMeasureCache);
// Arrange the children. // Arrange the children.
foreach (var child in Children.OfType<Control>()) foreach (var child in Children.OfType<Control>())
{ {
@ -315,7 +326,6 @@ namespace Avalonia.Controls
var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]); var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]);
var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]); var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]);
var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]); var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]);
child.Arrange(new Rect(x, y, width, height)); child.Arrange(new Rect(x, y, width, height));
} }

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

@ -360,7 +360,7 @@ namespace Avalonia.Controls.Primitives
{ {
if (!AlwaysSelected) if (!AlwaysSelected)
{ {
SelectedIndex = -1; selectedIndex = SelectedIndex = -1;
} }
else else
{ {
@ -368,6 +368,11 @@ namespace Avalonia.Controls.Primitives
} }
} }
var items = Items?.Cast<object>();
if (selectedIndex >= items.Count())
{
selectedIndex = SelectedIndex = items.Count() - 1;
}
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:

19
src/Avalonia.Controls/StackPanel.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Linq;
using Avalonia.Input; using Avalonia.Input;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -152,6 +153,7 @@ namespace Avalonia.Controls
double measuredWidth = 0; double measuredWidth = 0;
double measuredHeight = 0; double measuredHeight = 0;
double gap = Gap; double gap = Gap;
bool hasVisibleChild = Children.Any(c => c.IsVisible);
foreach (Control child in Children) foreach (Control child in Children)
{ {
@ -160,23 +162,23 @@ namespace Avalonia.Controls
if (Orientation == Orientation.Vertical) if (Orientation == Orientation.Vertical)
{ {
measuredHeight += size.Height + gap; measuredHeight += size.Height + (child.IsVisible ? gap : 0);
measuredWidth = Math.Max(measuredWidth, size.Width); measuredWidth = Math.Max(measuredWidth, size.Width);
} }
else else
{ {
measuredWidth += size.Width + gap; measuredWidth += size.Width + (child.IsVisible ? gap : 0);
measuredHeight = Math.Max(measuredHeight, size.Height); measuredHeight = Math.Max(measuredHeight, size.Height);
} }
} }
if (Orientation == Orientation.Vertical) if (Orientation == Orientation.Vertical)
{ {
measuredHeight -= gap; measuredHeight -= (hasVisibleChild ? gap : 0);
} }
else else
{ {
measuredWidth -= gap; measuredWidth -= (hasVisibleChild ? gap : 0);
} }
return new Size(measuredWidth, measuredHeight); return new Size(measuredWidth, measuredHeight);
@ -193,6 +195,7 @@ namespace Avalonia.Controls
double arrangedWidth = finalSize.Width; double arrangedWidth = finalSize.Width;
double arrangedHeight = finalSize.Height; double arrangedHeight = finalSize.Height;
double gap = Gap; double gap = Gap;
bool hasVisibleChild = Children.Any(c => c.IsVisible);
if (Orientation == Orientation.Vertical) if (Orientation == Orientation.Vertical)
{ {
@ -214,25 +217,25 @@ namespace Avalonia.Controls
Rect childFinal = new Rect(0, arrangedHeight, width, childHeight); Rect childFinal = new Rect(0, arrangedHeight, width, childHeight);
ArrangeChild(child, childFinal, finalSize, orientation); ArrangeChild(child, childFinal, finalSize, orientation);
arrangedWidth = Math.Max(arrangedWidth, childWidth); arrangedWidth = Math.Max(arrangedWidth, childWidth);
arrangedHeight += childHeight + gap; arrangedHeight += childHeight + (child.IsVisible ? gap : 0);
} }
else else
{ {
double height = Math.Max(childHeight, arrangedHeight); double height = Math.Max(childHeight, arrangedHeight);
Rect childFinal = new Rect(arrangedWidth, 0, childWidth, height); Rect childFinal = new Rect(arrangedWidth, 0, childWidth, height);
ArrangeChild(child, childFinal, finalSize, orientation); ArrangeChild(child, childFinal, finalSize, orientation);
arrangedWidth += childWidth + gap; arrangedWidth += childWidth + (child.IsVisible ? gap : 0);
arrangedHeight = Math.Max(arrangedHeight, childHeight); arrangedHeight = Math.Max(arrangedHeight, childHeight);
} }
} }
if (orientation == Orientation.Vertical) if (orientation == Orientation.Vertical)
{ {
arrangedHeight = Math.Max(arrangedHeight - gap, finalSize.Height); arrangedHeight = Math.Max(arrangedHeight - (hasVisibleChild ? gap : 0), finalSize.Height);
} }
else else
{ {
arrangedWidth = Math.Max(arrangedWidth - gap, finalSize.Width); arrangedWidth = Math.Max(arrangedWidth - (hasVisibleChild ? gap : 0), finalSize.Width);
} }
return new Size(arrangedWidth, arrangedHeight); return new Size(arrangedWidth, arrangedHeight);

2
src/Avalonia.Controls/TextBox.cs

@ -557,7 +557,7 @@ namespace Avalonia.Controls
var index = CaretIndex = _presenter.GetCaretIndex(point); var index = CaretIndex = _presenter.GetCaretIndex(point);
var text = Text; var text = Text;
if (text != null) if (text != null && e.MouseButton == MouseButton.Left)
{ {
switch (e.ClickCount) switch (e.ClickCount)
{ {

11
src/Avalonia.Styling/Styling/Setter.cs

@ -158,18 +158,11 @@ namespace Avalonia.Styling
var activated = new ActivatedObservable(activator, sourceInstance.Observable, description); var activated = new ActivatedObservable(activator, sourceInstance.Observable, description);
return InstancedBinding.OneWay(activated, BindingPriority.StyleTrigger); return InstancedBinding.OneWay(activated, BindingPriority.StyleTrigger);
} }
case BindingMode.OneWayToSource: default:
{
var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
return InstancedBinding.OneWayToSource(activated, BindingPriority.StyleTrigger);
}
case BindingMode.TwoWay:
{ {
var activated = new ActivatedSubject(activator, sourceInstance.Subject, description); var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
return InstancedBinding.TwoWay(activated, BindingPriority.StyleTrigger); return new InstancedBinding(activated, sourceInstance.Mode, BindingPriority.StyleTrigger);
} }
default:
throw new NotSupportedException("Unsupported BindingMode.");
} }
} }

4
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -491,6 +491,10 @@ namespace Avalonia.Skia
{ {
ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage); ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage);
} }
else
{
paint.Color = new SKColor(255, 255, 255, 0);
}
return paintWrapper; return paintWrapper;
} }

2
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -394,7 +394,7 @@ namespace Avalonia.Direct2D1.Media
{ {
return new RadialGradientBrushImpl(radialGradientBrush, _renderTarget, destinationSize); return new RadialGradientBrushImpl(radialGradientBrush, _renderTarget, destinationSize);
} }
else if (imageBrush != null) else if (imageBrush?.Source != null)
{ {
return new ImageBrushImpl( return new ImageBrushImpl(
imageBrush, imageBrush,

18
src/Windows/Avalonia.Win32/SystemDialogImpl.cs

@ -13,6 +13,9 @@ namespace Avalonia.Win32
class SystemDialogImpl : ISystemDialogImpl class SystemDialogImpl : ISystemDialogImpl
{ {
private const UnmanagedMethods.FOS DefaultDialogOptions = UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE |
UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT;
public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
{ {
var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero;
@ -29,7 +32,7 @@ namespace Avalonia.Win32
uint options; uint options;
frm.GetOptions(out options); frm.GetOptions(out options);
options |= (uint)(UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT); options |= (uint)(DefaultDialogOptions);
if (openDialog?.AllowMultiple == true) if (openDialog?.AllowMultiple == true)
options |= (uint)UnmanagedMethods.FOS.FOS_ALLOWMULTISELECT; options |= (uint)UnmanagedMethods.FOS.FOS_ALLOWMULTISELECT;
frm.SetOptions(options); frm.SetOptions(options);
@ -37,13 +40,16 @@ namespace Avalonia.Win32
var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? ""; var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? "";
frm.SetDefaultExtension(defaultExtension); frm.SetDefaultExtension(defaultExtension);
frm.SetFileName(dialog.InitialFileName ?? ""); frm.SetFileName(dialog.InitialFileName ?? "");
frm.SetTitle(dialog.Title); frm.SetTitle(dialog.Title ?? "");
var filters = new List<UnmanagedMethods.COMDLG_FILTERSPEC>(); var filters = new List<UnmanagedMethods.COMDLG_FILTERSPEC>();
foreach (var filter in dialog.Filters) if (dialog.Filters != null)
{ {
var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e)); foreach (var filter in dialog.Filters)
filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name, pszSpec = extMask }); {
var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e));
filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name, pszSpec = extMask });
}
} }
if (filters.Count == 0) if (filters.Count == 0)
filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = "All files", pszSpec = "*.*" }); filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = "All files", pszSpec = "*.*" });
@ -106,7 +112,7 @@ namespace Avalonia.Win32
var frm = (UnmanagedMethods.IFileDialog)unk; var frm = (UnmanagedMethods.IFileDialog)unk;
uint options; uint options;
frm.GetOptions(out options); frm.GetOptions(out options);
options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT); options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | DefaultDialogOptions);
frm.SetOptions(options); frm.SetOptions(options);
if (dialog.InitialDirectory != null) if (dialog.InitialDirectory != null)

52
tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

@ -1,11 +1,12 @@
// 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 Avalonia.Utilities;
using Moq;
using System; using System;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Base.UnitTests namespace Avalonia.Base.UnitTests
@ -21,7 +22,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Initial_Value_Should_Be_UnsetValue() public void Initial_Value_Should_Be_UnsetValue()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
Assert.Same(AvaloniaProperty.UnsetValue, target.Value); Assert.Same(AvaloniaProperty.UnsetValue, target.Value);
} }
@ -29,7 +30,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void First_Binding_Sets_Value() public void First_Binding_Sets_Value()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
target.Add(Single("foo"), 0); target.Add(Single("foo"), 0);
@ -39,7 +40,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Changing_Binding_Should_Set_Value() public void Changing_Binding_Should_Set_Value()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
var subject = new BehaviorSubject<string>("foo"); var subject = new BehaviorSubject<string>("foo");
target.Add(subject, 0); target.Add(subject, 0);
@ -51,7 +52,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Setting_Direct_Value_Should_Override_Binding() public void Setting_Direct_Value_Should_Override_Binding()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
target.Add(Single("foo"), 0); target.Add(Single("foo"), 0);
target.SetValue("bar", 0); target.SetValue("bar", 0);
@ -62,7 +63,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Binding_Firing_Should_Override_Direct_Value() public void Binding_Firing_Should_Override_Direct_Value()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
var source = new BehaviorSubject<object>("initial"); var source = new BehaviorSubject<object>("initial");
target.Add(source, 0); target.Add(source, 0);
@ -76,7 +77,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Earlier_Binding_Firing_Should_Not_Override_Later() public void Earlier_Binding_Firing_Should_Not_Override_Later()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
var nonActive = new BehaviorSubject<object>("na"); var nonActive = new BehaviorSubject<object>("na");
var source = new BehaviorSubject<object>("initial"); var source = new BehaviorSubject<object>("initial");
@ -92,7 +93,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Binding_Completing_Should_Revert_To_Direct_Value() public void Binding_Completing_Should_Revert_To_Direct_Value()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
var source = new BehaviorSubject<object>("initial"); var source = new BehaviorSubject<object>("initial");
target.Add(source, 0); target.Add(source, 0);
@ -108,7 +109,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Binding_With_Lower_Priority_Has_Precedence() public void Binding_With_Lower_Priority_Has_Precedence()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
target.Add(Single("foo"), 1); target.Add(Single("foo"), 1);
target.Add(Single("bar"), 0); target.Add(Single("bar"), 0);
@ -120,7 +121,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Later_Binding_With_Same_Priority_Should_Take_Precedence() public void Later_Binding_With_Same_Priority_Should_Take_Precedence()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
target.Add(Single("foo"), 1); target.Add(Single("foo"), 1);
target.Add(Single("bar"), 0); target.Add(Single("bar"), 0);
@ -133,7 +134,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Changing_Binding_With_Lower_Priority_Should_Set_Not_Value() public void Changing_Binding_With_Lower_Priority_Should_Set_Not_Value()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
var subject = new BehaviorSubject<string>("bar"); var subject = new BehaviorSubject<string>("bar");
target.Add(Single("foo"), 0); target.Add(Single("foo"), 0);
@ -146,7 +147,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void UnsetValue_Should_Fall_Back_To_Next_Binding() public void UnsetValue_Should_Fall_Back_To_Next_Binding()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
var subject = new BehaviorSubject<object>("bar"); var subject = new BehaviorSubject<object>("bar");
target.Add(subject, 0); target.Add(subject, 0);
@ -162,7 +163,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Adding_Value_Should_Call_OnNext() public void Adding_Value_Should_Call_OnNext()
{ {
var owner = new Mock<IPriorityValueOwner>(); var owner = GetMockOwner();
var target = new PriorityValue(owner.Object, TestProperty, typeof(string)); var target = new PriorityValue(owner.Object, TestProperty, typeof(string));
target.Add(Single("foo"), 0); target.Add(Single("foo"), 0);
@ -173,7 +174,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Changing_Value_Should_Call_OnNext() public void Changing_Value_Should_Call_OnNext()
{ {
var owner = new Mock<IPriorityValueOwner>(); var owner = GetMockOwner();
var target = new PriorityValue(owner.Object, TestProperty, typeof(string)); var target = new PriorityValue(owner.Object, TestProperty, typeof(string));
var subject = new BehaviorSubject<object>("foo"); var subject = new BehaviorSubject<object>("foo");
@ -186,7 +187,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Disposing_A_Binding_Should_Revert_To_Next_Value() public void Disposing_A_Binding_Should_Revert_To_Next_Value()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
target.Add(Single("foo"), 0); target.Add(Single("foo"), 0);
var disposable = target.Add(Single("bar"), 0); var disposable = target.Add(Single("bar"), 0);
@ -199,7 +200,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Disposing_A_Binding_Should_Remove_BindingEntry() public void Disposing_A_Binding_Should_Remove_BindingEntry()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
target.Add(Single("foo"), 0); target.Add(Single("foo"), 0);
var disposable = target.Add(Single("bar"), 0); var disposable = target.Add(Single("bar"), 0);
@ -212,7 +213,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Completing_A_Binding_Should_Revert_To_Previous_Binding() public void Completing_A_Binding_Should_Revert_To_Previous_Binding()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
var source = new BehaviorSubject<object>("bar"); var source = new BehaviorSubject<object>("bar");
target.Add(Single("foo"), 0); target.Add(Single("foo"), 0);
@ -226,7 +227,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Completing_A_Binding_Should_Revert_To_Lower_Priority() public void Completing_A_Binding_Should_Revert_To_Lower_Priority()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
var source = new BehaviorSubject<object>("bar"); var source = new BehaviorSubject<object>("bar");
target.Add(Single("foo"), 1); target.Add(Single("foo"), 1);
@ -240,7 +241,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Completing_A_Binding_Should_Remove_BindingEntry() public void Completing_A_Binding_Should_Remove_BindingEntry()
{ {
var target = new PriorityValue(null, TestProperty, typeof(string)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string));
var subject = new BehaviorSubject<object>("bar"); var subject = new BehaviorSubject<object>("bar");
target.Add(Single("foo"), 0); target.Add(Single("foo"), 0);
@ -254,7 +255,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Direct_Value_Should_Be_Coerced() public void Direct_Value_Should_Be_Coerced()
{ {
var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, 10)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(int), x => Math.Min((int)x, 10));
target.SetValue(5, 0); target.SetValue(5, 0);
Assert.Equal(5, target.Value); Assert.Equal(5, target.Value);
@ -265,7 +266,7 @@ namespace Avalonia.Base.UnitTests
[Fact] [Fact]
public void Bound_Value_Should_Be_Coerced() public void Bound_Value_Should_Be_Coerced()
{ {
var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, 10)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(int), x => Math.Min((int)x, 10));
var source = new Subject<object>(); var source = new Subject<object>();
target.Add(source, 0); target.Add(source, 0);
@ -279,7 +280,7 @@ namespace Avalonia.Base.UnitTests
public void Revalidate_Should_ReCoerce_Value() public void Revalidate_Should_ReCoerce_Value()
{ {
var max = 10; var max = 10;
var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, max)); var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(int), x => Math.Min((int)x, max));
var source = new Subject<object>(); var source = new Subject<object>();
target.Add(source, 0); target.Add(source, 0);
@ -302,5 +303,12 @@ namespace Avalonia.Base.UnitTests
{ {
return Observable.Never<T>().StartWith(value); return Observable.Never<T>().StartWith(value);
} }
private static Mock<IPriorityValueOwner> GetMockOwner()
{
var owner = new Mock<IPriorityValueOwner>();
owner.SetupGet(o => o.Setter).Returns(new DeferredSetter<object>());
return owner;
}
} }
} }

50
tests/Avalonia.Base.UnitTests/Utilities/SingleOrQueueTests.cs

@ -0,0 +1,50 @@
using Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
namespace Avalonia.Base.UnitTests.Utilities
{
public class SingleOrQueueTests
{
[Fact]
public void New_SingleOrQueue_Is_Empty()
{
Assert.True(new SingleOrQueue<object>().Empty);
}
[Fact]
public void Dequeue_Throws_When_Empty()
{
var queue = new SingleOrQueue<object>();
Assert.Throws<InvalidOperationException>(() => queue.Dequeue());
}
[Fact]
public void Enqueue_Adds_Element()
{
var queue = new SingleOrQueue<int>();
queue.Enqueue(1);
Assert.False(queue.Empty);
Assert.Equal(1, queue.Dequeue());
}
[Fact]
public void Multiple_Elements_Dequeued_In_Correct_Order()
{
var queue = new SingleOrQueue<int>();
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
Assert.Equal(1, queue.Dequeue());
Assert.Equal(2, queue.Dequeue());
Assert.Equal(3, queue.Dequeue());
}
}
}

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

@ -149,6 +149,34 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(1, target.SelectedIndex); Assert.Equal(1, target.SelectedIndex);
} }
[Fact]
public void SelectedIndex_Item_Is_Updated_As_Items_Removed_When_Last_Item_Is_Selected()
{
var items = new ObservableCollection<string>
{
"Foo",
"Bar",
"FooBar"
};
var target = new SelectingItemsControl
{
Items = items,
Template = Template(),
};
target.ApplyTemplate();
target.SelectedItem = items[2];
Assert.Equal(items[2], target.SelectedItem);
Assert.Equal(2, target.SelectedIndex);
items.RemoveAt(0);
Assert.Equal(items[1], target.SelectedItem);
Assert.Equal(1, target.SelectedIndex);
}
[Fact] [Fact]
public void Setting_SelectedItem_To_Not_Present_Item_Should_Clear_Selection() public void Setting_SelectedItem_To_Not_Present_Item_Should_Clear_Selection()
{ {

39
tests/Avalonia.Controls.UnitTests/StackPanelTests.cs

@ -146,5 +146,44 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(20, 0, 30, 120), target.Children[1].Bounds); Assert.Equal(new Rect(20, 0, 30, 120), target.Children[1].Bounds);
Assert.Equal(new Rect(50, 0, 50, 120), target.Children[2].Bounds); Assert.Equal(new Rect(50, 0, 50, 120), target.Children[2].Bounds);
} }
[Theory]
[InlineData(Orientation.Horizontal)]
[InlineData(Orientation.Vertical)]
public void Gap_Not_Added_For_Invisible_Children(Orientation orientation)
{
var targetThreeChildrenOneInvisble = new StackPanel
{
Gap = 40,
Orientation = orientation,
Children =
{
new StackPanel { Width = 10, Height= 10, IsVisible = false },
new StackPanel { Width = 10, Height= 10 },
new StackPanel { Width = 10, Height= 10 },
}
};
var targetTwoChildrenNoneInvisible = new StackPanel
{
Gap = 40,
Orientation = orientation,
Children =
{
new StackPanel { Width = 10, Height= 10 },
new StackPanel { Width = 10, Height= 10 }
}
};
targetThreeChildrenOneInvisble.Measure(Size.Infinity);
targetThreeChildrenOneInvisble.Arrange(new Rect(targetThreeChildrenOneInvisble.DesiredSize));
targetTwoChildrenNoneInvisible.Measure(Size.Infinity);
targetTwoChildrenNoneInvisible.Arrange(new Rect(targetTwoChildrenNoneInvisible.DesiredSize));
Size sizeWithTwoChildren = targetTwoChildrenNoneInvisible.Bounds.Size;
Size sizeWithThreeChildren = targetThreeChildrenOneInvisble.Bounds.Size;
Assert.Equal(sizeWithTwoChildren, sizeWithThreeChildren);
}
} }
} }

18
tests/Avalonia.RenderTests/Media/ImageBrushTests.cs

@ -32,6 +32,24 @@ namespace Avalonia.Direct2D1.RenderTests.Media
get { return System.IO.Path.Combine(OutputPath, "github_icon_small.png"); } get { return System.IO.Path.Combine(OutputPath, "github_icon_small.png"); }
} }
[Fact]
public async Task ImageBrush_NullSource()
{
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new Rectangle
{
Margin = new Thickness(8),
Fill = new ImageBrush()
}
};
await RenderToFile(target);
CompareImages();
}
[Fact] [Fact]
public async Task ImageBrush_Tile_Fill() public async Task ImageBrush_Tile_Fill()
{ {

BIN
tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NullSource.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

BIN
tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_NullSource.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Loading…
Cancel
Save