Browse Source

Merge branch 'master' into fixes/scrollchanged-behavior

pull/4080/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
b87f461b5e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      samples/RenderDemo/Pages/TransitionsPage.xaml
  2. 36
      src/Avalonia.Animation/TransitionInstance.cs
  3. 9
      src/Avalonia.Animation/Transition`1.cs
  4. 32
      src/Avalonia.Controls/TextBox.cs
  5. 10
      tests/Avalonia.Animation.UnitTests/TestClock.cs
  6. 88
      tests/Avalonia.Animation.UnitTests/TransitionsTests.cs

14
samples/RenderDemo/Pages/TransitionsPage.xaml

@ -77,6 +77,19 @@
<Setter Property="RenderTransform" Value="skewX(-20deg)" /> <Setter Property="RenderTransform" Value="skewX(-20deg)" />
</Style> </Style>
<Style Selector="Border.Rect6">
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.5" Delay="0:0:1"/>
</Transitions>
</Setter>
<Setter Property="RenderTransform" Value="scale(0.8)" />
</Style>
<Style Selector="Border.Rect6:pointerover">
<Setter Property="RenderTransform" Value="none" />
</Style>
</Styles> </Styles>
</UserControl.Styles> </UserControl.Styles>
@ -95,6 +108,7 @@
<Border Classes="Test Rect3"/> <Border Classes="Test Rect3"/>
<Border Classes="Test Rect4" Background="Navy"/> <Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/> <Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Orange"/>
</WrapPanel> </WrapPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>

36
src/Avalonia.Animation/TransitionInstance.cs

@ -4,6 +4,7 @@ using System.Reactive.Linq;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Animation.Utils; using Avalonia.Animation.Utils;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
@ -13,31 +14,56 @@ namespace Avalonia.Animation
internal class TransitionInstance : SingleSubscriberObservableBase<double> internal class TransitionInstance : SingleSubscriberObservableBase<double>
{ {
private IDisposable _timerSubscription; private IDisposable _timerSubscription;
private TimeSpan _delay;
private TimeSpan _duration; private TimeSpan _duration;
private readonly IClock _baseClock; private readonly IClock _baseClock;
private IClock _clock; private IClock _clock;
public TransitionInstance(IClock clock, TimeSpan Duration) public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration)
{ {
clock = clock ?? throw new ArgumentNullException(nameof(clock)); clock = clock ?? throw new ArgumentNullException(nameof(clock));
_duration = Duration; _delay = delay;
_duration = duration;
_baseClock = clock; _baseClock = clock;
} }
private void TimerTick(TimeSpan t) private void TimerTick(TimeSpan t)
{ {
var interpVal = _duration.Ticks == 0 ? 1d : (double)t.Ticks / _duration.Ticks;
// [<------------- normalizedTotalDur ------------------>]
// [<---- Delay ---->][<---------- Duration ------------>]
// ^- normalizedDelayEnd
// [<---- normalizedInterpVal --->]
var normalizedInterpVal = 1d;
if (!MathUtilities.AreClose(_duration.TotalSeconds, 0d))
{
var normalizedTotalDur = _delay + _duration;
var normalizedDelayEnd = _delay.TotalSeconds / normalizedTotalDur.TotalSeconds;
var normalizedPresentationTime = t.TotalSeconds / normalizedTotalDur.TotalSeconds;
if (normalizedPresentationTime < normalizedDelayEnd
|| MathUtilities.AreClose(normalizedPresentationTime, normalizedDelayEnd))
{
normalizedInterpVal = 0d;
}
else
{
normalizedInterpVal = (t.TotalSeconds - _delay.TotalSeconds) / _duration.TotalSeconds;
}
}
// Clamp interpolation value. // Clamp interpolation value.
if (interpVal >= 1d | interpVal < 0d) if (normalizedInterpVal >= 1d || normalizedInterpVal < 0d)
{ {
PublishNext(1d); PublishNext(1d);
PublishCompleted(); PublishCompleted();
} }
else else
{ {
PublishNext(interpVal); PublishNext(normalizedInterpVal);
} }
} }

9
src/Avalonia.Animation/Transition`1.cs

@ -13,10 +13,15 @@ namespace Avalonia.Animation
private AvaloniaProperty _prop; private AvaloniaProperty _prop;
/// <summary> /// <summary>
/// Gets the duration of the animation. /// Gets or sets the duration of the transition.
/// </summary> /// </summary>
public TimeSpan Duration { get; set; } public TimeSpan Duration { get; set; }
/// <summary>
/// Gets or sets delay before starting the transition.
/// </summary>
public TimeSpan Delay { get; set; } = TimeSpan.Zero;
/// <summary> /// <summary>
/// Gets the easing class to be used. /// Gets the easing class to be used.
/// </summary> /// </summary>
@ -47,7 +52,7 @@ namespace Avalonia.Animation
/// <inheritdocs/> /// <inheritdocs/>
public virtual IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue) public virtual IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue)
{ {
var transition = DoTransition(new TransitionInstance(clock, Duration), (T)oldValue, (T)newValue); var transition = DoTransition(new TransitionInstance(clock, Delay, Duration), (T)oldValue, (T)newValue);
return control.Bind<T>((AvaloniaProperty<T>)Property, transition, Data.BindingPriority.Animation); return control.Bind<T>((AvaloniaProperty<T>)Property, transition, Data.BindingPriority.Animation);
} }
} }

32
src/Avalonia.Controls/TextBox.cs

@ -347,7 +347,7 @@ namespace Avalonia.Controls
if (IsFocused) if (IsFocused)
{ {
DecideCaretVisibility(); _presenter?.ShowCaret();
} }
} }
@ -364,14 +364,7 @@ namespace Avalonia.Controls
{ {
SelectAll(); SelectAll();
} }
else
{
DecideCaretVisibility();
}
}
private void DecideCaretVisibility()
{
_presenter?.ShowCaret(); _presenter?.ShowCaret();
} }
@ -580,15 +573,15 @@ namespace Avalonia.Controls
switch (e.Key) switch (e.Key)
{ {
case Key.Left: case Key.Left:
MoveHorizontal(-1, hasWholeWordModifiers);
movement = true;
selection = DetectSelection(); selection = DetectSelection();
MoveHorizontal(-1, hasWholeWordModifiers, selection);
movement = true;
break; break;
case Key.Right: case Key.Right:
MoveHorizontal(1, hasWholeWordModifiers);
movement = true;
selection = DetectSelection(); selection = DetectSelection();
MoveHorizontal(1, hasWholeWordModifiers, selection);
movement = true;
break; break;
case Key.Up: case Key.Up:
@ -833,13 +826,21 @@ namespace Avalonia.Controls
return result; return result;
} }
private void MoveHorizontal(int direction, bool wholeWord) private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting)
{ {
var text = Text ?? string.Empty; var text = Text ?? string.Empty;
var caretIndex = CaretIndex; var caretIndex = CaretIndex;
if (!wholeWord) if (!wholeWord)
{ {
if (SelectionStart != SelectionEnd && !isSelecting)
{
var start = Math.Min(SelectionStart, SelectionEnd);
var end = Math.Max(SelectionStart, SelectionEnd);
CaretIndex = direction < 0 ? start : end;
return;
}
var index = caretIndex + direction; var index = caretIndex + direction;
if (index < 0 || index > text.Length) if (index < 0 || index > text.Length)
@ -975,6 +976,7 @@ namespace Avalonia.Controls
{ {
SelectionStart = 0; SelectionStart = 0;
SelectionEnd = Text?.Length ?? 0; SelectionEnd = Text?.Length ?? 0;
CaretIndex = SelectionEnd;
} }
private bool DeleteSelection() private bool DeleteSelection()
@ -1055,14 +1057,14 @@ namespace Avalonia.Controls
private void SetSelectionForControlBackspace() private void SetSelectionForControlBackspace()
{ {
SelectionStart = CaretIndex; SelectionStart = CaretIndex;
MoveHorizontal(-1, true); MoveHorizontal(-1, true, false);
SelectionEnd = CaretIndex; SelectionEnd = CaretIndex;
} }
private void SetSelectionForControlDelete() private void SetSelectionForControlDelete()
{ {
SelectionStart = CaretIndex; SelectionStart = CaretIndex;
MoveHorizontal(1, true); MoveHorizontal(1, true, false);
SelectionEnd = CaretIndex; SelectionEnd = CaretIndex;
} }

10
tests/Avalonia.Animation.UnitTests/TestClock.cs

@ -5,10 +5,12 @@ namespace Avalonia.Animation.UnitTests
{ {
internal class TestClock : IClock, IDisposable internal class TestClock : IClock, IDisposable
{ {
private TimeSpan _curTime;
private IObserver<TimeSpan> _observer; private IObserver<TimeSpan> _observer;
public PlayState PlayState { get; set; } = PlayState.Run; public PlayState PlayState { get; set; } = PlayState.Run;
public void Dispose() public void Dispose()
{ {
_observer?.OnCompleted(); _observer?.OnCompleted();
@ -19,6 +21,12 @@ namespace Avalonia.Animation.UnitTests
_observer?.OnNext(time); _observer?.OnNext(time);
} }
public void Pulse(TimeSpan time)
{
_curTime += time;
_observer?.OnNext(_curTime);
}
public IDisposable Subscribe(IObserver<TimeSpan> observer) public IDisposable Subscribe(IObserver<TimeSpan> observer)
{ {
_observer = observer; _observer = observer;

88
tests/Avalonia.Animation.UnitTests/TransitionsTests.cs

@ -10,13 +10,11 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void Check_Transitions_Interpolation_Negative_Bounds_Clamp() public void Check_Transitions_Interpolation_Negative_Bounds_Clamp()
{ {
var clock = new MockGlobalClock(); var clock = new TestClock();
using (UnitTestApplication.Start(new TestServices(globalClock: clock))) var border = new Border
{ {
var border = new Border Transitions = new Transitions
{
Transitions = new Transitions
{ {
new DoubleTransition new DoubleTransition
{ {
@ -24,27 +22,25 @@ namespace Avalonia.Animation.UnitTests
Property = Border.OpacityProperty, Property = Border.OpacityProperty,
} }
} }
}; };
border.Opacity = 0; border.Opacity = 0;
clock.Pulse(TimeSpan.FromSeconds(0)); clock.Pulse(TimeSpan.FromSeconds(0));
clock.Pulse(TimeSpan.FromSeconds(-0.5)); clock.Pulse(TimeSpan.FromSeconds(-0.5));
Assert.Equal(0, border.Opacity);
Assert.Equal(0, border.Opacity);
}
} }
[Fact] [Fact]
public void Check_Transitions_Interpolation_Positive_Bounds_Clamp() public void Check_Transitions_Interpolation_Positive_Bounds_Clamp()
{ {
var clock = new MockGlobalClock(); var clock = new TestClock();
using (UnitTestApplication.Start(new TestServices(globalClock: clock))) var border = new Border
{ {
var border = new Border Transitions = new Transitions
{
Transitions = new Transitions
{ {
new DoubleTransition new DoubleTransition
{ {
@ -52,34 +48,62 @@ namespace Avalonia.Animation.UnitTests
Property = Border.OpacityProperty, Property = Border.OpacityProperty,
} }
} }
}; };
border.Opacity = 0; border.Opacity = 0;
clock.Pulse(TimeSpan.FromSeconds(0)); clock.Pulse(TimeSpan.FromSeconds(0));
clock.Pulse(TimeSpan.FromMilliseconds(1001)); clock.Pulse(TimeSpan.FromMilliseconds(1001));
Assert.Equal(0, border.Opacity);
Assert.Equal(0, border.Opacity);
}
} }
[Fact] [Fact]
public void TransitionInstance_With_Zero_Duration_Is_Completed_On_First_Tick() public void TransitionInstance_With_Zero_Duration_Is_Completed_On_First_Tick()
{ {
var clock = new MockGlobalClock(); var clock = new TestClock();
using (UnitTestApplication.Start(new TestServices(globalClock: clock))) int i = 0;
var inst = new TransitionInstance(clock, TimeSpan.Zero, TimeSpan.Zero).Subscribe(nextValue =>
{ {
int i = 0; switch (i++)
var inst = new TransitionInstance(clock, TimeSpan.Zero).Subscribe(nextValue =>
{ {
switch (i++) case 0: Assert.Equal(0, nextValue); break;
{ case 1: Assert.Equal(1d, nextValue); break;
case 0: Assert.Equal(0, nextValue); break; }
case 1: Assert.Equal(1d, nextValue); break; });
}
}); clock.Pulse(TimeSpan.FromMilliseconds(10));
}
[Fact]
public void TransitionInstance_Properly_Calculates_Delay_And_Duration_Values()
{
var clock = new TestClock();
int i = -1;
var inst = new TransitionInstance(clock, TimeSpan.FromMilliseconds(30), TimeSpan.FromMilliseconds(70)).Subscribe(nextValue =>
{
switch (i++)
{
case 0: Assert.Equal(0, nextValue); break;
case 1: Assert.Equal(0, nextValue); break;
case 2: Assert.Equal(0, nextValue); break;
case 3: Assert.Equal(0, nextValue); break;
case 4: Assert.Equal(Math.Round(10d / 70d, 4), Math.Round(nextValue, 4)); break;
case 5: Assert.Equal(Math.Round(20d / 70d, 4), Math.Round(nextValue, 4)); break;
case 6: Assert.Equal(Math.Round(30d / 70d, 4), Math.Round(nextValue, 4)); break;
case 7: Assert.Equal(Math.Round(40d / 70d, 4), Math.Round(nextValue, 4)); break;
case 8: Assert.Equal(Math.Round(50d / 70d, 4), Math.Round(nextValue, 4)); break;
case 9: Assert.Equal(Math.Round(60d / 70d, 4), Math.Round(nextValue, 4)); break;
case 10: Assert.Equal(1d, nextValue); break;
}
});
for (int z = 0; z <= 10; z++)
{
clock.Pulse(TimeSpan.FromMilliseconds(10)); clock.Pulse(TimeSpan.FromMilliseconds(10));
} }
} }

Loading…
Cancel
Save