committed by
GitHub
56 changed files with 963 additions and 548 deletions
@ -0,0 +1,28 @@ |
|||||
|
// 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>
|
||||
|
/// Represents boxed value of type <typeparamref name="T"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">Type of stored value.</typeparam>
|
||||
|
internal readonly struct BoxedValue<T> |
||||
|
{ |
||||
|
public BoxedValue(T value) |
||||
|
{ |
||||
|
Boxed = value; |
||||
|
Typed = value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Boxed value.
|
||||
|
/// </summary>
|
||||
|
public object Boxed { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Typed value.
|
||||
|
/// </summary>
|
||||
|
public T Typed { get; } |
||||
|
} |
||||
|
} |
||||
@ -1,166 +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.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Linq; |
|
||||
using System.Reactive.Disposables; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using Avalonia.Collections; |
|
||||
using Avalonia.Controls.Presenters; |
|
||||
using Avalonia.Controls.Primitives; |
|
||||
using Avalonia.Interactivity; |
|
||||
using Avalonia.LogicalTree; |
|
||||
|
|
||||
namespace Avalonia.Controls.Mixins |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adds content control functionality to control classes.
|
|
||||
/// </summary>
|
|
||||
/// <para>
|
|
||||
/// The <see cref="ContentControlMixin"/> adds behavior to a control which acts as a content
|
|
||||
/// control such as <see cref="ContentControl"/> and <see cref="HeaderedItemsControl"/>. It
|
|
||||
/// keeps the control's logical children in sync with the content being displayed by the
|
|
||||
/// control.
|
|
||||
/// </para>
|
|
||||
public class ContentControlMixin |
|
||||
{ |
|
||||
private static Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>> subscriptions = |
|
||||
new Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>>(() => |
|
||||
new ConditionalWeakTable<TemplatedControl, IDisposable>()); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="SelectableMixin"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TControl">The control type.</typeparam>
|
|
||||
/// <param name="content">The content property.</param>
|
|
||||
/// <param name="logicalChildrenSelector">
|
|
||||
/// Given an control of <typeparamref name="TControl"/> should return the control's
|
|
||||
/// logical children collection.
|
|
||||
/// </param>
|
|
||||
/// <param name="presenterName">
|
|
||||
/// The name of the content presenter in the control's template.
|
|
||||
/// </param>
|
|
||||
public static void Attach<TControl>( |
|
||||
AvaloniaProperty content, |
|
||||
Func<TControl, IAvaloniaList<ILogical>> logicalChildrenSelector, |
|
||||
string presenterName = "PART_ContentPresenter") |
|
||||
where TControl : TemplatedControl |
|
||||
{ |
|
||||
Contract.Requires<ArgumentNullException>(content != null); |
|
||||
Contract.Requires<ArgumentNullException>(logicalChildrenSelector != null); |
|
||||
|
|
||||
void ChildChanging(object s, AvaloniaPropertyChangedEventArgs e) |
|
||||
{ |
|
||||
if (s is IControl sender && sender?.TemplatedParent is TControl parent) |
|
||||
{ |
|
||||
UpdateLogicalChild( |
|
||||
sender, |
|
||||
logicalChildrenSelector(parent), |
|
||||
e.OldValue, |
|
||||
null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void TemplateApplied(object s, RoutedEventArgs ev) |
|
||||
{ |
|
||||
if (s is TControl sender) |
|
||||
{ |
|
||||
var e = (TemplateAppliedEventArgs)ev; |
|
||||
var presenter = e.NameScope.Find(presenterName) as IContentPresenter; |
|
||||
|
|
||||
if (presenter != null) |
|
||||
{ |
|
||||
presenter.ApplyTemplate(); |
|
||||
|
|
||||
var logicalChildren = logicalChildrenSelector(sender); |
|
||||
var subscription = new CompositeDisposable(); |
|
||||
|
|
||||
presenter.ChildChanging += ChildChanging; |
|
||||
subscription.Add(Disposable.Create(() => presenter.ChildChanging -= ChildChanging)); |
|
||||
|
|
||||
subscription.Add(presenter |
|
||||
.GetPropertyChangedObservable(ContentPresenter.ChildProperty) |
|
||||
.Subscribe(c => UpdateLogicalChild( |
|
||||
sender, |
|
||||
logicalChildren, |
|
||||
null, |
|
||||
c.NewValue))); |
|
||||
|
|
||||
UpdateLogicalChild( |
|
||||
sender, |
|
||||
logicalChildren, |
|
||||
null, |
|
||||
presenter.GetValue(ContentPresenter.ChildProperty)); |
|
||||
|
|
||||
if (subscriptions.Value.TryGetValue(sender, out IDisposable previousSubscription)) |
|
||||
{ |
|
||||
subscription = new CompositeDisposable(previousSubscription, subscription); |
|
||||
subscriptions.Value.Remove(sender); |
|
||||
} |
|
||||
|
|
||||
subscriptions.Value.Add(sender, subscription); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
TemplatedControl.TemplateAppliedEvent.AddClassHandler( |
|
||||
typeof(TControl), |
|
||||
TemplateApplied, |
|
||||
RoutingStrategies.Direct); |
|
||||
|
|
||||
content.Changed.Subscribe(e => |
|
||||
{ |
|
||||
if (e.Sender is TControl sender) |
|
||||
{ |
|
||||
var logicalChildren = logicalChildrenSelector(sender); |
|
||||
UpdateLogicalChild(sender, logicalChildren, e.OldValue, e.NewValue); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
Control.TemplatedParentProperty.Changed.Subscribe(e => |
|
||||
{ |
|
||||
if (e.Sender is TControl sender) |
|
||||
{ |
|
||||
var logicalChild = logicalChildrenSelector(sender).FirstOrDefault() as IControl; |
|
||||
logicalChild?.SetValue(Control.TemplatedParentProperty, sender.TemplatedParent); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
TemplatedControl.TemplateProperty.Changed.Subscribe(e => |
|
||||
{ |
|
||||
if (e.Sender is TControl sender) |
|
||||
{ |
|
||||
if (subscriptions.Value.TryGetValue(sender, out IDisposable subscription)) |
|
||||
{ |
|
||||
subscription.Dispose(); |
|
||||
subscriptions.Value.Remove(sender); |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
private static void UpdateLogicalChild( |
|
||||
IControl control, |
|
||||
IAvaloniaList<ILogical> logicalChildren, |
|
||||
object oldValue, |
|
||||
object newValue) |
|
||||
{ |
|
||||
if (oldValue != newValue) |
|
||||
{ |
|
||||
if (oldValue is IControl child) |
|
||||
{ |
|
||||
logicalChildren.Remove(child); |
|
||||
((ISetInheritanceParent)child).SetParent(child.Parent); |
|
||||
} |
|
||||
|
|
||||
child = newValue as IControl; |
|
||||
|
|
||||
if (child != null && !logicalChildren.Contains(child)) |
|
||||
{ |
|
||||
child.SetValue(Control.TemplatedParentProperty, control.TemplatedParent); |
|
||||
logicalChildren.Add(child); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,107 +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.
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using Avalonia.Collections; |
|
||||
using Avalonia.Controls.Mixins; |
|
||||
using Avalonia.Controls.Presenters; |
|
||||
using Avalonia.Controls.Primitives; |
|
||||
using Avalonia.Controls.Templates; |
|
||||
using Avalonia.LogicalTree; |
|
||||
using Moq; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Avalonia.Controls.UnitTests.Mixins |
|
||||
{ |
|
||||
public class ContentControlMixinTests |
|
||||
{ |
|
||||
[Fact] |
|
||||
public void Multiple_Mixin_Usages_Should_Not_Throw() |
|
||||
{ |
|
||||
var target = new TestControl() |
|
||||
{ |
|
||||
Template = new FuncControlTemplate((_, scope) => new Panel |
|
||||
{ |
|
||||
Children = |
|
||||
{ |
|
||||
new ContentPresenter {Name = "Content_1_Presenter"}.RegisterInNameScope(scope), |
|
||||
new ContentPresenter {Name = "Content_2_Presenter"}.RegisterInNameScope(scope) |
|
||||
} |
|
||||
}) |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
var ex = Record.Exception(() => target.ApplyTemplate()); |
|
||||
|
|
||||
Assert.Null(ex); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Replacing_Template_Releases_Events() |
|
||||
{ |
|
||||
var p1 = new ContentPresenter { Name = "Content_1_Presenter" }; |
|
||||
var p2 = new ContentPresenter { Name = "Content_2_Presenter" }; |
|
||||
var target = new TestControl |
|
||||
{ |
|
||||
Template = new FuncControlTemplate((_, scope) => new Panel |
|
||||
{ |
|
||||
Children = |
|
||||
{ |
|
||||
p1.RegisterInNameScope(scope), |
|
||||
p2.RegisterInNameScope(scope) |
|
||||
} |
|
||||
}) |
|
||||
}; |
|
||||
target.ApplyTemplate(); |
|
||||
|
|
||||
Control tc; |
|
||||
|
|
||||
p1.Content = tc = new Control(); |
|
||||
p1.UpdateChild(); |
|
||||
Assert.Contains(tc, target.GetLogicalChildren()); |
|
||||
|
|
||||
p2.Content = tc = new Control(); |
|
||||
p2.UpdateChild(); |
|
||||
Assert.Contains(tc, target.GetLogicalChildren()); |
|
||||
|
|
||||
target.Template = null; |
|
||||
|
|
||||
p1.Content = tc = new Control(); |
|
||||
p1.UpdateChild(); |
|
||||
Assert.DoesNotContain(tc, target.GetLogicalChildren()); |
|
||||
|
|
||||
p2.Content = tc = new Control(); |
|
||||
p2.UpdateChild(); |
|
||||
Assert.DoesNotContain(tc, target.GetLogicalChildren()); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
private class TestControl : TemplatedControl |
|
||||
{ |
|
||||
public static readonly StyledProperty<object> Content1Property = |
|
||||
AvaloniaProperty.Register<TestControl, object>(nameof(Content1)); |
|
||||
|
|
||||
public static readonly StyledProperty<object> Content2Property = |
|
||||
AvaloniaProperty.Register<TestControl, object>(nameof(Content2)); |
|
||||
|
|
||||
static TestControl() |
|
||||
{ |
|
||||
ContentControlMixin.Attach<TestControl>(Content1Property, x => x.LogicalChildren, "Content_1_Presenter"); |
|
||||
ContentControlMixin.Attach<TestControl>(Content2Property, x => x.LogicalChildren, "Content_2_Presenter"); |
|
||||
} |
|
||||
|
|
||||
public object Content1 |
|
||||
{ |
|
||||
get { return GetValue(Content1Property); } |
|
||||
set { SetValue(Content1Property, value); } |
|
||||
} |
|
||||
|
|
||||
public object Content2 |
|
||||
{ |
|
||||
get { return GetValue(Content2Property); } |
|
||||
set { SetValue(Content2Property, value); } |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,112 @@ |
|||||
|
// 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 Xunit; |
||||
|
using Avalonia; |
||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Visuals.UnitTests |
||||
|
{ |
||||
|
public class VectorTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Length_Should_Return_Correct_Length_Of_Vector() |
||||
|
{ |
||||
|
var vector = new Vector(2, 4); |
||||
|
var length = Math.Sqrt(2 * 2 + 4 * 4); |
||||
|
|
||||
|
Assert.Equal(length, vector.Length); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Length_Squared_Should_Return_Correct_Length_Of_Vector() |
||||
|
{ |
||||
|
var vectorA = new Vector(2, 4); |
||||
|
var squaredLengthA = 2 * 2 + 4 * 4; |
||||
|
|
||||
|
Assert.Equal(squaredLengthA, vectorA.SquaredLength); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Normalize_Should_Return_Normalized_Vector() |
||||
|
{ |
||||
|
// the length of a normalized vector must be 1
|
||||
|
|
||||
|
var vectorA = new Vector(13, 84); |
||||
|
var vectorB = new Vector(-34, 345); |
||||
|
var vectorC = new Vector(-34, -84); |
||||
|
|
||||
|
Assert.Equal(1.0, vectorA.Normalize().Length); |
||||
|
Assert.Equal(1.0, vectorB.Normalize().Length); |
||||
|
Assert.Equal(1.0, vectorC.Normalize().Length); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Negate_Should_Return_Negated_Vector() |
||||
|
{ |
||||
|
var vector = new Vector(2, 4); |
||||
|
var negated = new Vector(-2, -4); |
||||
|
|
||||
|
Assert.Equal(negated, vector.Negate()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Dot_Should_Return_Correct_Value() |
||||
|
{ |
||||
|
var a = new Vector(-6, 8.0); |
||||
|
var b = new Vector(5, 12.0); |
||||
|
|
||||
|
Assert.Equal(66.0, Vector.Dot(a, b)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Cross_Should_Return_Correct_Value() |
||||
|
{ |
||||
|
var a = new Vector(-6, 8.0); |
||||
|
var b = new Vector(5, 12.0); |
||||
|
|
||||
|
Assert.Equal(-112.0, Vector.Cross(a, b)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Divied_By_Vector_Should_Return_Correct_Value() |
||||
|
{ |
||||
|
var a = new Vector(10, 2); |
||||
|
var b = new Vector(5, 2); |
||||
|
|
||||
|
var expected = new Vector(2, 1); |
||||
|
|
||||
|
Assert.Equal(expected, Vector.Divide(a, b)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Divied_Should_Return_Correct_Value() |
||||
|
{ |
||||
|
var vector = new Vector(10, 2); |
||||
|
var expected = new Vector(5, 1); |
||||
|
|
||||
|
Assert.Equal(expected, Vector.Divide(vector, 2)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Multiply_By_Vector_Should_Return_Correct_Value() |
||||
|
{ |
||||
|
var a = new Vector(10, 2); |
||||
|
var b = new Vector(2, 2); |
||||
|
|
||||
|
var expected = new Vector(20, 4); |
||||
|
|
||||
|
Assert.Equal(expected, Vector.Multiply(a, b)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Multiply_Should_Return_Correct_Value() |
||||
|
{ |
||||
|
var vector = new Vector(10, 2); |
||||
|
|
||||
|
var expected = new Vector(20, 4); |
||||
|
|
||||
|
Assert.Equal(expected, Vector.Multiply(vector, 2)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue