Browse Source

Merge branch 'master' into fix/selected-text-no-clear

pull/3748/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
a7ad472ebc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      Avalonia.sln
  2. 18
      src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs
  3. 2
      src/Avalonia.Controls/ContextMenu.cs
  4. 41
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  5. 21
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  6. 11
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  7. 31
      src/Avalonia.Controls/Primitives/ILogicalScrollable.cs
  8. 113
      src/Avalonia.Controls/ScrollViewer.cs
  9. 4
      src/Avalonia.Themes.Default/ScrollViewer.xaml
  10. 5
      src/Avalonia.Visuals/Media/Brush.cs
  11. 40
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  12. 178
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_MultiBinding.cs
  13. 17
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  14. 31
      tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests_ILogicalScrollable.cs
  15. 61
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  16. 62
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  17. 12
      tests/Avalonia.Visuals.UnitTests/Media/BrushTests.cs

8
Avalonia.sln

@ -204,16 +204,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{dabfd304-d6a4-4752-8123-c2ccf7ac7831}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution

18
src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs

@ -27,7 +27,23 @@ namespace Avalonia.Data.Converters
/// <inheritdoc/>
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
var converted = values.OfType<TIn>().ToList();
//standard OfType skip null values, even they are valid for the Type
static IEnumerable<TIn> OfTypeWithDefaultSupport(IList<object> list)
{
foreach (object obj in list)
{
if (obj is TIn result)
{
yield return result;
}
else if (Equals(obj, default(TIn)))
{
yield return default(TIn);
}
}
}
var converted = OfTypeWithDefaultSupport(values).ToList();
if (converted.Count == values.Count)
{

2
src/Avalonia.Controls/ContextMenu.cs

@ -75,7 +75,7 @@ namespace Avalonia.Controls
{
control.PointerReleased -= ControlPointerReleased;
oldMenu._attachedControl = null;
((ISetLogicalParent)oldMenu._popup).SetParent(null);
((ISetLogicalParent)oldMenu._popup)?.SetParent(null);
}
if (e.NewValue is ContextMenu newMenu)

41
src/Avalonia.Controls/Presenters/ItemVirtualizer.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters
{
@ -99,9 +100,14 @@ namespace Avalonia.Controls.Presenters
{
get
{
return Vertical ?
new Size(Owner.Panel.DesiredSize.Width, ExtentValue) :
new Size(ExtentValue, Owner.Panel.DesiredSize.Height);
if (IsLogicalScrollEnabled)
{
return Vertical ?
new Size(Owner.Panel.DesiredSize.Width, ExtentValue) :
new Size(ExtentValue, Owner.Panel.DesiredSize.Height);
}
return default;
}
}
@ -112,9 +118,14 @@ namespace Avalonia.Controls.Presenters
{
get
{
return Vertical ?
new Size(Owner.Panel.Bounds.Width, ViewportValue) :
new Size(ViewportValue, Owner.Panel.Bounds.Height);
if (IsLogicalScrollEnabled)
{
return Vertical ?
new Size(Owner.Panel.Bounds.Width, ViewportValue) :
new Size(ViewportValue, Owner.Panel.Bounds.Height);
}
return default;
}
}
@ -125,11 +136,21 @@ namespace Avalonia.Controls.Presenters
{
get
{
return Vertical ? new Vector(_crossAxisOffset, OffsetValue) : new Vector(OffsetValue, _crossAxisOffset);
if (IsLogicalScrollEnabled)
{
return Vertical ? new Vector(_crossAxisOffset, OffsetValue) : new Vector(OffsetValue, _crossAxisOffset);
}
return default;
}
set
{
if (!IsLogicalScrollEnabled)
{
throw new NotSupportedException("Logical scrolling disabled.");
}
var oldCrossAxisOffset = _crossAxisOffset;
if (Vertical)
@ -164,10 +185,10 @@ namespace Avalonia.Controls.Presenters
}
var virtualizingPanel = owner.Panel as IVirtualizingPanel;
var scrollable = (ILogicalScrollable)owner;
var scrollContentPresenter = owner.Parent as IScrollable;
ItemVirtualizer result = null;
if (virtualizingPanel != null && scrollable.InvalidateScroll != null)
if (virtualizingPanel != null && scrollContentPresenter is object)
{
switch (owner.VirtualizationMode)
{
@ -277,6 +298,6 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Invalidates the current scroll.
/// </summary>
protected void InvalidateScroll() => ((ILogicalScrollable)Owner).InvalidateScroll?.Invoke();
protected void InvalidateScroll() => ((ILogicalScrollable)Owner).RaiseScrollInvalidated(EventArgs.Empty);
}
}

21
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -21,6 +21,7 @@ namespace Avalonia.Controls.Presenters
private bool _canHorizontallyScroll;
private bool _canVerticallyScroll;
private EventHandler _scrollInvalidated;
/// <summary>
/// Initializes static members of the <see cref="ItemsPresenter"/> class.
@ -95,13 +96,17 @@ namespace Avalonia.Controls.Presenters
Size IScrollable.Viewport => Virtualizer?.Viewport ?? Bounds.Size;
/// <inheritdoc/>
Action ILogicalScrollable.InvalidateScroll { get; set; }
event EventHandler ILogicalScrollable.ScrollInvalidated
{
add => _scrollInvalidated += value;
remove => _scrollInvalidated -= value;
}
/// <inheritdoc/>
Size ILogicalScrollable.ScrollSize => new Size(1, 1);
Size ILogicalScrollable.ScrollSize => new Size(ScrollViewer.DefaultSmallChange, 1);
/// <inheritdoc/>
Size ILogicalScrollable.PageScrollSize => new Size(0, 1);
Size ILogicalScrollable.PageScrollSize => Virtualizer?.Viewport ?? new Size(16, 16);
internal ItemVirtualizer Virtualizer { get; private set; }
@ -117,6 +122,12 @@ namespace Avalonia.Controls.Presenters
return Virtualizer?.GetControlInDirection(direction, from);
}
/// <inheritdoc/>
void ILogicalScrollable.RaiseScrollInvalidated(EventArgs e)
{
_scrollInvalidated?.Invoke(this, e);
}
public override void ScrollIntoView(object item)
{
Virtualizer?.ScrollIntoView(item);
@ -138,7 +149,7 @@ namespace Avalonia.Controls.Presenters
{
Virtualizer?.Dispose();
Virtualizer = ItemVirtualizer.Create(this);
((ILogicalScrollable)this).InvalidateScroll?.Invoke();
_scrollInvalidated?.Invoke(this, EventArgs.Empty);
KeyboardNavigation.SetTabNavigation(
(InputElement)Panel,
@ -162,7 +173,7 @@ namespace Avalonia.Controls.Presenters
{
Virtualizer?.Dispose();
Virtualizer = ItemVirtualizer.Create(this);
((ILogicalScrollable)this).InvalidateScroll?.Invoke();
_scrollInvalidated?.Invoke(this, EventArgs.Empty);
}
}
}

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

@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.InteropServices.ComTypes;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters
@ -349,7 +351,7 @@ namespace Avalonia.Controls.Presenters
if (scrollable != null)
{
scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
scrollable.ScrollInvalidated += ScrollInvalidated;
if (scrollable.IsLogicalScrollEnabled)
{
@ -360,12 +362,17 @@ namespace Avalonia.Controls.Presenters
.Subscribe(x => scrollable.CanVerticallyScroll = x),
this.GetObservable(OffsetProperty)
.Skip(1).Subscribe(x => scrollable.Offset = x),
Disposable.Create(() => scrollable.InvalidateScroll = null));
Disposable.Create(() => scrollable.ScrollInvalidated -= ScrollInvalidated));
UpdateFromScrollable(scrollable);
}
}
}
private void ScrollInvalidated(object sender, EventArgs e)
{
UpdateFromScrollable((ILogicalScrollable)sender);
}
private void UpdateFromScrollable(ILogicalScrollable scrollable)
{
var logicalScroll = _logicalScrollSubscription != null;

31
src/Avalonia.Controls/Primitives/ILogicalScrollable.cs

@ -31,22 +31,6 @@ namespace Avalonia.Controls.Primitives
/// </summary>
bool IsLogicalScrollEnabled { get; }
/// <summary>
/// Gets or sets the scroll invalidation method.
/// </summary>
/// <remarks>
/// <para>
/// This method notifies the attached <see cref="ScrollViewer"/> of a change in
/// the <see cref="IScrollable.Extent"/>, <see cref="IScrollable.Offset"/> or
/// <see cref="IScrollable.Viewport"/> properties.
/// </para>
/// <para>
/// This property is set by the parent <see cref="ScrollViewer"/> when the
/// <see cref="ILogicalScrollable"/> is placed inside it.
/// </para>
/// </remarks>
Action InvalidateScroll { get; set; }
/// <summary>
/// Gets the size to scroll by, in logical units.
/// </summary>
@ -57,6 +41,15 @@ namespace Avalonia.Controls.Primitives
/// </summary>
Size PageScrollSize { get; }
/// <summary>
/// Raised when the scroll is invalidated.
/// </summary>
/// <remarks>
/// This event notifies an attached <see cref="ScrollViewer"/> of a change in
/// one of the scroll properties.
/// </remarks>
event EventHandler ScrollInvalidated;
/// <summary>
/// Attempts to bring a portion of the target visual into view by scrolling the content.
/// </summary>
@ -72,5 +65,11 @@ namespace Avalonia.Controls.Primitives
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
IControl GetControlInDirection(NavigationDirection direction, IControl from);
/// <summary>
/// Raises the <see cref="ScrollInvalidated"/> event.
/// </summary>
/// <param name="e">The event args.</param>
void RaiseScrollInvalidated(EventArgs e);
}
}

113
src/Avalonia.Controls/ScrollViewer.cs

@ -59,6 +59,22 @@ namespace Avalonia.Controls
o => o.Viewport,
(o, v) => o.Viewport = v);
/// <summary>
/// Defines the <see cref="LargeChange"/> property.
/// </summary>
public static readonly DirectProperty<ScrollViewer, Size> LargeChangeProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(
nameof(LargeChange),
o => o.LargeChange);
/// <summary>
/// Defines the <see cref="SmallChange"/> property.
/// </summary>
public static readonly DirectProperty<ScrollViewer, Size> SmallChangeProperty =
AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(
nameof(SmallChange),
o => o.SmallChange);
/// <summary>
/// Defines the HorizontalScrollBarMaximum property.
/// </summary>
@ -149,9 +165,15 @@ namespace Avalonia.Controls
nameof(VerticalScrollBarVisibility),
ScrollBarVisibility.Auto);
internal const double DefaultSmallChange = 16;
private IDisposable _childSubscription;
private ILogicalScrollable _logicalScrollable;
private Size _extent;
private Vector _offset;
private Size _viewport;
private Size _largeChange;
private Size _smallChange = new Size(DefaultSmallChange, DefaultSmallChange);
/// <summary>
/// Initializes static members of the <see cref="ScrollViewer"/> class.
@ -228,6 +250,16 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Gets the large (page) change value for the scroll viewer.
/// </summary>
public Size LargeChange => _largeChange;
/// <summary>
/// Gets the small (line) change value for the scroll viewer.
/// </summary>
public Size SmallChange => _smallChange;
/// <summary>
/// Gets or sets the horizontal scrollbar visibility.
/// </summary>
@ -246,22 +278,6 @@ namespace Avalonia.Controls
set { SetValue(VerticalScrollBarVisibilityProperty, value); }
}
/// <summary>
/// Scrolls to the top-left corner of the content.
/// </summary>
public void ScrollToHome()
{
Offset = new Vector(double.NegativeInfinity, double.NegativeInfinity);
}
/// <summary>
/// Scrolls to the bottom-left corner of the content.
/// </summary>
public void ScrollToEnd()
{
Offset = new Vector(double.NegativeInfinity, double.PositiveInfinity);
}
/// <summary>
/// Gets a value indicating whether the viewer can scroll horizontally.
/// </summary>
@ -347,6 +363,22 @@ namespace Avalonia.Controls
/// <inheritdoc/>
IControl IScrollAnchorProvider.CurrentAnchor => null; // TODO: Implement
/// <summary>
/// Scrolls to the top-left corner of the content.
/// </summary>
public void ScrollToHome()
{
Offset = new Vector(double.NegativeInfinity, double.NegativeInfinity);
}
/// <summary>
/// Scrolls to the bottom-left corner of the content.
/// </summary>
public void ScrollToEnd()
{
Offset = new Vector(double.NegativeInfinity, double.PositiveInfinity);
}
/// <summary>
/// Gets the value of the HorizontalScrollBarVisibility attached property.
/// </summary>
@ -397,6 +429,22 @@ namespace Avalonia.Controls
// TODO: Implement
}
protected override bool RegisterContentPresenter(IContentPresenter presenter)
{
_childSubscription?.Dispose();
_childSubscription = null;
if (base.RegisterContentPresenter(presenter))
{
_childSubscription = Presenter?
.GetObservable(ContentPresenter.ChildProperty)
.Subscribe(ChildChanged);
return true;
}
return false;
}
internal static Vector CoerceOffset(Size extent, Size viewport, Vector offset)
{
var maxX = Math.Max(extent.Width - viewport.Width, 0);
@ -431,6 +479,28 @@ namespace Avalonia.Controls
}
}
private void ChildChanged(IControl child)
{
if (_logicalScrollable is object)
{
_logicalScrollable.ScrollInvalidated -= LogicalScrollInvalidated;
_logicalScrollable = null;
}
if (child is ILogicalScrollable logical)
{
_logicalScrollable = logical;
logical.ScrollInvalidated += LogicalScrollInvalidated;
}
CalculatedPropertiesChanged();
}
private void LogicalScrollInvalidated(object sender, EventArgs e)
{
CalculatedPropertiesChanged();
}
private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e)
{
var wasEnabled = !ScrollBarVisibility.Disabled.Equals(e.OldValue);
@ -465,6 +535,17 @@ namespace Avalonia.Controls
RaisePropertyChanged(VerticalScrollBarMaximumProperty, 0, VerticalScrollBarMaximum);
RaisePropertyChanged(VerticalScrollBarValueProperty, 0, VerticalScrollBarValue);
RaisePropertyChanged(VerticalScrollBarViewportSizeProperty, 0, VerticalScrollBarViewportSize);
if (_logicalScrollable?.IsLogicalScrollEnabled == true)
{
SetAndRaise(SmallChangeProperty, ref _smallChange, _logicalScrollable.ScrollSize);
SetAndRaise(LargeChangeProperty, ref _largeChange, _logicalScrollable.PageScrollSize);
}
else
{
SetAndRaise(SmallChangeProperty, ref _smallChange, new Size(DefaultSmallChange, DefaultSmallChange));
SetAndRaise(LargeChangeProperty, ref _largeChange, Viewport);
}
}
protected override void OnKeyDown(KeyEventArgs e)

4
src/Avalonia.Themes.Default/ScrollViewer.xaml

@ -22,6 +22,8 @@
</ScrollContentPresenter>
<ScrollBar Name="horizontalScrollBar"
Orientation="Horizontal"
LargeChange="{Binding LargeChange.Width, RelativeSource={RelativeSource TemplatedParent}}"
SmallChange="{Binding SmallChange.Width, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{TemplateBinding HorizontalScrollBarMaximum}"
Value="{TemplateBinding HorizontalScrollBarValue, Mode=TwoWay}"
ViewportSize="{TemplateBinding HorizontalScrollBarViewportSize}"
@ -30,6 +32,8 @@
Focusable="False"/>
<ScrollBar Name="verticalScrollBar"
Orientation="Vertical"
LargeChange="{Binding LargeChange.Height, RelativeSource={RelativeSource TemplatedParent}}"
SmallChange="{Binding SmallChange.Height, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{TemplateBinding VerticalScrollBarMaximum}"
Value="{TemplateBinding VerticalScrollBarValue, Mode=TwoWay}"
ViewportSize="{TemplateBinding VerticalScrollBarViewportSize}"

5
src/Avalonia.Visuals/Media/Brush.cs

@ -19,6 +19,11 @@ namespace Avalonia.Media
/// <inheritdoc/>
public event EventHandler Invalidated;
static Brush()
{
AffectsRender<Brush>(OpacityProperty);
}
/// <summary>
/// Gets or sets the opacity of the brush.
/// </summary>

40
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -104,6 +104,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
if (results != null && result != null)
{
results.Add(result);
}
return results ?? result;
}
@ -158,9 +163,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
protected void EmitCall(XamlIlEmitContext context, IXamlIlEmitter codeGen, Func<IXamlIlMethod, bool> method)
{
var selectors = context.Configuration.TypeSystem.GetType("Avalonia.Styling.Selectors");
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 &&
m.Parameters[0].FullName == "Avalonia.Styling.Selector"
&& method(m));
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 && method(m));
codeGen.EmitCall(found);
}
}
@ -308,8 +311,35 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
_selectors.Add(node);
}
//TODO: actually find the type
public override IXamlIlType TargetType => _selectors.FirstOrDefault()?.TargetType;
public override IXamlIlType TargetType
{
get
{
IXamlIlType result = null;
foreach (var selector in _selectors)
{
if (selector.TargetType == null)
{
return null;
}
else if (result == null)
{
result = selector.TargetType;
}
else
{
while (!result.IsAssignableFrom(selector.TargetType))
{
result = result.BaseType;
}
}
}
return result;
}
}
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
if (_selectors.Count == 0)

178
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_MultiBinding.cs

@ -0,0 +1,178 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Xunit;
namespace Avalonia.Base.UnitTests
{
public class AvaloniaObjectTests_MultiBinding
{
[Fact]
public void Should_Update()
{
var target = new Class1();
var b = new Subject<object>();
var mb = new MultiBinding()
{
Converter = StringJoinConverter,
Bindings = new[]
{
b.ToBinding()
}
};
target.Bind(Class1.FooProperty, mb);
Assert.Equal(null, target.Foo);
b.OnNext("Foo");
Assert.Equal("Foo", target.Foo);
b.OnNext("Bar");
Assert.Equal("Bar", target.Foo);
}
[Fact]
public void Should_Update_With_Multiple_Bindings()
{
var target = new Class1();
var bindings = Enumerable.Range(0, 3).Select(i => new BehaviorSubject<object>("Empty")).ToArray();
var mb = new MultiBinding()
{
Converter = StringJoinConverter,
Bindings = bindings.Select(b => b.ToBinding()).ToArray()
};
target.Bind(Class1.FooProperty, mb);
Assert.Equal("Empty,Empty,Empty", target.Foo);
bindings[0].OnNext("Foo");
Assert.Equal("Foo,Empty,Empty", target.Foo);
bindings[1].OnNext("Bar");
Assert.Equal("Foo,Bar,Empty", target.Foo);
bindings[2].OnNext("Baz");
Assert.Equal("Foo,Bar,Baz", target.Foo);
}
[Fact]
public void Should_Update_When_Null_Value_In_Bindings()
{
var target = new Class1();
var b = new Subject<object>();
var mb = new MultiBinding()
{
Converter = StringJoinConverter,
Bindings = new[]
{
b.ToBinding()
}
};
target.Bind(Class1.FooProperty, mb);
Assert.Equal(null, target.Foo);
b.OnNext("Foo");
Assert.Equal("Foo", target.Foo);
b.OnNext(null);
Assert.Equal("", target.Foo);
}
[Fact]
public void Should_Update_When_Null_Value_In_Bindings_With_StringFormat()
{
var target = new Class1();
var b = new Subject<object>();
var mb = new MultiBinding()
{
StringFormat = "Converted: {0}",
Bindings = new[]
{
b.ToBinding()
}
};
target.Bind(Class1.FooProperty, mb);
Assert.Equal(null, target.Foo);
b.OnNext("Foo");
Assert.Equal("Converted: Foo", target.Foo);
b.OnNext(null);
Assert.Equal("Converted: ", target.Foo);
}
[Fact]
public void MultiValueConverter_Should_Not_Skip_Valid_Null_ReferenceType_Value()
{
var target = new FuncMultiValueConverter<string, string>(v => string.Join(",", v.ToArray()));
object value = target.Convert(new[] { "Foo", "Bar", "Baz" }, typeof(string), null, CultureInfo.InvariantCulture);
Assert.Equal("Foo,Bar,Baz", value);
value = target.Convert(new[] { null, "Bar", "Baz" }, typeof(string), null, CultureInfo.InvariantCulture);
Assert.Equal(",Bar,Baz", value);
}
[Fact]
public void MultiValueConverter_Should_Not_Skip_Valid_Default_ValueType_Value()
{
var target = new FuncMultiValueConverter<StringValueTypeWrapper, string>(v => string.Join(",", v.ToArray()));
IList<object> create(string[] values) =>
values.Select(v => (object)(v != null ? new StringValueTypeWrapper() { Value = v } : default)).ToList();
object value = target.Convert(create(new[] { "Foo", "Bar", "Baz" }), typeof(string), null, CultureInfo.InvariantCulture);
Assert.Equal("Foo,Bar,Baz", value);
value = target.Convert(create(new[] { null, "Bar", "Baz" }), typeof(string), null, CultureInfo.InvariantCulture);
Assert.Equal(",Bar,Baz", value);
}
private struct StringValueTypeWrapper
{
public string Value;
public override string ToString() => Value;
}
private static IMultiValueConverter StringJoinConverter = new FuncMultiValueConverter<object, string>(v => string.Join(",", v.ToArray()));
private class Class1 : AvaloniaObject
{
public static readonly StyledProperty<string> FooProperty =
AvaloniaProperty.Register<Class1, string>("Foo");
public string Foo
{
get => GetValue(FooProperty);
set => SetValue(FooProperty, value);
}
}
}
}

17
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -1,9 +1,5 @@
using System;
using System.Windows.Input;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Markup.Data;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Moq;
@ -159,6 +155,19 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Can_Set_Clear_ContextMenu_Property()
{
using (Application())
{
var target = new ContextMenu();
var control = new Panel();
control.ContextMenu = target;
control.ContextMenu = null;
}
}
[Fact(Skip = "The only reason this test was 'passing' before was that the author forgot to call Window.ApplyTemplate()")]
public void Cancelling_Closing_Leaves_ContextMenuOpen()
{

31
tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests_ILogicalScrollable.cs

@ -101,7 +101,7 @@ namespace Avalonia.Controls.UnitTests
target.UpdateChild();
Assert.NotNull(scrollable.InvalidateScroll);
Assert.True(scrollable.HasScrollInvalidatedSubscriber);
}
[Fact]
@ -117,7 +117,7 @@ namespace Avalonia.Controls.UnitTests
target.Content = null;
target.UpdateChild();
Assert.Null(scrollable.InvalidateScroll);
Assert.False(scrollable.HasScrollInvalidatedSubscriber);
}
[Fact]
@ -217,7 +217,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(0, 0, 100, 100), scrollable.Bounds);
scrollable.IsLogicalScrollEnabled = false;
scrollable.InvalidateScroll();
scrollable.RaiseScrollInvalidated(EventArgs.Empty);
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
@ -227,7 +227,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(0, 0, 150, 150), scrollable.Bounds);
scrollable.IsLogicalScrollEnabled = true;
scrollable.InvalidateScroll();
scrollable.RaiseScrollInvalidated(EventArgs.Empty);
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
@ -318,12 +318,20 @@ namespace Avalonia.Controls.UnitTests
private Size _extent;
private Vector _offset;
private Size _viewport;
private EventHandler _scrollInvalidated;
public bool CanHorizontallyScroll { get; set; }
public bool CanVerticallyScroll { get; set; }
public bool IsLogicalScrollEnabled { get; set; } = true;
public Size AvailableSize { get; private set; }
public Action InvalidateScroll { get; set; }
public bool HasScrollInvalidatedSubscriber => _scrollInvalidated != null;
public event EventHandler ScrollInvalidated
{
add => _scrollInvalidated += value;
remove => _scrollInvalidated -= value;
}
public Size Extent
{
@ -331,7 +339,7 @@ namespace Avalonia.Controls.UnitTests
set
{
_extent = value;
InvalidateScroll?.Invoke();
_scrollInvalidated?.Invoke(this, EventArgs.Empty);
}
}
@ -341,7 +349,7 @@ namespace Avalonia.Controls.UnitTests
set
{
_offset = value;
InvalidateScroll?.Invoke();
_scrollInvalidated?.Invoke(this, EventArgs.Empty);
}
}
@ -351,7 +359,7 @@ namespace Avalonia.Controls.UnitTests
set
{
_viewport = value;
InvalidateScroll?.Invoke();
_scrollInvalidated?.Invoke(this, EventArgs.Empty);
}
}
@ -376,6 +384,11 @@ namespace Avalonia.Controls.UnitTests
throw new NotImplementedException();
}
public void RaiseScrollInvalidated(EventArgs e)
{
_scrollInvalidated?.Invoke(this, e);
}
protected override Size MeasureOverride(Size availableSize)
{
AvailableSize = availableSize;
@ -388,4 +401,4 @@ namespace Avalonia.Controls.UnitTests
}
}
}
}
}

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

@ -4,6 +4,8 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -86,6 +88,65 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Vector(0, 40), target.Offset);
}
[Fact]
public void SmallChange_Should_Be_16()
{
var target = new ScrollViewer();
Assert.Equal(new Size(16, 16), target.SmallChange);
}
[Fact]
public void LargeChange_Should_Be_Viewport()
{
var target = new ScrollViewer();
target.SetValue(ScrollViewer.ViewportProperty, new Size(104, 143));
Assert.Equal(new Size(104, 143), target.LargeChange);
}
[Fact]
public void SmallChange_Should_Come_From_ILogicalScrollable_If_Present()
{
var child = new Mock<Control>();
var logicalScroll = child.As<ILogicalScrollable>();
logicalScroll.Setup(x => x.IsLogicalScrollEnabled).Returns(true);
logicalScroll.Setup(x => x.ScrollSize).Returns(new Size(12, 43));
var target = new ScrollViewer
{
Template = new FuncControlTemplate<ScrollViewer>(CreateTemplate),
Content = child.Object,
};
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
Assert.Equal(new Size(12, 43), target.SmallChange);
}
[Fact]
public void LargeChange_Should_Come_From_ILogicalScrollable_If_Present()
{
var child = new Mock<Control>();
var logicalScroll = child.As<ILogicalScrollable>();
logicalScroll.Setup(x => x.IsLogicalScrollEnabled).Returns(true);
logicalScroll.Setup(x => x.PageScrollSize).Returns(new Size(45, 67));
var target = new ScrollViewer
{
Template = new FuncControlTemplate<ScrollViewer>(CreateTemplate),
Content = child.Object,
};
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
Assert.Equal(new Size(45, 67), target.LargeChange);
}
private Control CreateTemplate(ScrollViewer control, INameScope scope)
{
return new Grid

62
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -275,5 +275,67 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(Colors.Red, ((ISolidColorBrush)notFoo.Background).Color);
}
}
[Fact]
public void Style_Can_Use_Or_Selector_1()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Border.foo, Border.bar'>
<Setter Property='Background' Value='Red'/>
</Style>
</Window.Styles>
<StackPanel>
<Border Name='foo' Classes='foo'/>
<Border Name='bar' Classes='bar'/>
<Border Name='baz' Classes='baz'/>
</StackPanel>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var foo = window.FindControl<Border>("foo");
var bar = window.FindControl<Border>("bar");
var baz = window.FindControl<Border>("baz");
Assert.Equal(Brushes.Red, foo.Background);
Assert.Equal(Brushes.Red, bar.Background);
Assert.Null(baz.Background);
}
}
[Fact]
public void Style_Can_Use_Or_Selector_2()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Button,Carousel,ListBox'>
<Setter Property='Background' Value='Red'/>
</Style>
</Window.Styles>
<StackPanel>
<Button Name='button'/>
<Carousel Name='carousel'/>
<ListBox Name='listBox'/>
</StackPanel>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var carousel = window.FindControl<Carousel>("carousel");
var listBox = window.FindControl<ListBox>("listBox");
Assert.Equal(Brushes.Red, button.Background);
Assert.Equal(Brushes.Red, carousel.Background);
Assert.Equal(Brushes.Red, listBox.Background);
}
}
}
}

12
tests/Avalonia.Visuals.UnitTests/Media/BrushTests.cs

@ -77,5 +77,17 @@ namespace Avalonia.Visuals.UnitTests.Media
{
Assert.Throws<FormatException>(() => Brush.Parse("#ff808g80"));
}
[Fact]
public void Changing_Opacity_Raises_Invalidated()
{
var target = new SolidColorBrush();
var raised = false;
target.Invalidated += (s, e) => raised = true;
target.Opacity = 0.5;
Assert.True(raised);
}
}
}

Loading…
Cancel
Save