Browse Source

Merge pull request #4 from AvaloniaUI/master

Merge
pull/1874/head
William David Cossey 8 years ago
committed by GitHub
parent
commit
4b7702dbb7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      samples/ControlCatalog/ControlCatalog.csproj
  2. 3
      samples/ControlCatalog/MainView.xaml
  3. 13
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  4. 25
      samples/ControlCatalog/Pages/ListBoxPage.xaml.cs
  5. 4
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  6. 8
      samples/ControlCatalog/Pages/ScreenPage.cs
  7. 2
      samples/ControlCatalog/SideBar.xaml
  8. 2
      samples/RenderDemo/SideBar.xaml
  9. 10
      samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
  10. 6
      src/Android/Avalonia.Android/AndroidPlatform.cs
  11. 10
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  12. 14
      src/Android/Avalonia.Android/AppBuilder.cs
  13. 9
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  14. 9
      src/Android/Avalonia.Android/AvaloniaView.cs
  15. 2
      src/Android/Avalonia.Android/CursorFactory.cs
  16. 4
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  17. 5
      src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
  18. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  19. 10
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  20. 9
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  21. 13
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  22. 8
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  23. 6
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  24. 9
      src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs
  25. 15
      src/Android/Avalonia.Android/PlatformIconLoader.cs
  26. 2
      src/Android/Avalonia.Android/SystemDialogImpl.cs
  27. 53
      src/Avalonia.Animation/Animatable.cs
  28. 98
      src/Avalonia.Animation/Animation.cs
  29. 228
      src/Avalonia.Animation/AnimationInstance`1.cs
  30. 5
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  31. 273
      src/Avalonia.Animation/AnimatorStateMachine`1.cs
  32. 101
      src/Avalonia.Animation/Animator`1.cs
  33. 7
      src/Avalonia.Animation/Cue.cs
  34. 11
      src/Avalonia.Animation/DoubleAnimator.cs
  35. 1
      src/Avalonia.Animation/DoubleTransition.cs
  36. 1
      src/Avalonia.Animation/Easing/BounceEaseIn.cs
  37. 5
      src/Avalonia.Animation/Easing/Easing.cs
  38. 3
      src/Avalonia.Animation/Easing/EasingTypeConverter.cs
  39. 2
      src/Avalonia.Animation/Easing/ElasticEaseIn.cs
  40. 2
      src/Avalonia.Animation/Easing/ElasticEaseOut.cs
  41. 2
      src/Avalonia.Animation/Easing/SineEaseIn.cs
  42. 2
      src/Avalonia.Animation/Easing/SineEaseOut.cs
  43. 6
      src/Avalonia.Animation/FillMode.cs
  44. 1
      src/Avalonia.Animation/FloatTransition.cs
  45. 2
      src/Avalonia.Animation/IAnimation.cs
  46. 1
      src/Avalonia.Animation/IAnimator.cs
  47. 2
      src/Avalonia.Animation/ITransition.cs
  48. 1
      src/Avalonia.Animation/IntegerTransition.cs
  49. 3
      src/Avalonia.Animation/KeyFrame.cs
  50. 11
      src/Avalonia.Animation/KeyFramePair`1.cs
  51. 8
      src/Avalonia.Animation/PlayState.cs
  52. 6
      src/Avalonia.Animation/PlaybackDirection.cs
  53. 5
      src/Avalonia.Animation/RepeatCount.cs
  54. 3
      src/Avalonia.Animation/RepeatCountTypeConverter.cs
  55. 81
      src/Avalonia.Animation/Timing.cs
  56. 54
      src/Avalonia.Animation/TransitionInstance.cs
  57. 22
      src/Avalonia.Animation/Transition`1.cs
  58. 18
      src/Avalonia.Animation/Utils/DoubleUtils.cs
  59. 2
      src/Avalonia.Animation/Utils/EasingUtils.cs
  60. 1
      src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs
  61. 14
      src/Avalonia.Base/Threading/Dispatcher.cs
  62. 18
      src/Avalonia.Base/Threading/IDispatcher.cs
  63. 26
      src/Avalonia.Controls/Canvas.cs
  64. 2
      src/Avalonia.Controls/ContentControl.cs
  65. 32
      src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
  66. 4
      src/Avalonia.Controls/DockPanel.cs
  67. 5
      src/Avalonia.Controls/Grid.cs
  68. 42
      src/Avalonia.Controls/Panel.cs
  69. 2
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  70. 8
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  71. 8
      src/Avalonia.Controls/Slider.cs
  72. 42
      src/Avalonia.Controls/TreeView.cs
  73. 36
      src/Avalonia.Controls/TreeViewItem.cs
  74. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  75. 5
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  76. 2
      src/Avalonia.Diagnostics/Debug.cs
  77. 1
      src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs
  78. 3
      src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs
  79. 2
      src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs
  80. 1
      src/Avalonia.Diagnostics/Views/SimpleGrid.cs
  81. 9
      src/Avalonia.DotNetCoreRuntime/AppBuilder.cs
  82. 4
      src/Avalonia.DotNetCoreRuntime/RuntimeInfo.cs
  83. 2
      src/Avalonia.Input/AccessKeyHandler.cs
  84. 6
      src/Avalonia.Input/Cursors.cs
  85. 4
      src/Avalonia.Input/DataObject.cs
  86. 4
      src/Avalonia.Input/DragDrop.cs
  87. 1
      src/Avalonia.Input/ICustomKeyboardNavigation.cs
  88. 4
      src/Avalonia.Input/IDataObject.cs
  89. 8
      src/Avalonia.Input/IInputManager.cs
  90. 2
      src/Avalonia.Input/IPointerDevice.cs
  91. 1
      src/Avalonia.Input/InputElement.cs
  92. 4
      src/Avalonia.Input/InputExtensions.cs
  93. 2
      src/Avalonia.Input/InputManager.cs
  94. 2
      src/Avalonia.Input/Key.cs
  95. 5
      src/Avalonia.Input/KeyBinding.cs
  96. 1
      src/Avalonia.Input/KeyEventArgs.cs
  97. 6
      src/Avalonia.Input/KeyGesture.cs
  98. 3
      src/Avalonia.Input/KeyboardDevice.cs
  99. 2
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  100. 5
      src/Avalonia.Input/MouseDevice.cs

2
samples/ControlCatalog/ControlCatalog.csproj

@ -35,4 +35,4 @@
</ItemGroup>
<Import Project="..\..\build\Serilog.props" />
</Project>
</Project>

3
samples/ControlCatalog/MainView.xaml

@ -20,8 +20,9 @@
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="ListBox"><pages:ListBoxPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>

13
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -0,0 +1,13 @@
<UserControl xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ListBox</TextBlock>
<TextBlock Classes="h2">Hosts a collection of ListBoxItem.</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<ListBox Items="{Binding}" Width="250" Height="350"></ListBox>
</StackPanel>
</StackPanel>
</UserControl>

25
samples/ControlCatalog/Pages/ListBoxPage.xaml.cs

@ -0,0 +1,25 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class ListBoxPage : UserControl
{
public ListBoxPage()
{
this.InitializeComponent();
DataContext = Enumerable.Range(1, 10).Select(i => $"Item {i}" )
.ToArray();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

4
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -14,7 +14,7 @@
<CheckBox Grid.Row="1" Grid.Column="1" IsChecked="{Binding #upDown.IsReadOnly}" VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">AllowSpin:</TextBlock>
<CheckBox Grid.Row="2" Grid.Column="1" IsChecked="{Binding #upDown.AllowSpin}" IsEnabled="{Binding #upDown.!IsReadOnly}" VerticalAlignment="Center" Margin="2"/>
<CheckBox Grid.Row="2" Grid.Column="1" IsChecked="{Binding #upDown.AllowSpin}" VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">ClipValueToMinMax:</TextBlock>
<CheckBox Grid.Row="3" Grid.Column="1" IsChecked="{Binding #upDown.ClipValueToMinMax}" VerticalAlignment="Center" Margin="2"/>
@ -77,4 +77,4 @@
</StackPanel>
</StackPanel>
</UserControl>
</UserControl>

8
samples/ControlCatalog/Pages/ScreenPage.cs

@ -42,7 +42,11 @@ namespace ControlCatalog.Pages
context.DrawRectangle(p, boundsRect);
context.DrawRectangle(p, workingAreaRect);
FormattedText text = new FormattedText();
FormattedText text = new FormattedText()
{
Typeface = Typeface.Default
};
text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height), text);
@ -59,4 +63,4 @@ namespace ControlCatalog.Pages
context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));
}
}
}
}

2
samples/ControlCatalog/SideBar.xaml

@ -36,7 +36,7 @@
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
<DoubleTransition Property="Opacity" Duration="0:0:0.2"/>
</Transitions>
</Setter>
</Style>

2
samples/RenderDemo/SideBar.xaml

@ -37,7 +37,7 @@
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
<DoubleTransition Property="Opacity" Duration="0:0:0.2"/>
</Transitions>
</Setter>
</Style>

10
samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs

@ -10,21 +10,21 @@ namespace RenderDemo.ViewModels
public AnimationsPageViewModel()
{
ToggleGlobalPlayState = ReactiveCommand.Create(()=>TogglePlayState());
ToggleGlobalPlayState = ReactiveCommand.Create(() => TogglePlayState());
}
void TogglePlayState()
{
switch (Timing.GetGlobalPlayState())
switch (Animation.GlobalPlayState)
{
case PlayState.Run:
PlayStateText = "Resume all animations";
Timing.SetGlobalPlayState(PlayState.Pause);
Animation.GlobalPlayState = PlayState.Pause;
break;
case PlayState.Pause:
PlayStateText = "Pause all animations";
Timing.SetGlobalPlayState(PlayState.Run);
Animation.GlobalPlayState = PlayState.Run;
break;
}
}
@ -36,5 +36,5 @@ namespace RenderDemo.ViewModels
}
public ReactiveCommand ToggleGlobalPlayState { get; }
}
}
}

6
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -1,8 +1,4 @@
using System;
using System.IO;
using System.Linq;
using Android.Content;
using Android.Views;
using Avalonia.Android.Platform;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.SkiaPlatform;
@ -79,4 +75,4 @@ namespace Avalonia.Android
return new PopupImpl();
}
}
}
}

10
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@ -1,16 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables;
using System.Text;
using System.Threading;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Platform;
using Avalonia.Threading;
@ -88,4 +79,3 @@ namespace Avalonia.Android
public event Action<DispatcherPriority?> Signaled;
}
}

14
src/Android/Avalonia.Android/AppBuilder.cs

@ -1,16 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Shared.PlatformSupport;
namespace Avalonia
@ -23,4 +11,4 @@ namespace Avalonia
}
}
}
}

9
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -1,14 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace Avalonia.Android
{
@ -48,4 +41,4 @@ namespace Avalonia.Android
return View.DispatchKeyEvent(e);
}
}
}
}

9
src/Android/Avalonia.Android/AvaloniaView.cs

@ -1,12 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Android.Platform.SkiaPlatform;
@ -66,4 +59,4 @@ namespace Avalonia.Android
public IDisposable ShowDialog() => null;
}
}
}
}

2
src/Android/Avalonia.Android/CursorFactory.cs

@ -9,4 +9,4 @@ namespace Avalonia.Android
public IPlatformHandle GetCursor(StandardCursorType cursorType)
=> new PlatformHandle(IntPtr.Zero, "ZeroCursor");
}
}
}

4
src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

@ -1,9 +1,9 @@
using System.Threading.Tasks;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Avalonia.Input.Platform;
using Avalonia.Platform;
using System.Threading.Tasks;
namespace Avalonia.Android.Platform
{
@ -44,4 +44,4 @@ namespace Avalonia.Android.Platform
return Task.FromResult<object>(null);
}
}
}
}

5
src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs

@ -1,12 +1,11 @@
using System;
using Avalonia.Input;
//using Android.InputMethodServices;
using System.Collections.Generic;
using Android.Views;
using Avalonia.Input;
namespace Avalonia.Android.Platform.Input
{
public class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
public class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
private static readonly Dictionary<Keycode, Key> KeyDic = new Dictionary<Keycode, Key>
{
// { Keycode.Cancel?, Key.Cancel },

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@ -102,4 +102,4 @@ namespace Avalonia.Android.Platform.SkiaPlatform
uint reserved6;
}
}
}
}

10
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@ -1,17 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Android
@ -90,4 +82,4 @@ namespace Avalonia.Android
protected abstract void Draw();
public string HandleDescriptor => "SurfaceView";
}
}
}

9
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@ -1,15 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Controls;
using Avalonia.Platform;
@ -116,4 +109,4 @@ namespace Avalonia.Android.Platform.SkiaPlatform
//Not supported
}
}
}
}

13
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -1,18 +1,15 @@
using Android.App;
using System;
using System.Collections.Generic;
using Android.Content;
using Android.Graphics;
using Android.Views;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Android.Platform.Input;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform
@ -196,4 +193,4 @@ namespace Avalonia.Android.Platform.SkiaPlatform
ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(_view.Holder.Surface);
}
}
}

8
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -1,3 +1,5 @@
using System;
using System.ComponentModel;
using Android.Content;
using Android.Runtime;
using Android.Views;
@ -7,8 +9,6 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using System;
using System.ComponentModel;
namespace Avalonia.Android.Platform.Specific.Helpers
{
@ -120,7 +120,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
_lastFocusedElement = element;
}
public void ActivateAutoShowKeybord()
public void ActivateAutoShowKeyboard()
{
var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged);
@ -142,4 +142,4 @@ namespace Avalonia.Android.Platform.Specific.Helpers
HandleEvents = false;
}
}
}
}

6
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@ -1,10 +1,8 @@
using Android.Graphics;
using System;
using Android.Views;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Media;
using Avalonia.Platform;
using System;
namespace Avalonia.Android.Platform.Specific.Helpers
{
@ -131,4 +129,4 @@ namespace Avalonia.Android.Platform.Specific.Helpers
HandleEvents = false;
}
}
}
}

9
src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs

@ -1,16 +1,9 @@
using Android.Graphics;
using Android.Views;
using System;
using System.Collections.Generic;
namespace Avalonia.Android.Platform.Specific
{
public interface IAndroidView
{
View View { get; }
}
}
}

15
src/Android/Avalonia.Android/PlatformIconLoader.cs

@ -1,16 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Platform;
using System.IO;
using Avalonia.Platform;
namespace Avalonia.Android
{
@ -54,4 +43,4 @@ namespace Avalonia.Android
stream.CopyTo(outputStream);
}
}
}
}

2
src/Android/Avalonia.Android/SystemDialogImpl.cs

@ -18,4 +18,4 @@ namespace Avalonia.Android
throw new NotImplementedException();
}
}
}
}

53
src/Avalonia.Animation/Animatable.cs

@ -1,48 +1,20 @@
// 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.Linq;
using Avalonia.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Animation;
using System.Collections.Generic;
using System.Threading;
using System.Collections.Concurrent;
using Avalonia.Data;
namespace Avalonia.Animation
{
/// <summary>
/// Base class for control which can have property transitions.
/// Base class for all animatable objects.
/// </summary>
public class Animatable : AvaloniaObject
{
/// <summary>
/// Initializes this <see cref="Animatable"/> object.
/// </summary>
public Animatable()
{
Transitions = new Transitions();
AnimatableTimer = Timing.AnimationStateTimer
.Select(p =>
{
if (this._playState == PlayState.Pause)
{
return PlayState.Pause;
}
else return p;
})
.Publish()
.RefCount();
}
/// <summary>
/// The specific animations timer for this control.
/// </summary>
/// <returns></returns>
public IObservable<PlayState> AnimatableTimer;
{
/// <summary>
/// Defines the <see cref="PlayState"/> property.
/// </summary>
@ -62,27 +34,25 @@ namespace Avalonia.Animation
{
get { return _playState; }
set { SetAndRaise(PlayStateProperty, ref _playState, value); }
}
/// <summary>
/// Defines the <see cref="Transitions"/> property.
/// </summary>
public static readonly DirectProperty<Animatable, IEnumerable<ITransition>> TransitionsProperty =
AvaloniaProperty.RegisterDirect<Animatable, IEnumerable<ITransition>>(
public static readonly DirectProperty<Animatable, Transitions> TransitionsProperty =
AvaloniaProperty.RegisterDirect<Animatable, Transitions>(
nameof(Transitions),
o => o.Transitions,
(o, v) => o.Transitions = v);
private IEnumerable<ITransition> _transitions = new AvaloniaList<ITransition>();
private Transitions _transitions;
/// <summary>
/// Gets or sets the property transitions for the control.
/// </summary>
public IEnumerable<ITransition> Transitions
public Transitions Transitions
{
get { return _transitions; }
get { return _transitions ?? (_transitions = new Transitions()); }
set { SetAndRaise(TransitionsProperty, ref _transitions, value); }
}
@ -103,6 +73,5 @@ namespace Avalonia.Animation
}
}
}
}
}
}

98
src/Avalonia.Animation/Animation.cs

@ -1,18 +1,14 @@
// 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 Avalonia.Animation.Easings;
using Avalonia.Animation;
using Avalonia.Collections;
using Avalonia.Metadata;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Linq;
using System.Threading.Tasks;
using System.Reactive.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Collections;
namespace Avalonia.Animation
{
@ -21,6 +17,60 @@ namespace Avalonia.Animation
/// </summary>
public class Animation : AvaloniaList<KeyFrame>, IAnimation
{
/// <summary>
/// Gets or sets the animation play state for all animations
/// </summary>
public static PlayState GlobalPlayState { get; set; } = PlayState.Run;
/// <summary>
/// Gets or sets the active time of this animation.
/// </summary>
public TimeSpan Duration { get; set; }
/// <summary>
/// Gets or sets the repeat count for this animation.
/// </summary>
public RepeatCount RepeatCount { get; set; }
/// <summary>
/// Gets or sets the playback direction for this animation.
/// </summary>
public PlaybackDirection PlaybackDirection { get; set; }
/// <summary>
/// Gets or sets the value fill mode for this animation.
/// </summary>
public FillMode FillMode { get; set; }
/// <summary>
/// Gets or sets the easing function to be used for this animation.
/// </summary>
public Easing Easing { get; set; } = new LinearEasing();
/// <summary>
/// Gets or sets the speed multiple for this animation.
/// </summary>
public double SpeedRatio { get; set; } = 1d;
/// <summary>
/// Gets or sets the delay time for this animation.
/// </summary>
/// <remarks>
/// Describes a delay to be added before the animation starts, and optionally between
/// repeats of the animation if <see cref="DelayBetweenIterations"/> is set.
/// </remarks>
public TimeSpan Delay { get; set; }
/// <summary>
/// Gets or sets a value indicating whether <see cref="Delay"/> will be applied between
/// iterations of the animation.
/// </summary>
/// <remarks>
/// If this property is not set, then <see cref="Delay"/> will only be applied to the first
/// iteration of the animation.
/// </remarks>
public bool DelayBetweenIterations { get; set; }
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
{
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
@ -44,38 +94,6 @@ namespace Avalonia.Animation
return null;
}
public AvaloniaList<IAnimator> _animators { get; set; } = new AvaloniaList<IAnimator>();
/// <summary>
/// Run time of this animation.
/// </summary>
public TimeSpan Duration { get; set; }
/// <summary>
/// Delay time for this animation.
/// </summary>
public TimeSpan Delay { get; set; }
/// <summary>
/// The repeat count for this animation.
/// </summary>
public RepeatCount RepeatCount { get; set; }
/// <summary>
/// The playback direction for this animation.
/// </summary>
public PlaybackDirection PlaybackDirection { get; set; }
/// <summary>
/// The value fill mode for this animation.
/// </summary>
public FillMode FillMode { get; set; }
/// <summary>
/// Easing function to be used.
/// </summary>
public Easing Easing { get; set; } = new LinearEasing();
private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
{
var handlerList = new List<(Type type, AvaloniaProperty property)>();

228
src/Avalonia.Animation/AnimationInstance`1.cs

@ -0,0 +1,228 @@
using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Utils;
using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia.Animation
{
/// <summary>
/// Handles interpolatoin and time-related functions
/// for keyframe animations.
/// </summary>
internal class AnimationInstance<T> : SingleSubscriberObservableBase<T>
{
private T _lastInterpValue;
private T _firstKFValue;
private long _repeatCount;
private double _currentIteration;
private bool _isLooping;
private bool _gotFirstKFValue;
private bool _gotFirstFrameCount;
private bool _iterationDelay;
private FillMode _fillMode;
private PlaybackDirection _animationDirection;
private Animator<T> _parent;
private Animatable _targetControl;
private T _neutralValue;
private double _speedRatio;
private TimeSpan _delay;
private TimeSpan _duration;
private TimeSpan _firstFrameCount;
private TimeSpan _internalClock;
private TimeSpan? _previousClock;
private Easings.Easing _easeFunc;
private Action _onCompleteAction;
private Func<double, T, T> _interpolator;
private IDisposable _timerSubscription;
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, Action OnComplete, Func<double, T, T> Interpolator)
{
if (animation.SpeedRatio <= 0)
throw new InvalidOperationException("Speed ratio cannot be negative or zero.");
if (animation.Duration.TotalSeconds <= 0)
throw new InvalidOperationException("Duration cannot be negative or zero.");
_parent = animator;
_easeFunc = animation.Easing;
_targetControl = control;
_neutralValue = (T)_targetControl.GetValue(_parent.Property);
_speedRatio = animation.SpeedRatio;
_delay = animation.Delay;
_duration = animation.Duration;
_iterationDelay = animation.DelayBetweenIterations;
switch (animation.RepeatCount.RepeatType)
{
case RepeatType.None:
_repeatCount = 1;
break;
case RepeatType.Loop:
_isLooping = true;
break;
case RepeatType.Repeat:
_repeatCount = (long)animation.RepeatCount.Value;
break;
}
_animationDirection = animation.PlaybackDirection;
_fillMode = animation.FillMode;
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
}
protected override void Unsubscribed()
{
_timerSubscription?.Dispose();
}
protected override void Subscribed()
{
_timerSubscription = Timing.AnimationsTimer
.Subscribe(p => this.Step(p));
}
public void Step(TimeSpan frameTick)
{
try
{
InternalStep(frameTick);
}
catch (Exception e)
{
PublishError(e);
}
}
private void DoComplete()
{
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
_onCompleteAction?.Invoke();
PublishCompleted();
}
private void DoDelay()
{
if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both)
if (_currentIteration == 0)
PublishNext(_firstKFValue);
else
PublishNext(_lastInterpValue);
}
private void DoPlayStatesAndTime(TimeSpan systemTime)
{
if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop)
DoComplete();
if (!_previousClock.HasValue)
{
_previousClock = systemTime;
_internalClock = TimeSpan.Zero;
}
else
{
if (Animation.GlobalPlayState == PlayState.Pause || _targetControl.PlayState == PlayState.Pause)
{
_previousClock = systemTime;
return;
}
var delta = systemTime - _previousClock;
_internalClock += delta.Value;
_previousClock = systemTime;
}
if (!_gotFirstKFValue)
{
_firstKFValue = (T)_parent.First().Value;
_gotFirstKFValue = true;
}
if (!_gotFirstFrameCount)
{
_firstFrameCount = _internalClock;
_gotFirstFrameCount = true;
}
}
private void InternalStep(TimeSpan systemTime)
{
DoPlayStatesAndTime(systemTime);
var time = _internalClock - _firstFrameCount;
var delayEndpoint = _delay;
var iterationEndpoint = delayEndpoint + _duration;
//determine if time is currently in the first iteration.
if (time >= TimeSpan.Zero & time <= iterationEndpoint)
{
_currentIteration = 1;
}
else if (time > iterationEndpoint)
{
//Subtract first iteration to properly get the subsequent iteration time
time -= iterationEndpoint;
if (!_iterationDelay & delayEndpoint > TimeSpan.Zero)
{
delayEndpoint = TimeSpan.Zero;
iterationEndpoint = _duration;
}
//Calculate the current iteration number
_currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2;
}
else
{
_previousClock = systemTime;
return;
}
time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks);
if (!_isLooping)
{
if (_currentIteration > _repeatCount)
DoComplete();
if (time > iterationEndpoint)
DoComplete();
}
// Determine if the current iteration should have its normalized time inverted.
bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false :
_animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true :
_animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false :
_animationDirection == PlaybackDirection.Reverse ? true : false;
if (delayEndpoint > TimeSpan.Zero & time < delayEndpoint)
{
DoDelay();
}
else
{
// Offset the delay time
time -= delayEndpoint;
iterationEndpoint -= delayEndpoint;
// Normalize time
var interpVal = (double)time.Ticks / iterationEndpoint.Ticks;
if (isCurIterReverse)
interpVal = 1 - interpVal;
// Ease and interpolate
var easedTime = _easeFunc.Ease(interpVal);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
PublishNext(_lastInterpValue);
}
}
}
}

5
src/Avalonia.Animation/AnimatorKeyFrame.cs

@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using Avalonia.Metadata;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Reactive;
@ -29,6 +25,7 @@ namespace Avalonia.Animation
Cue = cue;
}
internal bool isNeutral;
public Type AnimatorType { get; }
public Cue Cue { get; }
public AvaloniaProperty Property { get; private set; }

273
src/Avalonia.Animation/AnimatorStateMachine`1.cs

@ -1,273 +0,0 @@
using System;
using System.Linq;
using Avalonia.Data;
namespace Avalonia.Animation
{
/// <summary>
/// Provides statefulness for an iteration of a keyframe animation.
/// </summary>
internal class AnimatorStateMachine<T> : IObservable<object>, IDisposable
{
object _lastInterpValue;
object _firstKFValue;
private ulong _delayTotalFrameCount;
private ulong _durationTotalFrameCount;
private ulong _delayFrameCount;
private ulong _durationFrameCount;
private ulong _repeatCount;
private ulong _currentIteration;
private bool _isLooping;
private bool _isRepeating;
private bool _isReversed;
private bool _checkLoopAndRepeat;
private bool _gotFirstKFValue;
private FillMode _fillMode;
private PlaybackDirection _animationDirection;
private KeyFramesStates _currentState;
private KeyFramesStates _savedState;
private Animator<T> _parent;
private Animation _targetAnimation;
private Animatable _targetControl;
private T _neutralValue;
internal bool _unsubscribe = false;
private IObserver<object> _targetObserver;
private readonly Action _onComplete;
[Flags]
private enum KeyFramesStates
{
Initialize,
DoDelay,
DoRun,
RunForwards,
RunBackwards,
RunApplyValue,
RunComplete,
Pause,
Stop,
Disposed
}
public AnimatorStateMachine(Animation animation, Animatable control, Animator<T> animator, Action onComplete)
{
_parent = animator;
_targetAnimation = animation;
_targetControl = control;
_neutralValue = (T)_targetControl.GetValue(_parent.Property);
_delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks);
_durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks);
switch (animation.RepeatCount.RepeatType)
{
case RepeatType.Loop:
_isLooping = true;
_checkLoopAndRepeat = true;
break;
case RepeatType.Repeat:
_isRepeating = true;
_checkLoopAndRepeat = true;
_repeatCount = animation.RepeatCount.Value;
break;
}
_isReversed = (animation.PlaybackDirection & PlaybackDirection.Reverse) != 0;
_animationDirection = _targetAnimation.PlaybackDirection;
_fillMode = _targetAnimation.FillMode;
if (_durationTotalFrameCount > 0)
_currentState = KeyFramesStates.DoDelay;
else
_currentState = KeyFramesStates.DoRun;
_onComplete = onComplete;
}
public void Step(PlayState _playState, Func<double, T, T> Interpolator)
{
try
{
InternalStep(_playState, Interpolator);
}
catch (Exception e)
{
_targetObserver?.OnError(e);
}
}
private void InternalStep(PlayState _playState, Func<double, T, T> Interpolator)
{
if (!_gotFirstKFValue)
{
_firstKFValue = _parent.First().Value;
_gotFirstKFValue = true;
}
if (_currentState == KeyFramesStates.Disposed)
throw new InvalidProgramException("This KeyFrames Animation is already disposed.");
if (_playState == PlayState.Stop)
_currentState = KeyFramesStates.Stop;
// Save state and pause the machine
if (_playState == PlayState.Pause && _currentState != KeyFramesStates.Pause)
{
_savedState = _currentState;
_currentState = KeyFramesStates.Pause;
}
// Resume the previous state
if (_playState != PlayState.Pause && _currentState == KeyFramesStates.Pause)
_currentState = _savedState;
double _tempDuration = 0d, _easedTime;
bool handled = false;
while (!handled)
{
switch (_currentState)
{
case KeyFramesStates.DoDelay:
if (_fillMode == FillMode.Backward
|| _fillMode == FillMode.Both)
{
if (_currentIteration == 0)
{
_targetObserver.OnNext(_firstKFValue);
}
else
{
_targetObserver.OnNext(_lastInterpValue);
}
}
if (_delayFrameCount > _delayTotalFrameCount)
{
_currentState = KeyFramesStates.DoRun;
}
else
{
handled = true;
_delayFrameCount++;
}
break;
case KeyFramesStates.DoRun:
if (_isReversed)
_currentState = KeyFramesStates.RunBackwards;
else
_currentState = KeyFramesStates.RunForwards;
break;
case KeyFramesStates.RunForwards:
if (_durationFrameCount > _durationTotalFrameCount)
{
_currentState = KeyFramesStates.RunComplete;
}
else
{
_tempDuration = (double)_durationFrameCount / _durationTotalFrameCount;
_currentState = KeyFramesStates.RunApplyValue;
}
break;
case KeyFramesStates.RunBackwards:
if (_durationFrameCount > _durationTotalFrameCount)
{
_currentState = KeyFramesStates.RunComplete;
}
else
{
_tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount;
_currentState = KeyFramesStates.RunApplyValue;
}
break;
case KeyFramesStates.RunApplyValue:
_easedTime = _targetAnimation.Easing.Ease(_tempDuration);
_durationFrameCount++;
_lastInterpValue = Interpolator(_easedTime, _neutralValue);
_targetObserver.OnNext(_lastInterpValue);
_currentState = KeyFramesStates.DoRun;
handled = true;
break;
case KeyFramesStates.RunComplete:
if (_checkLoopAndRepeat)
{
_delayFrameCount = 0;
_durationFrameCount = 0;
if (_isLooping)
{
_currentState = KeyFramesStates.DoRun;
}
else if (_isRepeating)
{
if (_currentIteration >= _repeatCount)
{
_currentState = KeyFramesStates.Stop;
}
else
{
_currentState = KeyFramesStates.DoRun;
}
_currentIteration++;
}
if (_animationDirection == PlaybackDirection.Alternate
|| _animationDirection == PlaybackDirection.AlternateReverse)
_isReversed = !_isReversed;
break;
}
_currentState = KeyFramesStates.Stop;
break;
case KeyFramesStates.Stop:
if (_fillMode == FillMode.Forward
|| _fillMode == FillMode.Both)
{
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
}
_targetObserver.OnCompleted();
_onComplete?.Invoke();
Dispose();
handled = true;
break;
default:
handled = true;
break;
}
}
}
public IDisposable Subscribe(IObserver<object> observer)
{
_targetObserver = observer;
return this;
}
public void Dispose()
{
_unsubscribe = true;
_currentState = KeyFramesStates.Disposed;
}
}
}

101
src/Avalonia.Animation/Animator`1.cs

@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Collections;
using System.ComponentModel;
using Avalonia.Animation.Utils;
using System.Reactive.Linq;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Utils;
using Avalonia.Collections;
using Avalonia.Data;
using System.Reactive.Disposables;
namespace Avalonia.Animation
{
@ -19,9 +16,9 @@ namespace Avalonia.Animation
/// <summary>
/// List of type-converted keyframes.
/// </summary>
private readonly SortedList<double, (AnimatorKeyFrame, bool isNeutral)> _convertedKeyframes = new SortedList<double, (AnimatorKeyFrame, bool)>();
private bool _isVerfifiedAndConverted;
private readonly List<AnimatorKeyFrame> _convertedKeyframes = new List<AnimatorKeyFrame>();
private bool _isVerifiedAndConverted;
/// <summary>
/// Gets or sets the target property for the keyframe.
@ -31,18 +28,17 @@ namespace Avalonia.Animation
public Animator()
{
// Invalidate keyframes when changed.
this.CollectionChanged += delegate { _isVerfifiedAndConverted = false; };
this.CollectionChanged += delegate { _isVerifiedAndConverted = false; };
}
/// <inheritdoc/>
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch, Action onComplete)
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> match, Action onComplete)
{
if (!_isVerfifiedAndConverted)
if (!_isVerifiedAndConverted)
VerifyConvertKeyFrames();
return obsMatch
// Ignore triggers when global timers are paused.
.Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause)
return match
.Where(p => p)
.Subscribe(_ =>
{
var timerObs = RunKeyFrames(animation, control, onComplete);
@ -59,53 +55,56 @@ namespace Avalonia.Animation
/// <param name="t">The time parameter, relative to the total animation time</param>
protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t)
{
KeyValuePair<double, (AnimatorKeyFrame frame, bool isNeutral)> firstCue, lastCue;
AnimatorKeyFrame firstCue, lastCue ;
int kvCount = _convertedKeyframes.Count;
if (kvCount > 2)
{
if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0)
if (t <= 0.0)
{
firstCue = _convertedKeyframes.First();
lastCue = _convertedKeyframes.Skip(1).First();
firstCue = _convertedKeyframes[0];
lastCue = _convertedKeyframes[1];
}
else if (DoubleUtils.AboutEqual(t, 1.0) || t > 1.0)
else if (t >= 1.0)
{
firstCue = _convertedKeyframes.Skip(kvCount - 2).First();
lastCue = _convertedKeyframes.Last();
firstCue = _convertedKeyframes[_convertedKeyframes.Count - 2];
lastCue = _convertedKeyframes[_convertedKeyframes.Count - 1];
}
else
{
firstCue = _convertedKeyframes.Last(j => j.Key <= t);
lastCue = _convertedKeyframes.First(j => j.Key >= t);
(double time, int index) maxval = (0.0d, 0);
for (int i = 0; i < _convertedKeyframes.Count; i++)
{
var comp = _convertedKeyframes[i].Cue.CueValue;
if (t >= comp)
{
maxval = (comp, i);
}
}
firstCue = _convertedKeyframes[maxval.index];
lastCue = _convertedKeyframes[maxval.index + 1];
}
}
else
{
firstCue = _convertedKeyframes.First();
lastCue = _convertedKeyframes.Last();
firstCue = _convertedKeyframes[0];
lastCue = _convertedKeyframes[1];
}
double t0 = firstCue.Key;
double t1 = lastCue.Key;
double t0 = firstCue.Cue.CueValue;
double t1 = lastCue.Cue.CueValue;
var intraframeTime = (t - t0) / (t1 - t0);
var firstFrameData = (firstCue.Value.frame.GetTypedValue<T>(), firstCue.Value.isNeutral);
var lastFrameData = (lastCue.Value.frame.GetTypedValue<T>(), lastCue.Value.isNeutral);
var firstFrameData = (firstCue.GetTypedValue<T>(), firstCue.isNeutral);
var lastFrameData = (lastCue.GetTypedValue<T>(), lastCue.isNeutral);
return (intraframeTime, new KeyFramePair<T>(firstFrameData, lastFrameData));
}
/// <summary>
/// Runs the KeyFrames Animation.
/// </summary>
private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete)
{
var stateMachine = new AnimatorStateMachine<T>(animation, control, this, onComplete);
Timing.AnimationStateTimer
.TakeWhile(_ => !stateMachine._unsubscribe)
.Subscribe(p => stateMachine.Step(p, DoInterpolation));
return control.Bind(Property, stateMachine, BindingPriority.Animation);
var instance = new AnimationInstance<T>(animation, control, this, onComplete, DoInterpolation);
return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
}
/// <summary>
@ -114,18 +113,26 @@ namespace Avalonia.Animation
protected abstract T DoInterpolation(double time, T neutralValue);
/// <summary>
/// Verifies and converts keyframe values according to this class's target type.
/// Verifies, converts and sorts keyframe values according to this class's target type.
/// </summary>
private void VerifyConvertKeyFrames()
{
foreach (AnimatorKeyFrame keyframe in this)
{
_convertedKeyframes.Add(keyframe.Cue.CueValue, (keyframe, false));
_convertedKeyframes.Add(keyframe);
}
AddNeutralKeyFramesIfNeeded();
_isVerfifiedAndConverted = true;
var copy = _convertedKeyframes.ToList().OrderBy(p => p.Cue.CueValue);
_convertedKeyframes.Clear();
foreach (AnimatorKeyFrame keyframe in copy)
{
_convertedKeyframes.Add(keyframe);
}
_isVerifiedAndConverted = true;
}
private void AddNeutralKeyFramesIfNeeded()
@ -133,14 +140,14 @@ namespace Avalonia.Animation
bool hasStartKey, hasEndKey;
hasStartKey = hasEndKey = false;
// Make start and end keyframe mandatory.
foreach (var converted in _convertedKeyframes.Keys)
// Check if there's start and end keyframes.
foreach (var frame in _convertedKeyframes)
{
if (DoubleUtils.AboutEqual(converted, 0.0))
if (frame.Cue.CueValue == 0.0d)
{
hasStartKey = true;
}
else if (DoubleUtils.AboutEqual(converted, 1.0))
else if (frame.Cue.CueValue == 1.0d)
{
hasEndKey = true;
}
@ -154,12 +161,12 @@ namespace Avalonia.Animation
{
if (!hasStartKey)
{
_convertedKeyframes.Add(0.0d, (new AnimatorKeyFrame { Value = default(T) }, true));
_convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
}
if (!hasEndKey)
{
_convertedKeyframes.Add(1.0d, (new AnimatorKeyFrame { Value = default(T) }, true));
_convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(1.0d)) { Value = default(T), isNeutral = true });
}
}
}

7
src/Avalonia.Animation/Cue.cs

@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
namespace Avalonia.Animation
{
/// <summary>
/// A Cue object for <see cref="KeyFrame"/>.
/// Determines the time index for a <see cref="KeyFrame"/>.
/// </summary>
[TypeConverter(typeof(CueTypeConverter))]
public readonly struct Cue : IEquatable<Cue>, IEquatable<double>
@ -84,5 +82,4 @@ namespace Avalonia.Animation
return Cue.Parse((string)value, culture);
}
}
}
}

11
src/Avalonia.Animation/DoubleAnimator.cs

@ -1,13 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reactive.Linq;
using System.Diagnostics;
using Avalonia.Animation.Utils;
using Avalonia.Data;
namespace Avalonia.Animation
namespace Avalonia.Animation
{
/// <summary>
/// Animator that handles <see cref="double"/> properties.

1
src/Avalonia.Animation/DoubleTransition.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Metadata;
using System;
using System.Reactive.Linq;

1
src/Avalonia.Animation/Easing/BounceEaseIn.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings

5
src/Avalonia.Animation/Easing/Easing.cs

@ -1,10 +1,7 @@
using Avalonia.Collections;
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Linq;
using System.ComponentModel;
using System.Linq;
namespace Avalonia.Animation.Easings
{

3
src/Avalonia.Animation/Easing/EasingTypeConverter.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Animation.Easings;
using System;
using System.ComponentModel;
using System.Globalization;
@ -20,4 +19,4 @@ namespace Avalonia.Animation.Easings
return Easing.Parse((string)value);
}
}
}
}

2
src/Avalonia.Animation/Easing/ElasticEaseIn.cs

@ -1,8 +1,8 @@
// 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 Avalonia.Animation.Utils;
using System;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings
{

2
src/Avalonia.Animation/Easing/ElasticEaseOut.cs

@ -1,8 +1,8 @@
// 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 Avalonia.Animation.Utils;
using System;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings
{

2
src/Avalonia.Animation/Easing/SineEaseIn.cs

@ -1,8 +1,8 @@
// 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 Avalonia.Animation.Utils;
using System;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings
{

2
src/Avalonia.Animation/Easing/SineEaseOut.cs

@ -1,8 +1,8 @@
// 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 Avalonia.Animation.Utils;
using System;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings
{

6
src/Avalonia.Animation/FillMode.cs

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Animation
namespace Avalonia.Animation
{
public enum FillMode
{

1
src/Avalonia.Animation/FloatTransition.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Metadata;
using System;
using System.Reactive.Linq;

2
src/Avalonia.Animation/IAnimation.cs

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Animation

1
src/Avalonia.Animation/IAnimator.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Animation
{

2
src/Avalonia.Animation/ITransition.cs

@ -1,9 +1,7 @@
// 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 Avalonia.Metadata;
using System;
using System.Reactive.Linq;
namespace Avalonia.Animation
{

1
src/Avalonia.Animation/IntegerTransition.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Metadata;
using System;
using System.Reactive.Linq;

3
src/Avalonia.Animation/KeyFrame.cs

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using Avalonia.Metadata;
using Avalonia.Collections;
namespace Avalonia.Animation

11
src/Avalonia.Animation/KeyFramePair`1.cs

@ -1,14 +1,3 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Collections;
using System.ComponentModel;
using Avalonia.Animation.Utils;
using System.Reactive.Linq;
using System.Linq;
using Avalonia.Data;
using System.Reactive.Disposables;
namespace Avalonia.Animation
{
/// <summary>

8
src/Avalonia.Animation/PlayState.cs

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Animation
namespace Avalonia.Animation
{
/// <summary>
/// Determines the playback state of an animation.
@ -24,4 +20,4 @@ namespace Avalonia.Animation
/// </summary>
Stop
}
}
}

6
src/Avalonia.Animation/PlaybackDirection.cs

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Animation
namespace Avalonia.Animation
{
/// <summary>
/// Determines the playback direction of an animation.

5
src/Avalonia.Animation/RepeatCount.cs

@ -1,12 +1,9 @@
// 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 Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
namespace Avalonia.Animation
{
@ -199,4 +196,4 @@ namespace Avalonia.Animation
}
}
}
}
}

3
src/Avalonia.Animation/RepeatCountTypeConverter.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Animation.Easings;
using System;
using System.ComponentModel;
using System.Globalization;
@ -20,4 +19,4 @@ namespace Avalonia.Animation
return RepeatCount.Parse((string)value);
}
}
}
}

81
src/Avalonia.Animation/Timing.cs

@ -2,10 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.Animation
@ -15,9 +13,6 @@ namespace Avalonia.Animation
/// </summary>
public static class Timing
{
static ulong _transitionsFrameCount;
static PlayState _globalState = PlayState.Run;
/// <summary>
/// The number of frames per second.
/// </summary>
@ -32,41 +27,16 @@ namespace Avalonia.Animation
/// Initializes static members of the <see cref="Timing"/> class.
/// </summary>
static Timing()
{
{
var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance);
AnimationStateTimer = globalTimer
.Select(_ =>
{
return _globalState;
})
AnimationsTimer = globalTimer
.Select(_ => GetTickCount())
.Publish()
.RefCount();
TransitionsTimer = globalTimer
.Select(p => _transitionsFrameCount++)
.Publish()
.RefCount();
}
/// <summary>
/// Sets the animation play state for all animations
/// </summary>
public static void SetGlobalPlayState(PlayState playState)
{
Dispatcher.UIThread.VerifyAccess();
_globalState = playState;
}
/// <summary>
/// Gets the animation play state for all animations
/// </summary>
public static PlayState GetGlobalPlayState()
{
Dispatcher.UIThread.VerifyAccess();
return _globalState;
}
internal static TimeSpan GetTickCount() => TimeSpan.FromMilliseconds(Environment.TickCount);
/// <summary>
/// Gets the animation timer.
@ -76,48 +46,9 @@ namespace Avalonia.Animation
/// defined in <see cref="FramesPerSecond"/>.
/// The parameter passed to a subsciber is the current playstate of the animation.
/// </remarks>
internal static IObservable<PlayState> AnimationStateTimer
internal static IObservable<TimeSpan> AnimationsTimer
{
get;
}
/// <summary>
/// Gets the transitions timer.
/// </summary>
/// <remarks>
/// The transitions timer increments usually 60 times per second as
/// defined in <see cref="FramesPerSecond"/>.
/// The parameter passed to a subsciber is the number of frames since the animation system was
/// initialized.
/// </remarks>
public static IObservable<ulong> TransitionsTimer
{
get;
}
/// <summary>
/// Gets a timer that fires every frame for the specified duration with delay.
/// </summary>
/// <returns>
/// An observable that notifies the subscriber of the progress along the transition.
/// </returns>
/// <remarks>
/// The parameter passed to the subscriber is the progress along the transition, with
/// 0 being the start and 1 being the end. The observable is guaranteed to fire 0
/// immediately on subscribe and 1 at the end of the duration.
/// </remarks>
public static IObservable<double> GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan))
{
var startTime = _transitionsFrameCount;
var _duration = (ulong)(duration.Ticks / FrameTick.Ticks);
var endTime = startTime + _duration;
return TransitionsTimer
.TakeWhile(x => x < endTime)
.Select(x => (double)(x - startTime) / _duration)
.StartWith(0.0)
.Concat(Observable.Return(1.0));
}
}
}
}

54
src/Avalonia.Animation/TransitionInstance.cs

@ -0,0 +1,54 @@
// 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 Avalonia.Metadata;
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Easings;
using Avalonia.Animation.Utils;
using Avalonia.Reactive;
namespace Avalonia.Animation
{
/// <summary>
/// Handles the timing and lifetime of a <see cref="Transition{T}"/>.
/// </summary>
internal class TransitionInstance : SingleSubscriberObservableBase<double>
{
private IDisposable timerSubscription;
private TimeSpan startTime;
private TimeSpan duration;
public TransitionInstance(TimeSpan Duration)
{
duration = Duration;
}
private void TimerTick(TimeSpan t)
{
var interpVal = (double)(t.Ticks - startTime.Ticks) / duration.Ticks;
if (interpVal > 1d
|| interpVal < 0d)
{
PublishCompleted();
return;
}
PublishNext(interpVal);
}
protected override void Unsubscribed()
{
timerSubscription?.Dispose();
}
protected override void Subscribed()
{
startTime = Timing.GetTickCount();
timerSubscription = Timing.AnimationsTimer
.Subscribe(t => TimerTick(t));
PublishNext(0.0d);
}
}
}

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

@ -1,10 +1,10 @@
// 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 Avalonia.Metadata;
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Easings;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation
{
@ -24,17 +24,7 @@ namespace Avalonia.Animation
/// <summary>
/// Gets the easing class to be used.
/// </summary>
public Easing Easing
{
get
{
return _easing ?? (_easing = new LinearEasing());
}
set
{
_easing = value;
}
}
public Easing Easing { get; set; } = new LinearEasing();
/// <inheritdocs/>
public AvaloniaProperty Property
@ -61,8 +51,10 @@ namespace Avalonia.Animation
/// <inheritdocs/>
public virtual IDisposable Apply(Animatable control, object oldValue, object newValue)
{
var transition = DoTransition(Timing.GetTransitionsTimer(control, Duration, TimeSpan.Zero), (T)oldValue, (T)newValue).Select(p => (object)p);
return control.Bind(Property, transition, Data.BindingPriority.Animation);
var transition = DoTransition(new TransitionInstance(Duration), (T)oldValue, (T)newValue);
return control.Bind<T>((AvaloniaProperty<T>)Property, transition, Data.BindingPriority.Animation);
}
}
}
}

18
src/Avalonia.Animation/Utils/DoubleUtils.cs

@ -1,18 +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.Collections.Generic;
using System.Text;
namespace Avalonia.Animation.Utils
{
internal static class DoubleUtils
{
internal static bool AboutEqual(double x, double y)
{
double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;
return Math.Abs(x - y) <= epsilon;
}
}
}

2
src/Avalonia.Animation/Utils/EasingUtils.cs

@ -13,6 +13,6 @@ namespace Avalonia.Animation.Utils
/// <summary>
/// Half of <see cref="Math.PI"/>
/// </summary>
internal static double HALFPI = Math.PI / 2d;
internal const double HALFPI = Math.PI / 2d;
}
}

1
src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins
{

14
src/Avalonia.Base/Threading/Dispatcher.cs

@ -92,6 +92,20 @@ namespace Avalonia.Threading
return _jobRunner.InvokeAsync(function, priority);
}
/// <inheritdoc/>
public Task InvokeAsync(Func<Task> function, DispatcherPriority priority = DispatcherPriority.Normal)
{
Contract.Requires<ArgumentNullException>(function != null);
return _jobRunner.InvokeAsync(function, priority).Unwrap();
}
/// <inheritdoc/>
public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> function, DispatcherPriority priority = DispatcherPriority.Normal)
{
Contract.Requires<ArgumentNullException>(function != null);
return _jobRunner.InvokeAsync(function, priority).Unwrap();
}
/// <inheritdoc/>
public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{

18
src/Avalonia.Base/Threading/IDispatcher.cs

@ -40,5 +40,23 @@ namespace Avalonia.Threading
/// <param name="function">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary>
/// Queues the specified work to run on the dispatcher thread and returns a proxy for the
/// task returned by <paramref name="function"/>.
/// </summary>
/// <param name="function">The work to execute asynchronously.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that represents a proxy for the task returned by <paramref name="function"/>.</returns>
Task InvokeAsync(Func<Task> function, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary>
/// Queues the specified work to run on the dispatcher thread and returns a proxy for the
/// task returned by <paramref name="function"/>.
/// </summary>
/// <param name="function">The work to execute asynchronously.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that represents a proxy for the task returned by <paramref name="function"/>.</returns>
Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> function, DispatcherPriority priority = DispatcherPriority.Normal);
}
}

26
src/Avalonia.Controls/Canvas.cs

@ -48,7 +48,7 @@ namespace Avalonia.Controls
static Canvas()
{
ClipToBoundsProperty.OverrideDefaultValue<Canvas>(false);
AffectsCanvasArrange(LeftProperty, TopProperty, RightProperty, BottomProperty);
AffectsParentArrange<Canvas>(LeftProperty, TopProperty, RightProperty, BottomProperty);
}
/// <summary>
@ -207,29 +207,5 @@ namespace Avalonia.Controls
return finalSize;
}
/// <summary>
/// Marks a property on a child as affecting the canvas' arrangement.
/// </summary>
/// <param name="properties">The properties.</param>
private static void AffectsCanvasArrange(params AvaloniaProperty[] properties)
{
foreach (var property in properties)
{
property.Changed.Subscribe(AffectsCanvasArrangeInvalidate);
}
}
/// <summary>
/// Calls <see cref="Layoutable.InvalidateArrange"/> on the parent of the control whose
/// property changed, if that parent is a canvas.
/// </summary>
/// <param name="e">The event args.</param>
private static void AffectsCanvasArrangeInvalidate(AvaloniaPropertyChangedEventArgs e)
{
var control = e.Sender as IControl;
var canvas = control?.VisualParent as Canvas;
canvas?.InvalidateArrange();
}
}
}

2
src/Avalonia.Controls/ContentControl.cs

@ -45,6 +45,8 @@ namespace Avalonia.Controls
static ContentControl()
{
ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren);
PseudoClass(ContentProperty, x => x != null, ":valid");
PseudoClass(ContentProperty, x => x == null, ":invalid");
}
/// <summary>

32
src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs

@ -0,0 +1,32 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace Avalonia.Controls.Converters
{
public class MarginMultiplierConverter : IValueConverter
{
public double Indent { get; set; }
public bool Left { get; set; } = false;
public bool Top { get; set; } = false;
public bool Right { get; set; } = false;
public bool Bottom { get; set; } = false;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is int depth))
return new Thickness(0);
return new Thickness(Left ? Indent * depth : 0, Top ? Indent * depth : 0, Right ? Indent * depth : 0, Bottom ? Indent * depth : 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
}

4
src/Avalonia.Controls/DockPanel.cs

@ -37,7 +37,7 @@ namespace Avalonia.Controls
/// </summary>
static DockPanel()
{
AffectsArrange(DockProperty);
AffectsParentMeasure<DockPanel>(DockProperty);
}
/// <summary>
@ -173,4 +173,4 @@ namespace Avalonia.Controls
return arrangeSize;
}
}
}
}

5
src/Avalonia.Controls/Grid.cs

@ -48,6 +48,11 @@ namespace Avalonia.Controls
private RowDefinitions _rowDefinitions;
static Grid()
{
AffectsParentMeasure<Grid>(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty);
}
/// <summary>
/// Gets or sets the columns definitions for the grid.
/// </summary>

42
src/Avalonia.Controls/Panel.cs

@ -72,6 +72,32 @@ namespace Avalonia.Controls
base.Render(context);
}
/// <summary>
/// Marks a property on a child as affecting the parent panel's arrangement.
/// </summary>
/// <param name="properties">The properties.</param>
protected static void AffectsParentArrange<TPanel>(params AvaloniaProperty[] properties)
where TPanel : class, IPanel
{
foreach (var property in properties)
{
property.Changed.Subscribe(AffectsParentArrangeInvalidate<TPanel>);
}
}
/// <summary>
/// Marks a property on a child as affecting the parent panel's measurement.
/// </summary>
/// <param name="properties">The properties.</param>
protected static void AffectsParentMeasure<TPanel>(params AvaloniaProperty[] properties)
where TPanel : class, IPanel
{
foreach (var property in properties)
{
property.Changed.Subscribe(AffectsParentMeasureInvalidate<TPanel>);
}
}
/// <summary>
/// Called when the <see cref="Children"/> collection changes.
/// </summary>
@ -116,5 +142,21 @@ namespace Avalonia.Controls
InvalidateMeasure();
}
private static void AffectsParentArrangeInvalidate<TPanel>(AvaloniaPropertyChangedEventArgs e)
where TPanel : class, IPanel
{
var control = e.Sender as IControl;
var panel = control?.VisualParent as TPanel;
panel?.InvalidateArrange();
}
private static void AffectsParentMeasureInvalidate<TPanel>(AvaloniaPropertyChangedEventArgs e)
where TPanel : class, IPanel
{
var control = e.Sender as IControl;
var panel = control?.VisualParent as TPanel;
panel?.InvalidateMeasure();
}
}
}

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

@ -279,6 +279,6 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Invalidates the current scroll.
/// </summary>
protected void InvalidateScroll() => ((ILogicalScrollable)Owner).InvalidateScroll();
protected void InvalidateScroll() => ((ILogicalScrollable)Owner).InvalidateScroll?.Invoke();
}
}

8
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -35,6 +35,11 @@ namespace Avalonia.Controls.Presenters
private int _selectionEnd;
private bool _caretBlink;
private IBrush _highlightBrush;
static TextPresenter()
{
AffectsRender(PasswordCharProperty);
}
public TextPresenter()
{
@ -49,6 +54,9 @@ namespace Avalonia.Controls.Presenters
this.GetObservable(CaretIndexProperty)
.Subscribe(CaretIndexChanged);
this.GetObservable(PasswordCharProperty)
.Subscribe(_ => InvalidateFormattedText());
}
public int CaretIndex

8
src/Avalonia.Controls/Slider.cs

@ -171,11 +171,7 @@ namespace Avalonia.Controls
/// <param name="value">Value that want to snap to closest Tick.</param>
private void MoveToNextTick(double value)
{
double next = SnapToTick(Math.Max(Minimum, Math.Min(Maximum, value)));
if (next != value)
{
Value = next;
}
Value = SnapToTick(Math.Max(Minimum, Math.Min(Maximum, value)));
}
/// <summary>
@ -194,4 +190,4 @@ namespace Avalonia.Controls
return value;
}
}
}
}

42
src/Avalonia.Controls/TreeView.cs

@ -1,6 +1,8 @@
// 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 Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
@ -31,6 +33,14 @@ namespace Avalonia.Controls
o => o.SelectedItem,
(o, v) => o.SelectedItem = v);
/// <summary>
/// Defines the <see cref="SelectedItemChanged"/> event.
/// </summary>
public static readonly RoutedEvent<SelectionChangedEventArgs> SelectedItemChangedEvent =
RoutedEvent.Register<TreeView, SelectionChangedEventArgs>(
"SelectedItemChanged",
RoutingStrategies.Bubble);
private object _selectedItem;
/// <summary>
@ -41,6 +51,15 @@ namespace Avalonia.Controls
// HACK: Needed or SelectedItem property will not be found in Release build.
}
/// <summary>
/// Occurs when the control's selection changes.
/// </summary>
public event EventHandler<SelectionChangedEventArgs> SelectedItemChanged
{
add { AddHandler(SelectedItemChangedEvent, value); }
remove { RemoveHandler(SelectedItemChangedEvent, value); }
}
/// <summary>
/// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view.
/// </summary>
@ -74,6 +93,7 @@ namespace Avalonia.Controls
MarkContainerSelected(container, false);
}
var oldItem = _selectedItem;
SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
if (_selectedItem != null)
@ -86,6 +106,28 @@ namespace Avalonia.Controls
container.BringIntoView();
}
}
if (oldItem != _selectedItem)
{
// Fire the SelectionChanged event
List<object> removed = new List<object>();
if (oldItem != null)
{
removed.Add(oldItem);
}
List<object> added = new List<object>();
if (_selectedItem != null)
{
added.Add(_selectedItem);
}
var changed = new SelectionChangedEventArgs(
SelectedItemChangedEvent,
added,
removed);
RaiseEvent(changed);
}
}
}

36
src/Avalonia.Controls/TreeViewItem.cs

@ -21,7 +21,7 @@ namespace Avalonia.Controls
/// </summary>
public static readonly DirectProperty<TreeViewItem, bool> IsExpandedProperty =
AvaloniaProperty.RegisterDirect<TreeViewItem, bool>(
"IsExpanded",
nameof(IsExpanded),
o => o.IsExpanded,
(o, v) => o.IsExpanded = v);
@ -31,17 +31,25 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsSelectedProperty =
ListBoxItem.IsSelectedProperty.AddOwner<TreeViewItem>();
/// <summary>
/// Defines the <see cref="Level"/> property.
/// </summary>
public static readonly DirectProperty<TreeViewItem, int> LevelProperty =
AvaloniaProperty.RegisterDirect<TreeViewItem, int>(
nameof(Level), o => o.Level);
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel());
private TreeView _treeView;
private bool _isExpanded;
private int _level;
/// <summary>
/// Initializes static members of the <see cref="TreeViewItem"/> class.
/// </summary>
static TreeViewItem()
{
{
SelectableMixin.Attach<TreeViewItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue<TreeViewItem>(true);
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
@ -65,6 +73,15 @@ namespace Avalonia.Controls
set { SetValue(IsSelectedProperty, value); }
}
/// <summary>
/// Gets the level/indentation of the item.
/// </summary>
public int Level
{
get { return _level; }
private set { SetAndRaise(LevelProperty, ref _level, value); }
}
/// <summary>
/// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view.
/// </summary>
@ -89,6 +106,8 @@ namespace Avalonia.Controls
base.OnAttachedToLogicalTree(e);
_treeView = this.GetLogicalAncestors().OfType<TreeView>().FirstOrDefault();
Level = CalculateDistanceFromLogicalParent<TreeView>(this) - 1;
if (ItemTemplate == null && _treeView?.ItemTemplate != null)
{
ItemTemplate = _treeView.ItemTemplate;
@ -126,5 +145,18 @@ namespace Avalonia.Controls
// Don't call base.OnKeyDown - let events bubble up to containing TreeView.
}
private static int CalculateDistanceFromLogicalParent<T>(ILogical logical, int @default = -1) where T : class
{
var result = 0;
while (logical != null && logical.GetType() != typeof(T))
{
++result;
logical = logical.LogicalParent;
}
return logical != null ? result : @default;
}
}
}

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -63,4 +63,4 @@ namespace Avalonia.DesignerSupport.Remote
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
}
}
}

5
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.DesignerSupport;
using Avalonia.Input;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Designer;
@ -207,4 +204,4 @@ namespace Avalonia.DesignerSupport.Remote
}
});
}
}
}

2
src/Avalonia.Diagnostics/Debug.cs

@ -2,10 +2,8 @@
// 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.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.VisualTree;

1
src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.LogicalTree;

3
src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;

2
src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs

@ -5,7 +5,7 @@ using System.Reflection;
namespace Avalonia.Diagnostics.Views
{
internal static class PropertyChangedExtenions
internal static class PropertyChangedExtensions
{
public static IObservable<T> GetObservable<T>(this INotifyPropertyChanged source, string propertyName)
{

1
src/Avalonia.Diagnostics/Views/SimpleGrid.cs

@ -2,7 +2,6 @@
// 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.Controls;
namespace Avalonia.Diagnostics.Views

9
src/Avalonia.DotNetCoreRuntime/AppBuilder.cs

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Shared.PlatformSupport;
@ -60,4 +55,4 @@ namespace Avalonia
void LoadWin32() => this.UseWin32();
void LoadGtk3() => this.UseGtk3();
}
}
}

4
src/Avalonia.DotNetCoreRuntime/RuntimeInfo.cs

@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;

2
src/Avalonia.Input/AccessKeyHandler.cs

@ -49,7 +49,7 @@ namespace Avalonia.Input
private bool _altIsDown;
/// <summary>
/// Element to restore folowing AltKey taking focus.
/// Element to restore following AltKey taking focus.
/// </summary>
private IInputElement _restoreFocusElement;

6
src/Avalonia.Input/Cursors.cs

@ -2,10 +2,6 @@
// 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.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Input
@ -69,7 +65,7 @@ namespace Avalonia.Input
{
return Enum.TryParse<StandardCursorType>(s, true, out var t) ?
new Cursor(t) :
throw new ArgumentException($"Unrecognised cursor type '{s}'.");
throw new ArgumentException($"Unrecognized cursor type '{s}'.");
}
private static IPlatformHandle GetCursor(StandardCursorType type)

4
src/Avalonia.Input/DataObject.cs

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Generic;
namespace Avalonia.Input
{

4
src/Avalonia.Input/DragDrop.cs

@ -1,6 +1,6 @@
using System.Threading.Tasks;
using Avalonia.Interactivity;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
namespace Avalonia.Input
{
@ -51,4 +51,4 @@ namespace Avalonia.Input
return src?.DoDragDrop(data, allowedEffects) ?? Task.FromResult(DragDropEffects.None);
}
}
}
}

1
src/Avalonia.Input/ICustomKeyboardNavigation.cs

@ -1,7 +1,6 @@
// 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;
namespace Avalonia.Input
{

4
src/Avalonia.Input/IDataObject.cs

@ -14,7 +14,7 @@ namespace Avalonia.Input
IEnumerable<string> GetDataFormats();
/// <summary>
/// Checks wether a given DataFormat is present in this object
/// Checks whether a given DataFormat is present in this object
/// <seealso cref="DataFormats"/>
/// </summary>
bool Contains(string dataFormat);
@ -36,4 +36,4 @@ namespace Avalonia.Input
/// </summary>
object Get(string dataFormat);
}
}
}

8
src/Avalonia.Input/IInputManager.cs

@ -7,24 +7,24 @@ using Avalonia.Input.Raw;
namespace Avalonia.Input
{
/// <summary>
/// Recieves input from the windowing subsystem and dispatches it to interested parties
/// Receives input from the windowing subsystem and dispatches it to interested parties
/// for processing.
/// </summary>
public interface IInputManager
{
/// <summary>
/// Gets an observable that notifies on each input event recieved before
/// Gets an observable that notifies on each input event received before
/// <see cref="Process"/>.
/// </summary>
IObservable<RawInputEventArgs> PreProcess { get; }
/// <summary>
/// Gets an observable that notifies on each input event recieved.
/// Gets an observable that notifies on each input event received.
/// </summary>
IObservable<RawInputEventArgs> Process { get; }
/// <summary>
/// Gets an observable that notifies on each input event recieved after
/// Gets an observable that notifies on each input event received after
/// <see cref="Process"/>.
/// </summary>
IObservable<RawInputEventArgs> PostProcess { get; }

2
src/Avalonia.Input/IPointerDevice.cs

@ -1,8 +1,6 @@
// 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 Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Input

1
src/Avalonia.Input/InputElement.cs

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Interactivity;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Input

4
src/Avalonia.Input/InputExtensions.cs

@ -1,10 +1,10 @@
// 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 Avalonia.VisualTree;
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
@ -48,4 +48,4 @@ namespace Avalonia.Input
element.IsEnabledCore;
}
}
}
}

2
src/Avalonia.Input/InputManager.cs

@ -8,7 +8,7 @@ using Avalonia.Input.Raw;
namespace Avalonia.Input
{
/// <summary>
/// Recieves input from the windowing subsystem and dispatches it to interested parties
/// Receives input from the windowing subsystem and dispatches it to interested parties
/// for processing.
/// </summary>
public class InputManager : IInputManager

2
src/Avalonia.Input/Key.cs

@ -77,7 +77,7 @@ namespace Avalonia.Input
KanaMode = 9,
/// <summary>
/// The IME Janja mode key.
/// The IME Junja mode key.
/// </summary>
JunjaMode = 10,

5
src/Avalonia.Input/KeyBinding.cs

@ -1,8 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Avalonia.Input

1
src/Avalonia.Input/KeyEventArgs.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Interactivity;
namespace Avalonia.Input

6
src/Avalonia.Input/KeyGesture.cs

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Input
{
@ -45,7 +43,7 @@ namespace Avalonia.Input
public InputModifiers Modifiers { get; set; }
static readonly Dictionary<string, Key> KeySynonims = new Dictionary<string, Key>
static readonly Dictionary<string, Key> KeySynonyms = new Dictionary<string, Key>
{
{"+", Key.OemPlus },
{"-", Key.OemMinus},
@ -56,7 +54,7 @@ namespace Avalonia.Input
static Key ParseKey(string key)
{
Key rv;
if (KeySynonims.TryGetValue(key.ToLower(), out rv))
if (KeySynonyms.TryGetValue(key.ToLower(), out rv))
return rv;
return (Key)Enum.Parse(typeof (Key), key, true);
}

3
src/Avalonia.Input/KeyboardDevice.cs

@ -1,10 +1,7 @@
// 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.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;

2
src/Avalonia.Input/KeyboardNavigationHandler.cs

@ -46,7 +46,7 @@ namespace Avalonia.Input
/// <param name="direction">The navigation direction.</param>
/// <returns>
/// The next element in the specified direction, or null if <paramref name="element"/>
/// was the last in therequested direction.
/// was the last in the requested direction.
/// </returns>
public static IInputElement GetNext(
IInputElement element,

5
src/Avalonia.Input/MouseDevice.cs

@ -2,7 +2,6 @@
// 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 Avalonia.Input.Raw;
@ -27,7 +26,7 @@ namespace Avalonia.Input
/// Gets the control that is currently capturing by the mouse, if any.
/// </summary>
/// <remarks>
/// When an element captures the mouse, it recieves mouse input whether the cursor is
/// When an element captures the mouse, it receives mouse input whether the cursor is
/// within the control's bounds or not. To set the mouse capture, call the
/// <see cref="Capture"/> method.
/// </remarks>
@ -66,7 +65,7 @@ namespace Avalonia.Input
/// </summary>
/// <param name="control">The control.</param>
/// <remarks>
/// When an element captures the mouse, it recieves mouse input whether the cursor is
/// When an element captures the mouse, it receives mouse input whether the cursor is
/// within the control's bounds or not. The current mouse capture control is exposed
/// by the <see cref="Captured"/> property.
/// </remarks>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save