committed by
GitHub
118 changed files with 2957 additions and 705 deletions
@ -0,0 +1,53 @@ |
|||
using System; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using Avalonia.Rendering.SceneGraph; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace RenderDemo.Controls |
|||
{ |
|||
public class LineBoundsDemoControl : Control |
|||
{ |
|||
static LineBoundsDemoControl() |
|||
{ |
|||
AffectsRender<LineBoundsDemoControl>(AngleProperty); |
|||
} |
|||
|
|||
public LineBoundsDemoControl() |
|||
{ |
|||
var timer = new DispatcherTimer(); |
|||
timer.Interval = TimeSpan.FromSeconds(1 / 60.0); |
|||
timer.Tick += (sender, e) => Angle += Math.PI / 360; |
|||
timer.Start(); |
|||
} |
|||
|
|||
public static readonly StyledProperty<double> AngleProperty = |
|||
AvaloniaProperty.Register<LineBoundsDemoControl, double>(nameof(Angle)); |
|||
|
|||
public double Angle |
|||
{ |
|||
get => GetValue(AngleProperty); |
|||
set => SetValue(AngleProperty, value); |
|||
} |
|||
|
|||
public override void Render(DrawingContext drawingContext) |
|||
{ |
|||
var lineLength = Math.Sqrt((100 * 100) + (100 * 100)); |
|||
|
|||
var diffX = LineBoundsHelper.CalculateAdjSide(Angle, lineLength); |
|||
var diffY = LineBoundsHelper.CalculateOppSide(Angle, lineLength); |
|||
|
|||
|
|||
var p1 = new Point(200, 200); |
|||
var p2 = new Point(p1.X + diffX, p1.Y + diffY); |
|||
|
|||
var pen = new Pen(Brushes.Green, 20, lineCap: PenLineCap.Square); |
|||
var boundPen = new Pen(Brushes.Black); |
|||
|
|||
drawingContext.DrawLine(pen, p1, p2); |
|||
|
|||
drawingContext.DrawRectangle(boundPen, LineBoundsHelper.CalculateBounds(p1, p2, pen)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
xmlns:controls="clr-namespace:RenderDemo.Controls" |
|||
x:Class="RenderDemo.Pages.LineBoundsPage"> |
|||
<controls:LineBoundsDemoControl /> |
|||
</UserControl> |
|||
@ -0,0 +1,19 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace RenderDemo.Pages |
|||
{ |
|||
public class LineBoundsPage : UserControl |
|||
{ |
|||
public LineBoundsPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Rendering.SceneGraph |
|||
{ |
|||
internal static class LineBoundsHelper |
|||
{ |
|||
private static double CalculateAngle(Point p1, Point p2) |
|||
{ |
|||
var xDiff = p2.X - p1.X; |
|||
var yDiff = p2.Y - p1.Y; |
|||
|
|||
return Math.Atan2(yDiff, xDiff); |
|||
} |
|||
|
|||
internal static double CalculateOppSide(double angle, double hyp) |
|||
{ |
|||
return Math.Sin(angle) * hyp; |
|||
} |
|||
|
|||
internal static double CalculateAdjSide(double angle, double hyp) |
|||
{ |
|||
return Math.Cos(angle) * hyp; |
|||
} |
|||
|
|||
private static (Point p1, Point p2) TranslatePointsAlongTangent(Point p1, Point p2, double angle, double distance) |
|||
{ |
|||
var xDiff = CalculateOppSide(angle, distance); |
|||
var yDiff = CalculateAdjSide(angle, distance); |
|||
|
|||
var c1 = new Point(p1.X + xDiff, p1.Y - yDiff); |
|||
var c2 = new Point(p1.X - xDiff, p1.Y + yDiff); |
|||
|
|||
var c3 = new Point(p2.X + xDiff, p2.Y - yDiff); |
|||
var c4 = new Point(p2.X - xDiff, p2.Y + yDiff); |
|||
|
|||
var minX = Math.Min(c1.X, Math.Min(c2.X, Math.Min(c3.X, c4.X))); |
|||
var minY = Math.Min(c1.Y, Math.Min(c2.Y, Math.Min(c3.Y, c4.Y))); |
|||
var maxX = Math.Max(c1.X, Math.Max(c2.X, Math.Max(c3.X, c4.X))); |
|||
var maxY = Math.Max(c1.Y, Math.Max(c2.Y, Math.Max(c3.Y, c4.Y))); |
|||
|
|||
return (new Point(minX, minY), new Point(maxX, maxY)); |
|||
} |
|||
|
|||
private static Rect CalculateBounds(Point p1, Point p2, double thickness, double angleToCorner) |
|||
{ |
|||
var pts = TranslatePointsAlongTangent(p1, p2, angleToCorner, thickness / 2); |
|||
|
|||
return new Rect(pts.p1, pts.p2); |
|||
} |
|||
|
|||
public static Rect CalculateBounds(Point p1, Point p2, IPen p) |
|||
{ |
|||
var radians = CalculateAngle(p1, p2); |
|||
|
|||
if (p.LineCap != PenLineCap.Flat) |
|||
{ |
|||
var pts = TranslatePointsAlongTangent(p1, p2, radians - Math.PI / 2, p.Thickness / 2); |
|||
|
|||
return CalculateBounds(pts.p1, pts.p2, p.Thickness, radians); |
|||
} |
|||
else |
|||
{ |
|||
return CalculateBounds(p1, p2, p.Thickness, radians); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,339 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Styling; |
|||
using Avalonia.UnitTests; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Animation.UnitTests |
|||
{ |
|||
public class AnimatableTests |
|||
{ |
|||
[Fact] |
|||
public void Transition_Is_Not_Applied_When_Not_Attached_To_Visual_Tree() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = new Control |
|||
{ |
|||
Transitions = new Transitions { target.Object }, |
|||
}; |
|||
|
|||
control.Opacity = 0.5; |
|||
|
|||
target.Verify(x => x.Apply( |
|||
control, |
|||
It.IsAny<IClock>(), |
|||
1.0, |
|||
0.5), |
|||
Times.Never); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Transition_Is_Not_Applied_To_Initial_Style() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.RealStyler)) |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = new Control |
|||
{ |
|||
Transitions = new Transitions { target.Object }, |
|||
}; |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
Styles = |
|||
{ |
|||
new Style(x => x.OfType<Control>()) |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter(Visual.OpacityProperty, 0.8), |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
root.Child = control; |
|||
|
|||
Assert.Equal(0.8, control.Opacity); |
|||
|
|||
target.Verify(x => x.Apply( |
|||
It.IsAny<Control>(), |
|||
It.IsAny<IClock>(), |
|||
It.IsAny<object>(), |
|||
It.IsAny<object>()), |
|||
Times.Never); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Transition_Is_Applied_When_Local_Value_Changes() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = CreateControl(target.Object); |
|||
|
|||
control.Opacity = 0.5; |
|||
|
|||
target.Verify(x => x.Apply( |
|||
control, |
|||
It.IsAny<IClock>(), |
|||
1.0, |
|||
0.5)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Transition_Is_Not_Applied_When_Animated_Value_Changes() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = CreateControl(target.Object); |
|||
|
|||
control.SetValue(Visual.OpacityProperty, 0.5, BindingPriority.Animation); |
|||
|
|||
target.Verify(x => x.Apply( |
|||
control, |
|||
It.IsAny<IClock>(), |
|||
1.0, |
|||
0.5), |
|||
Times.Never); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Transition_Is_Not_Applied_When_StyleTrigger_Changes_With_LocalValue_Present() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = CreateControl(target.Object); |
|||
|
|||
control.SetValue(Visual.OpacityProperty, 0.5); |
|||
|
|||
target.Verify(x => x.Apply( |
|||
control, |
|||
It.IsAny<IClock>(), |
|||
1.0, |
|||
0.5)); |
|||
target.ResetCalls(); |
|||
|
|||
control.SetValue(Visual.OpacityProperty, 0.8, BindingPriority.StyleTrigger); |
|||
|
|||
target.Verify(x => x.Apply( |
|||
It.IsAny<Control>(), |
|||
It.IsAny<IClock>(), |
|||
It.IsAny<object>(), |
|||
It.IsAny<object>()), |
|||
Times.Never); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Transition_Is_Disposed_When_Local_Value_Changes() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = CreateControl(target.Object); |
|||
var sub = new Mock<IDisposable>(); |
|||
|
|||
target.Setup(x => x.Apply(control, It.IsAny<IClock>(), 1.0, 0.5)).Returns(sub.Object); |
|||
|
|||
control.Opacity = 0.5; |
|||
sub.ResetCalls(); |
|||
control.Opacity = 0.4; |
|||
|
|||
sub.Verify(x => x.Dispose()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void New_Transition_Is_Applied_When_Local_Value_Changes() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = CreateControl(target.Object); |
|||
|
|||
target.Setup(x => x.Property).Returns(Visual.OpacityProperty); |
|||
target.Setup(x => x.Apply(control, It.IsAny<IClock>(), 1.0, 0.5)) |
|||
.Callback(() => |
|||
{ |
|||
control.SetValue(Visual.OpacityProperty, 0.9, BindingPriority.Animation); |
|||
}) |
|||
.Returns(Mock.Of<IDisposable>()); |
|||
|
|||
control.Opacity = 0.5; |
|||
|
|||
Assert.Equal(0.9, control.Opacity); |
|||
target.ResetCalls(); |
|||
|
|||
control.Opacity = 0.4; |
|||
|
|||
target.Verify(x => x.Apply( |
|||
control, |
|||
It.IsAny<IClock>(), |
|||
0.9, |
|||
0.4)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Transition_Is_Not_Applied_When_Removed_From_Visual_Tree() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = CreateControl(target.Object); |
|||
|
|||
control.Opacity = 0.5; |
|||
|
|||
target.Verify(x => x.Apply( |
|||
control, |
|||
It.IsAny<IClock>(), |
|||
1.0, |
|||
0.5)); |
|||
target.ResetCalls(); |
|||
|
|||
var root = (TestRoot)control.Parent; |
|||
root.Child = null; |
|||
control.Opacity = 0.8; |
|||
|
|||
target.Verify(x => x.Apply( |
|||
It.IsAny<Control>(), |
|||
It.IsAny<IClock>(), |
|||
It.IsAny<object>(), |
|||
It.IsAny<object>()), |
|||
Times.Never); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Animation_Is_Cancelled_When_Transition_Removed() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = CreateControl(target.Object); |
|||
var sub = new Mock<IDisposable>(); |
|||
|
|||
target.Setup(x => x.Apply( |
|||
It.IsAny<Animatable>(), |
|||
It.IsAny<IClock>(), |
|||
It.IsAny<object>(), |
|||
It.IsAny<object>())).Returns(sub.Object); |
|||
|
|||
control.Opacity = 0.5; |
|||
control.Transitions.RemoveAt(0); |
|||
|
|||
sub.Verify(x => x.Dispose()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Animation_Is_Cancelled_When_New_Style_Activates() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.RealStyler)) |
|||
{ |
|||
var target = CreateTarget(); |
|||
var control = CreateStyledControl(target.Object); |
|||
var sub = new Mock<IDisposable>(); |
|||
|
|||
target.Setup(x => x.Apply( |
|||
control, |
|||
It.IsAny<IClock>(), |
|||
1.0, |
|||
0.5)).Returns(sub.Object); |
|||
|
|||
control.Opacity = 0.5; |
|||
|
|||
target.Verify(x => x.Apply( |
|||
control, |
|||
It.IsAny<Clock>(), |
|||
1.0, |
|||
0.5), |
|||
Times.Once); |
|||
|
|||
control.Classes.Add("foo"); |
|||
|
|||
sub.Verify(x => x.Dispose()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Transition_From_Style_Trigger_Is_Applied() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.RealStyler)) |
|||
{ |
|||
var target = CreateTransition(Control.WidthProperty); |
|||
var control = CreateStyledControl(transition2: target.Object); |
|||
var sub = new Mock<IDisposable>(); |
|||
|
|||
control.Classes.Add("foo"); |
|||
control.Width = 100; |
|||
|
|||
target.Verify(x => x.Apply( |
|||
control, |
|||
It.IsAny<Clock>(), |
|||
double.NaN, |
|||
100.0), |
|||
Times.Once); |
|||
} |
|||
} |
|||
|
|||
private static Mock<ITransition> CreateTarget() |
|||
{ |
|||
return CreateTransition(Visual.OpacityProperty); |
|||
} |
|||
|
|||
private static Control CreateControl(ITransition transition) |
|||
{ |
|||
var control = new Control |
|||
{ |
|||
Transitions = new Transitions { transition }, |
|||
}; |
|||
|
|||
var root = new TestRoot(control); |
|||
return control; |
|||
} |
|||
|
|||
private static Control CreateStyledControl( |
|||
ITransition transition1 = null, |
|||
ITransition transition2 = null) |
|||
{ |
|||
transition1 = transition1 ?? CreateTarget().Object; |
|||
transition2 = transition2 ?? CreateTransition(Control.WidthProperty).Object; |
|||
|
|||
var control = new Control |
|||
{ |
|||
Styles = |
|||
{ |
|||
new Style(x => x.OfType<Control>()) |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter |
|||
{ |
|||
Property = Control.TransitionsProperty, |
|||
Value = new Transitions { transition1 }, |
|||
} |
|||
} |
|||
}, |
|||
new Style(x => x.OfType<Control>().Class("foo")) |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter |
|||
{ |
|||
Property = Control.TransitionsProperty, |
|||
Value = new Transitions { transition2 }, |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var root = new TestRoot(control); |
|||
return control; |
|||
} |
|||
|
|||
private static Mock<ITransition> CreateTransition(AvaloniaProperty property) |
|||
{ |
|||
var target = new Mock<ITransition>(); |
|||
var sub = new Mock<IDisposable>(); |
|||
|
|||
target.Setup(x => x.Property).Returns(property); |
|||
target.Setup(x => x.Apply( |
|||
It.IsAny<Animatable>(), |
|||
It.IsAny<IClock>(), |
|||
It.IsAny<object>(), |
|||
It.IsAny<object>())).Returns(sub.Object); |
|||
|
|||
return target; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,142 @@ |
|||
// 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.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
using Avalonia.Data; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Base.UnitTests |
|||
{ |
|||
public class AvaloniaObjectTests_OnPropertyChanged |
|||
{ |
|||
[Fact] |
|||
public void OnPropertyChangedCore_Is_Called_On_Property_Change() |
|||
{ |
|||
var target = new Class1(); |
|||
|
|||
target.SetValue(Class1.FooProperty, "newvalue"); |
|||
|
|||
Assert.Equal(1, target.CoreChanges.Count); |
|||
|
|||
var change = (AvaloniaPropertyChangedEventArgs<string>)target.CoreChanges[0]; |
|||
|
|||
Assert.Equal("newvalue", change.NewValue.Value); |
|||
Assert.Equal("foodefault", change.OldValue.Value); |
|||
Assert.Equal(BindingPriority.LocalValue, change.Priority); |
|||
Assert.True(change.IsEffectiveValueChange); |
|||
} |
|||
|
|||
[Fact] |
|||
public void OnPropertyChangedCore_Is_Called_On_Non_Effective_Property_Value_Change() |
|||
{ |
|||
var target = new Class1(); |
|||
|
|||
target.SetValue(Class1.FooProperty, "newvalue"); |
|||
target.SetValue(Class1.FooProperty, "styled", BindingPriority.Style); |
|||
|
|||
Assert.Equal(2, target.CoreChanges.Count); |
|||
|
|||
var change = (AvaloniaPropertyChangedEventArgs<string>)target.CoreChanges[1]; |
|||
|
|||
Assert.Equal("styled", change.NewValue.Value); |
|||
Assert.False(change.OldValue.HasValue); |
|||
Assert.Equal(BindingPriority.Style, change.Priority); |
|||
Assert.False(change.IsEffectiveValueChange); |
|||
} |
|||
|
|||
[Fact] |
|||
public void OnPropertyChangedCore_Is_Called_On_All_Binding_Property_Changes() |
|||
{ |
|||
var target = new Class1(); |
|||
var style = new Subject<BindingValue<string>>(); |
|||
var animation = new Subject<BindingValue<string>>(); |
|||
var templatedParent = new Subject<BindingValue<string>>(); |
|||
|
|||
target.Bind(Class1.FooProperty, style, BindingPriority.Style); |
|||
target.Bind(Class1.FooProperty, animation, BindingPriority.Animation); |
|||
target.Bind(Class1.FooProperty, templatedParent, BindingPriority.TemplatedParent); |
|||
|
|||
style.OnNext("style1"); |
|||
templatedParent.OnNext("tp1"); |
|||
animation.OnNext("a1"); |
|||
templatedParent.OnNext("tp2"); |
|||
templatedParent.OnCompleted(); |
|||
animation.OnNext("a2"); |
|||
style.OnNext("style2"); |
|||
style.OnCompleted(); |
|||
animation.OnCompleted(); |
|||
|
|||
var changes = target.CoreChanges.Cast<AvaloniaPropertyChangedEventArgs<string>>(); |
|||
|
|||
Assert.Equal( |
|||
new[] { true, true, true, false, false, true, false, false, true }, |
|||
changes.Select(x => x.IsEffectiveValueChange).ToList()); |
|||
Assert.Equal( |
|||
new[] { "style1", "tp1", "a1", "tp2", "$unset", "a2", "style2", "$unset", "foodefault" }, |
|||
changes.Select(x => x.NewValue.GetValueOrDefault("$unset")).ToList()); |
|||
Assert.Equal( |
|||
new[] { "foodefault", "style1", "tp1", "$unset", "$unset", "a1", "$unset", "$unset", "a2" }, |
|||
changes.Select(x => x.OldValue.GetValueOrDefault("$unset")).ToList()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void OnPropertyChanged_Is_Called_Only_For_Effective_Value_Changes() |
|||
{ |
|||
var target = new Class1(); |
|||
|
|||
target.SetValue(Class1.FooProperty, "newvalue"); |
|||
target.SetValue(Class1.FooProperty, "styled", BindingPriority.Style); |
|||
|
|||
Assert.Equal(1, target.Changes.Count); |
|||
Assert.Equal(2, target.CoreChanges.Count); |
|||
} |
|||
|
|||
private class Class1 : AvaloniaObject |
|||
{ |
|||
public static readonly StyledProperty<string> FooProperty = |
|||
AvaloniaProperty.Register<Class1, string>("Foo", "foodefault"); |
|||
|
|||
public Class1() |
|||
{ |
|||
Changes = new List<AvaloniaPropertyChangedEventArgs>(); |
|||
CoreChanges = new List<AvaloniaPropertyChangedEventArgs>(); |
|||
} |
|||
|
|||
public List<AvaloniaPropertyChangedEventArgs> Changes { get; } |
|||
public List<AvaloniaPropertyChangedEventArgs> CoreChanges { get; } |
|||
|
|||
protected override void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change) |
|||
{ |
|||
CoreChanges.Add(Clone(change)); |
|||
base.OnPropertyChangedCore(change); |
|||
} |
|||
|
|||
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change) |
|||
{ |
|||
Changes.Add(Clone(change)); |
|||
base.OnPropertyChanged(change); |
|||
} |
|||
|
|||
private static AvaloniaPropertyChangedEventArgs<T> Clone<T>(AvaloniaPropertyChangedEventArgs<T> change) |
|||
{ |
|||
var result = new AvaloniaPropertyChangedEventArgs<T>( |
|||
change.Sender, |
|||
change.Property, |
|||
change.OldValue, |
|||
change.NewValue, |
|||
change.Priority); |
|||
|
|||
if (!change.IsEffectiveValueChange) |
|||
{ |
|||
result.MarkNonEffectiveValue(); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue