Browse Source

Merge branch 'master' of github.com:AvaloniaUI/Avalonia into dev/siegfriedpammer/event-viewer

pull/1888/head
Siegfried Pammer 8 years ago
parent
commit
e52ab43b74
  1. 9
      samples/ControlCatalog/App.xaml
  2. 2
      samples/ControlCatalog/ControlCatalog.csproj
  3. 3
      samples/ControlCatalog/MainView.xaml
  4. 3
      samples/ControlCatalog/Pages/CanvasPage.xaml
  5. 13
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  6. 25
      samples/ControlCatalog/Pages/ListBoxPage.xaml.cs
  7. 59
      samples/ControlCatalog/Pages/MenuPage.xaml
  8. 38
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  9. 4
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  10. 8
      samples/ControlCatalog/Pages/ScreenPage.cs
  11. 2
      samples/ControlCatalog/SideBar.xaml
  12. 2
      samples/RenderDemo/SideBar.xaml
  13. 10
      samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
  14. 278
      samples/interop/Direct3DInteropSample/MainWindow.cs
  15. 6
      src/Android/Avalonia.Android/AndroidPlatform.cs
  16. 10
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  17. 14
      src/Android/Avalonia.Android/AppBuilder.cs
  18. 9
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  19. 9
      src/Android/Avalonia.Android/AvaloniaView.cs
  20. 2
      src/Android/Avalonia.Android/CursorFactory.cs
  21. 4
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  22. 5
      src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
  23. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  24. 10
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  25. 9
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  26. 13
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  27. 8
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  28. 6
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  29. 9
      src/Android/Avalonia.Android/Platform/Specific/IAndroidView.cs
  30. 15
      src/Android/Avalonia.Android/PlatformIconLoader.cs
  31. 2
      src/Android/Avalonia.Android/SystemDialogImpl.cs
  32. 53
      src/Avalonia.Animation/Animatable.cs
  33. 98
      src/Avalonia.Animation/Animation.cs
  34. 228
      src/Avalonia.Animation/AnimationInstance`1.cs
  35. 5
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  36. 273
      src/Avalonia.Animation/AnimatorStateMachine`1.cs
  37. 101
      src/Avalonia.Animation/Animator`1.cs
  38. 7
      src/Avalonia.Animation/Cue.cs
  39. 11
      src/Avalonia.Animation/DoubleAnimator.cs
  40. 1
      src/Avalonia.Animation/DoubleTransition.cs
  41. 1
      src/Avalonia.Animation/Easing/BounceEaseIn.cs
  42. 5
      src/Avalonia.Animation/Easing/Easing.cs
  43. 3
      src/Avalonia.Animation/Easing/EasingTypeConverter.cs
  44. 2
      src/Avalonia.Animation/Easing/ElasticEaseIn.cs
  45. 2
      src/Avalonia.Animation/Easing/ElasticEaseOut.cs
  46. 2
      src/Avalonia.Animation/Easing/SineEaseIn.cs
  47. 2
      src/Avalonia.Animation/Easing/SineEaseOut.cs
  48. 6
      src/Avalonia.Animation/FillMode.cs
  49. 1
      src/Avalonia.Animation/FloatTransition.cs
  50. 2
      src/Avalonia.Animation/IAnimation.cs
  51. 1
      src/Avalonia.Animation/IAnimator.cs
  52. 2
      src/Avalonia.Animation/ITransition.cs
  53. 1
      src/Avalonia.Animation/IntegerTransition.cs
  54. 3
      src/Avalonia.Animation/KeyFrame.cs
  55. 11
      src/Avalonia.Animation/KeyFramePair`1.cs
  56. 8
      src/Avalonia.Animation/PlayState.cs
  57. 6
      src/Avalonia.Animation/PlaybackDirection.cs
  58. 5
      src/Avalonia.Animation/RepeatCount.cs
  59. 3
      src/Avalonia.Animation/RepeatCountTypeConverter.cs
  60. 81
      src/Avalonia.Animation/Timing.cs
  61. 54
      src/Avalonia.Animation/TransitionInstance.cs
  62. 22
      src/Avalonia.Animation/Transition`1.cs
  63. 18
      src/Avalonia.Animation/Utils/DoubleUtils.cs
  64. 2
      src/Avalonia.Animation/Utils/EasingUtils.cs
  65. 1
      src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs
  66. 7
      src/Avalonia.Base/PriorityBindingEntry.cs
  67. 14
      src/Avalonia.Base/PriorityLevel.cs
  68. 14
      src/Avalonia.Base/Threading/Dispatcher.cs
  69. 18
      src/Avalonia.Base/Threading/IDispatcher.cs
  70. 8
      src/Avalonia.Controls/Border.cs
  71. 2
      src/Avalonia.Controls/Button.cs
  72. 6
      src/Avalonia.Controls/ButtonSpinner.cs
  73. 26
      src/Avalonia.Controls/Canvas.cs
  74. 2
      src/Avalonia.Controls/ContentControl.cs
  75. 32
      src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
  76. 2
      src/Avalonia.Controls/Decorator.cs
  77. 4
      src/Avalonia.Controls/DockPanel.cs
  78. 8
      src/Avalonia.Controls/DrawingPresenter.cs
  79. 10
      src/Avalonia.Controls/Expander.cs
  80. 5
      src/Avalonia.Controls/Grid.cs
  81. 3
      src/Avalonia.Controls/Image.cs
  82. 38
      src/Avalonia.Controls/MenuItem.cs
  83. 43
      src/Avalonia.Controls/Panel.cs
  84. 4
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  85. 2
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  86. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  87. 8
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  88. 2
      src/Avalonia.Controls/Primitives/AccessText.cs
  89. 4
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  90. 6
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  91. 4
      src/Avalonia.Controls/Primitives/Track.cs
  92. 6
      src/Avalonia.Controls/ProgressBar.cs
  93. 5
      src/Avalonia.Controls/Shapes/Shape.cs
  94. 10
      src/Avalonia.Controls/Slider.cs
  95. 4
      src/Avalonia.Controls/StackPanel.cs
  96. 2
      src/Avalonia.Controls/TabControl.cs
  97. 13
      src/Avalonia.Controls/TextBlock.cs
  98. 2
      src/Avalonia.Controls/TopLevel.cs
  99. 42
      src/Avalonia.Controls/TreeView.cs
  100. 36
      src/Avalonia.Controls/TreeViewItem.cs

9
samples/ControlCatalog/App.xaml

@ -14,6 +14,11 @@
<Setter Property="FontSize" Value="13"/> <Setter Property="FontSize" Value="13"/>
</Style> </Style>
<StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/> <Style Selector="TextBlock.h3">
<Setter Property="Foreground" Value="#a2a2a2"/>
<Setter Property="FontSize" Value="13"/>
</Style>
<StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
</Application.Styles> </Application.Styles>
</Application> </Application>

2
samples/ControlCatalog/ControlCatalog.csproj

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

3
samples/ControlCatalog/MainView.xaml

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

3
samples/ControlCatalog/Pages/CanvasPage.xaml

@ -11,7 +11,8 @@
<GradientStop Offset="1" Color="Transparent"/> <GradientStop Offset="1" Color="Transparent"/>
</LinearGradientBrush.GradientStops> </LinearGradientBrush.GradientStops>
</LinearGradientBrush> </LinearGradientBrush>
</Rectangle.OpacityMask> </Rectangle> </Rectangle.OpacityMask>
</Rectangle>
<Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/> <Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/>
<Path Fill="Orange" Data="M 0,0 c 0,0 50,0 50,-50 c 0,0 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/> <Path Fill="Orange" Data="M 0,0 c 0,0 50,0 50,-50 c 0,0 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/>
<Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250"> <Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250">

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);
}
}
}

59
samples/ControlCatalog/Pages/MenuPage.xaml

@ -7,29 +7,44 @@
Margin="0,16,0,0" Margin="0,16,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Spacing="16"> Spacing="16">
<Menu> <StackPanel>
<MenuItem Header="_First"> <TextBlock Classes="h3" Margin="4 8">Defined in XAML</TextBlock>
<MenuItem Header="Standard _Menu Item"/> <Menu>
<Separator/> <MenuItem Header="_First">
<MenuItem Header="Menu with _Submenu"> <MenuItem Header="Standard _Menu Item"/>
<MenuItem Header="Submenu _1"/> <Separator/>
<MenuItem Header="Submenu _2"/> <MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
</MenuItem>
<MenuItem Header="Menu Item with _Icon">
<MenuItem.Icon>
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="Menu Item with _Icon"> <MenuItem Header="_Second">
<MenuItem.Icon> <MenuItem Header="Second _Menu Item"/>
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
</MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Menu Item with _Checkbox"> </Menu>
<MenuItem.Icon> </StackPanel>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon> <StackPanel>
</MenuItem> <TextBlock Classes="h3" Margin="4 8">Dyanamically generated</TextBlock>
</MenuItem> <Menu Items="{Binding}">
<MenuItem Header="_Second"> <Menu.Styles>
<MenuItem Header="Second _Menu Item"/> <Style Selector="MenuItem">
</MenuItem> <Setter Property="Header" Value="{Binding Header}"/>
</Menu> <Setter Property="Items" Value="{Binding Items}"/>
</Style>
</Menu.Styles>
</Menu>
</StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

38
samples/ControlCatalog/Pages/MenuPage.xaml.cs

@ -1,3 +1,4 @@
using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
@ -8,6 +9,37 @@ namespace ControlCatalog.Pages
public MenuPage() public MenuPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
DataContext = new[]
{
new MenuItemViewModel
{
Header = "_File",
Items = new[]
{
new MenuItemViewModel { Header = "_Open..." },
new MenuItemViewModel { Header = "Save" },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
Items = new[]
{
new MenuItemViewModel { Header = "File1.txt" },
new MenuItemViewModel { Header = "File2.txt" },
}
},
}
},
new MenuItemViewModel
{
Header = "_Edit",
Items = new[]
{
new MenuItemViewModel { Header = "_Copy" },
new MenuItemViewModel { Header = "_Paste" },
}
}
};
} }
private void InitializeComponent() private void InitializeComponent()
@ -15,4 +47,10 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
} }
public class MenuItemViewModel
{
public string Header { get; set; }
public IList<MenuItemViewModel> Items { get; set; }
}
} }

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"/> <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> <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> <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"/> <CheckBox Grid.Row="3" Grid.Column="1" IsChecked="{Binding #upDown.ClipValueToMinMax}" VerticalAlignment="Center" Margin="2"/>
@ -77,4 +77,4 @@
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

8
samples/ControlCatalog/Pages/ScreenPage.cs

@ -42,7 +42,11 @@ namespace ControlCatalog.Pages
context.DrawRectangle(p, boundsRect); context.DrawRectangle(p, boundsRect);
context.DrawRectangle(p, workingAreaRect); context.DrawRectangle(p, workingAreaRect);
FormattedText text = new FormattedText(); FormattedText text = new FormattedText()
{
Typeface = Typeface.Default
};
text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}"; text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height), text); 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)); 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="Opacity" Value="0.5"/>
<Setter Property="Transitions"> <Setter Property="Transitions">
<Transitions> <Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.5"/> <DoubleTransition Property="Opacity" Duration="0:0:0.2"/>
</Transitions> </Transitions>
</Setter> </Setter>
</Style> </Style>

2
samples/RenderDemo/SideBar.xaml

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

10
samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs

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

278
samples/interop/Direct3DInteropSample/MainWindow.cs

@ -1,81 +1,83 @@
using System; // Copyright (c) The Avalonia Project. All rights reserved.
using System.Collections.Generic; // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
using System.Text; using System;
using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Direct2D1;
using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using SharpDX; using SharpDX;
using SharpDX.D3DCompiler; using SharpDX.D3DCompiler;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using SharpDX.Direct3D; using SharpDX.Direct3D;
using SharpDX.Direct3D11; using SharpDX.Direct3D11;
using SharpDX.DXGI; using SharpDX.DXGI;
using SharpDX.WIC;
using SharpDX.Mathematics;
using AlphaMode = SharpDX.Direct2D1.AlphaMode; using AlphaMode = SharpDX.Direct2D1.AlphaMode;
using Buffer = SharpDX.Direct3D11.Buffer; using Buffer = SharpDX.Direct3D11.Buffer;
using DeviceContext = SharpDX.Direct3D11.DeviceContext; using DeviceContext = SharpDX.Direct2D1.DeviceContext;
using Factory1 = SharpDX.DXGI.Factory1; using Factory2 = SharpDX.DXGI.Factory2;
using InputElement = SharpDX.Direct3D11.InputElement; using InputElement = SharpDX.Direct3D11.InputElement;
using Matrix = SharpDX.Matrix; using Matrix = SharpDX.Matrix;
using PixelFormat = SharpDX.Direct2D1.PixelFormat; using PixelFormat = SharpDX.Direct2D1.PixelFormat;
using Resource = SharpDX.Direct3D11.Resource;
namespace Direct3DInteropSample namespace Direct3DInteropSample
{ {
class MainWindow : Window public class MainWindow : Window
{ {
private SharpDX.Direct3D11.Device _d3dDevice; Texture2D _backBuffer;
private SharpDX.DXGI.Device _dxgiDevice; RenderTargetView _renderView;
Texture2D backBuffer = null; Texture2D _depthBuffer;
RenderTargetView renderView = null; DepthStencilView _depthView;
Texture2D depthBuffer = null;
DepthStencilView depthView = null;
private readonly SwapChain _swapChain; private readonly SwapChain _swapChain;
private SwapChainDescription _desc; private SwapChainDescription1 _desc;
private Matrix _proj = Matrix.Identity; private Matrix _proj = Matrix.Identity;
private Matrix _view; private readonly Matrix _view;
private Buffer _contantBuffer; private Buffer _contantBuffer;
private SharpDX.Direct2D1.Device _d2dDevice; private DeviceContext _deviceContext;
private SharpDX.Direct2D1.DeviceContext _d2dContext; private readonly MainWindowViewModel _model;
private RenderTarget _d2dRenderTarget;
private MainWindowViewModel _model;
public MainWindow() public MainWindow()
{ {
_dxgiDevice = AvaloniaLocator.Current.GetService<SharpDX.DXGI.Device>();
_d3dDevice = _dxgiDevice.QueryInterface<SharpDX.Direct3D11.Device>();
_d2dDevice = AvaloniaLocator.Current.GetService<SharpDX.Direct2D1.Device>();
DataContext = _model = new MainWindowViewModel(); DataContext = _model = new MainWindowViewModel();
_desc = new SwapChainDescription()
_desc = new SwapChainDescription1()
{ {
BufferCount = 1, BufferCount = 1,
ModeDescription = Width = (int)ClientSize.Width,
new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height, Height = (int)ClientSize.Height,
new Rational(60, 1), Format.R8G8B8A8_UNorm), Format = Format.R8G8B8A8_UNorm,
IsWindowed = true,
OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero,
SampleDescription = new SampleDescription(1, 0), SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard, SwapEffect = SwapEffect.Discard,
Usage = Usage.RenderTargetOutput Usage = Usage.RenderTargetOutput
}; };
_swapChain = new SwapChain(new Factory1(), _d3dDevice, _desc); using (var factory = Direct2D1Platform.DxgiDevice.Adapter.GetParent<Factory2>())
{
_swapChain = new SwapChain1(factory, Direct2D1Platform.DxgiDevice, PlatformImpl?.Handle.Handle ?? IntPtr.Zero, ref _desc);
}
_d2dContext = new SharpDX.Direct2D1.DeviceContext(_d2dDevice, DeviceContextOptions.None) _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None)
{ {
DotsPerInch = new Size2F(96, 96) DotsPerInch = new Size2F(96, 96)
}; };
CreateMesh(); CreateMesh();
_view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY); _view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY);
this.GetObservable(ClientSizeProperty).Subscribe(Resize); this.GetObservable(ClientSizeProperty).Subscribe(Resize);
Resize(ClientSize); Resize(ClientSize);
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
Background = Avalonia.Media.Brushes.Transparent; Background = Avalonia.Media.Brushes.Transparent;
} }
@ -83,29 +85,32 @@ namespace Direct3DInteropSample
protected override void HandlePaint(Rect rect) protected override void HandlePaint(Rect rect)
{ {
var viewProj = Matrix.Multiply(_view, _proj); var viewProj = Matrix.Multiply(_view, _proj);
var context = _d3dDevice.ImmediateContext; var context = Direct2D1Platform.Direct3D11Device.ImmediateContext;
// Clear views // Clear views
context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0); context.ClearDepthStencilView(_depthView, DepthStencilClearFlags.Depth, 1.0f, 0);
context.ClearRenderTargetView(renderView, Color.White); context.ClearRenderTargetView(_renderView, Color.White);
// Update WorldViewProj Matrix // Update WorldViewProj Matrix
var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) * var worldViewProj = Matrix.RotationX((float)_model.RotationX) * Matrix.RotationY((float)_model.RotationY)
Matrix.RotationZ((float) _model.RotationZ) * Matrix.RotationZ((float)_model.RotationZ)
* Matrix.Scaling((float) _model.Zoom) * viewProj; * Matrix.Scaling((float)_model.Zoom)
* viewProj;
worldViewProj.Transpose(); worldViewProj.Transpose();
context.UpdateSubresource(ref worldViewProj, _contantBuffer); context.UpdateSubresource(ref worldViewProj, _contantBuffer);
// Draw the cube // Draw the cube
context.Draw(36, 0); context.Draw(36, 0);
base.HandlePaint(rect); base.HandlePaint(rect);
// Present! // Present!
_swapChain.Present(0, PresentFlags.None); _swapChain.Present(0, PresentFlags.None);
} }
private void CreateMesh()
void CreateMesh()
{ {
var device = _d3dDevice; var device = Direct2D1Platform.Direct3D11Device;
// Compile Vertex and Pixel shaders // Compile Vertex and Pixel shaders
var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0"); var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0");
var vertexShader = new VertexShader(device, vertexShaderByteCode); var vertexShader = new VertexShader(device, vertexShaderByteCode);
@ -114,63 +119,72 @@ namespace Direct3DInteropSample
var pixelShader = new PixelShader(device, pixelShaderByteCode); var pixelShader = new PixelShader(device, pixelShaderByteCode);
var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode); var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode);
var inputElements = new[]
{
new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0),
new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0)
};
// Layout from VertexShader input signature // Layout from VertexShader input signature
var layout = new InputLayout(device, signature, new[] var layout = new InputLayout(
{ device,
new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), signature,
new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) inputElements);
});
// Instantiate Vertex buffer from vertex data
// Instantiate Vertex buiffer from vertex data var vertices = Buffer.Create(
var vertices = Buffer.Create(device, BindFlags.VertexBuffer, new[] device,
{ BindFlags.VertexBuffer,
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front new[]
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), {
new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom
new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right
new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
}); new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
});
// Create Constant Buffer // Create Constant Buffer
_contantBuffer = new Buffer(device, Utilities.SizeOf<Matrix>(), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0); _contantBuffer = new Buffer(device, Utilities.SizeOf<Matrix>(), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
var context = _d3dDevice.ImmediateContext; var context = Direct2D1Platform.Direct3D11Device.ImmediateContext;
// Prepare All the stages // Prepare All the stages
context.InputAssembler.InputLayout = layout; context.InputAssembler.InputLayout = layout;
@ -181,63 +195,73 @@ namespace Direct3DInteropSample
context.PixelShader.Set(pixelShader); context.PixelShader.Set(pixelShader);
} }
void Resize(Size size) private void Resize(Size size)
{ {
Utilities.Dispose(ref _d2dRenderTarget); Utilities.Dispose(ref _deviceContext);
Utilities.Dispose(ref backBuffer); Utilities.Dispose(ref _backBuffer);
Utilities.Dispose(ref renderView); Utilities.Dispose(ref _renderView);
Utilities.Dispose(ref depthBuffer); Utilities.Dispose(ref _depthBuffer);
Utilities.Dispose(ref depthView); Utilities.Dispose(ref _depthView);
var context = _d3dDevice.ImmediateContext; var context = Direct2D1Platform.Direct3D11Device.ImmediateContext;
// Resize the backbuffer // Resize the backbuffer
_swapChain.ResizeBuffers(_desc.BufferCount, (int)size.Width, (int)size.Height, Format.Unknown, SwapChainFlags.None); _swapChain.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None);
// Get the backbuffer from the swapchain // Get the backbuffer from the swapchain
backBuffer = Texture2D.FromSwapChain<Texture2D>(_swapChain, 0); _backBuffer = Resource.FromSwapChain<Texture2D>(_swapChain, 0);
// Renderview on the backbuffer // Renderview on the backbuffer
renderView = new RenderTargetView(_d3dDevice, backBuffer); _renderView = new RenderTargetView(Direct2D1Platform.Direct3D11Device, _backBuffer);
// Create the depth buffer // Create the depth buffer
depthBuffer = new Texture2D(_d3dDevice, new Texture2DDescription() _depthBuffer = new Texture2D(
{ Direct2D1Platform.Direct3D11Device,
Format = Format.D32_Float_S8X24_UInt, new Texture2DDescription()
ArraySize = 1, {
MipLevels = 1, Format = Format.D32_Float_S8X24_UInt,
Width = (int)size.Width, ArraySize = 1,
Height = (int)size.Height, MipLevels = 1,
SampleDescription = new SampleDescription(1, 0), Width = (int)size.Width,
Usage = ResourceUsage.Default, Height = (int)size.Height,
BindFlags = BindFlags.DepthStencil, SampleDescription = new SampleDescription(1, 0),
CpuAccessFlags = CpuAccessFlags.None, Usage = ResourceUsage.Default,
OptionFlags = ResourceOptionFlags.None BindFlags = BindFlags.DepthStencil,
}); CpuAccessFlags = CpuAccessFlags.None,
OptionFlags = ResourceOptionFlags.None
});
// Create the depth buffer view // Create the depth buffer view
depthView = new DepthStencilView(_d3dDevice, depthBuffer); _depthView = new DepthStencilView(Direct2D1Platform.Direct3D11Device, _depthBuffer);
// Setup targets and viewport for rendering // Setup targets and viewport for rendering
context.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f)); context.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f));
context.OutputMerger.SetTargets(depthView, renderView); context.OutputMerger.SetTargets(_depthView, _renderView);
// Setup new projection matrix with correct aspect ratio // Setup new projection matrix with correct aspect ratio
_proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f); _proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f);
using (var dxgiBackBuffer = _swapChain.GetBackBuffer<Surface>(0)) using (var dxgiBackBuffer = _swapChain.GetBackBuffer<Surface>(0))
{ {
_d2dRenderTarget = new RenderTarget(AvaloniaLocator.Current.GetService<SharpDX.Direct2D1.Factory>() var renderTarget = new SharpDX.Direct2D1.RenderTarget(
, dxgiBackBuffer, new RenderTargetProperties Direct2D1Platform.Direct2D1Factory,
dxgiBackBuffer,
new RenderTargetProperties
{ {
DpiX = 96, DpiX = 96,
DpiY = 96, DpiY = 96,
Type = RenderTargetType.Default, Type = RenderTargetType.Default,
PixelFormat = new PixelFormat(Format.Unknown, AlphaMode.Premultiplied) PixelFormat = new PixelFormat(
Format.Unknown,
AlphaMode.Premultiplied)
}); });
}
_deviceContext = renderTarget.QueryInterface<DeviceContext>();
renderTarget.Dispose();
}
} }
class D3DRenderTarget: IRenderTarget private class D3DRenderTarget : IRenderTarget
{ {
private readonly MainWindow _window; private readonly MainWindow _window;
@ -245,16 +269,14 @@ namespace Direct3DInteropSample
{ {
_window = window; _window = window;
} }
public void Dispose() public void Dispose()
{ {
} }
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{ {
return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget, return new DrawingContextImpl(visualBrushRenderer, null, _window._deviceContext);
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>(),
AvaloniaLocator.Current.GetService<ImagingFactory>());
} }
} }

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

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

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

@ -1,16 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Text;
using System.Threading; using System.Threading;
using Android.App;
using Android.Content;
using Android.OS; using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
@ -88,4 +79,3 @@ namespace Avalonia.Android
public event Action<DispatcherPriority?> Signaled; 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.Controls;
using Avalonia.Platform;
using Avalonia.Shared.PlatformSupport; using Avalonia.Shared.PlatformSupport;
namespace Avalonia 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.App;
using Android.Content;
using Android.OS; using Android.OS;
using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Widget;
namespace Avalonia.Android namespace Avalonia.Android
{ {
@ -48,4 +41,4 @@ namespace Avalonia.Android
return View.DispatchKeyEvent(e); return View.DispatchKeyEvent(e);
} }
} }
} }

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

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

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

@ -9,4 +9,4 @@ namespace Avalonia.Android
public IPlatformHandle GetCursor(StandardCursorType cursorType) public IPlatformHandle GetCursor(StandardCursorType cursorType)
=> new PlatformHandle(IntPtr.Zero, "ZeroCursor"); => 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.Content;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Platform; using Avalonia.Platform;
using System.Threading.Tasks;
namespace Avalonia.Android.Platform namespace Avalonia.Android.Platform
{ {
@ -44,4 +44,4 @@ namespace Avalonia.Android.Platform
return Task.FromResult<object>(null); 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 Android.InputMethodServices;
using System.Collections.Generic; using System.Collections.Generic;
using Android.Views; using Android.Views;
using Avalonia.Input;
namespace Avalonia.Android.Platform.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> private static readonly Dictionary<Keycode, Key> KeyDic = new Dictionary<Keycode, Key>
{ {
// { Keycode.Cancel?, Key.Cancel }, // { Keycode.Cancel?, Key.Cancel },

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

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

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

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

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

@ -1,15 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Widget;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Platform; using Avalonia.Platform;
@ -116,4 +109,4 @@ namespace Avalonia.Android.Platform.SkiaPlatform
//Not supported //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.Content;
using Android.Graphics; using Android.Graphics;
using Android.Views; using Android.Views;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Platform; 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; using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
@ -196,4 +193,4 @@ namespace Avalonia.Android.Platform.SkiaPlatform
ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(_view.Holder.Surface); 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.Content;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
@ -7,8 +9,6 @@ using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Platform; using Avalonia.Platform;
using System;
using System.ComponentModel;
namespace Avalonia.Android.Platform.Specific.Helpers namespace Avalonia.Android.Platform.Specific.Helpers
{ {
@ -120,7 +120,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
_lastFocusedElement = element; _lastFocusedElement = element;
} }
public void ActivateAutoShowKeybord() public void ActivateAutoShowKeyboard()
{ {
var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged); var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged);
@ -142,4 +142,4 @@ namespace Avalonia.Android.Platform.Specific.Helpers
HandleEvents = false; 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 Android.Views;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using System;
namespace Avalonia.Android.Platform.Specific.Helpers namespace Avalonia.Android.Platform.Specific.Helpers
{ {
@ -131,4 +129,4 @@ namespace Avalonia.Android.Platform.Specific.Helpers
HandleEvents = false; HandleEvents = false;
} }
} }
} }

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

@ -1,16 +1,9 @@
using Android.Graphics;
using Android.Views; using Android.Views;
using System;
using System.Collections.Generic;
namespace Avalonia.Android.Platform.Specific namespace Avalonia.Android.Platform.Specific
{ {
public interface IAndroidView public interface IAndroidView
{ {
View View { get; } 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 System.IO;
using Avalonia.Platform;
namespace Avalonia.Android namespace Avalonia.Android
{ {
@ -54,4 +43,4 @@ namespace Avalonia.Android
stream.CopyTo(outputStream); stream.CopyTo(outputStream);
} }
} }
} }

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

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

53
src/Avalonia.Animation/Animatable.cs

@ -1,48 +1,20 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Animation; using Avalonia.Data;
using System.Collections.Generic;
using System.Threading;
using System.Collections.Concurrent;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
/// Base class for control which can have property transitions. /// Base class for all animatable objects.
/// </summary> /// </summary>
public class Animatable : AvaloniaObject 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> /// <summary>
/// Defines the <see cref="PlayState"/> property. /// Defines the <see cref="PlayState"/> property.
/// </summary> /// </summary>
@ -62,27 +34,25 @@ namespace Avalonia.Animation
{ {
get { return _playState; } get { return _playState; }
set { SetAndRaise(PlayStateProperty, ref _playState, value); } set { SetAndRaise(PlayStateProperty, ref _playState, value); }
} }
/// <summary> /// <summary>
/// Defines the <see cref="Transitions"/> property. /// Defines the <see cref="Transitions"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<Animatable, IEnumerable<ITransition>> TransitionsProperty = public static readonly DirectProperty<Animatable, Transitions> TransitionsProperty =
AvaloniaProperty.RegisterDirect<Animatable, IEnumerable<ITransition>>( AvaloniaProperty.RegisterDirect<Animatable, Transitions>(
nameof(Transitions), nameof(Transitions),
o => o.Transitions, o => o.Transitions,
(o, v) => o.Transitions = v); (o, v) => o.Transitions = v);
private IEnumerable<ITransition> _transitions = new AvaloniaList<ITransition>(); private Transitions _transitions;
/// <summary> /// <summary>
/// Gets or sets the property transitions for the control. /// Gets or sets the property transitions for the control.
/// </summary> /// </summary>
public IEnumerable<ITransition> Transitions public Transitions Transitions
{ {
get { return _transitions; } get { return _transitions ?? (_transitions = new Transitions()); }
set { SetAndRaise(TransitionsProperty, ref _transitions, value); } 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. // 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. // 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Reactive.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Collections;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
@ -21,6 +17,60 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public class Animation : AvaloniaList<KeyFrame>, IAnimation 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)> 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) ) ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
@ -44,38 +94,6 @@ namespace Avalonia.Animation
return null; 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) private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
{ {
var handlerList = new List<(Type type, AvaloniaProperty property)>(); 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;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Metadata;
using Avalonia.Collections;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Reactive; using Avalonia.Reactive;
@ -29,6 +25,7 @@ namespace Avalonia.Animation
Cue = cue; Cue = cue;
} }
internal bool isNeutral;
public Type AnimatorType { get; } public Type AnimatorType { get; }
public Cue Cue { get; } public Cue Cue { get; }
public AvaloniaProperty Property { get; private set; } 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;
using System.Collections.Generic; 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.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Utils;
using Avalonia.Collections;
using Avalonia.Data; using Avalonia.Data;
using System.Reactive.Disposables;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
@ -19,9 +16,9 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// List of type-converted keyframes. /// List of type-converted keyframes.
/// </summary> /// </summary>
private readonly SortedList<double, (AnimatorKeyFrame, bool isNeutral)> _convertedKeyframes = new SortedList<double, (AnimatorKeyFrame, bool)>(); private readonly List<AnimatorKeyFrame> _convertedKeyframes = new List<AnimatorKeyFrame>();
private bool _isVerfifiedAndConverted; private bool _isVerifiedAndConverted;
/// <summary> /// <summary>
/// Gets or sets the target property for the keyframe. /// Gets or sets the target property for the keyframe.
@ -31,18 +28,17 @@ namespace Avalonia.Animation
public Animator() public Animator()
{ {
// Invalidate keyframes when changed. // Invalidate keyframes when changed.
this.CollectionChanged += delegate { _isVerfifiedAndConverted = false; }; this.CollectionChanged += delegate { _isVerifiedAndConverted = false; };
} }
/// <inheritdoc/> /// <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(); VerifyConvertKeyFrames();
return obsMatch return match
// Ignore triggers when global timers are paused. .Where(p => p)
.Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause)
.Subscribe(_ => .Subscribe(_ =>
{ {
var timerObs = RunKeyFrames(animation, control, onComplete); 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> /// <param name="t">The time parameter, relative to the total animation time</param>
protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t) protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t)
{ {
KeyValuePair<double, (AnimatorKeyFrame frame, bool isNeutral)> firstCue, lastCue; AnimatorKeyFrame firstCue, lastCue ;
int kvCount = _convertedKeyframes.Count; int kvCount = _convertedKeyframes.Count;
if (kvCount > 2) if (kvCount > 2)
{ {
if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0) if (t <= 0.0)
{ {
firstCue = _convertedKeyframes.First(); firstCue = _convertedKeyframes[0];
lastCue = _convertedKeyframes.Skip(1).First(); lastCue = _convertedKeyframes[1];
} }
else if (DoubleUtils.AboutEqual(t, 1.0) || t > 1.0) else if (t >= 1.0)
{ {
firstCue = _convertedKeyframes.Skip(kvCount - 2).First(); firstCue = _convertedKeyframes[_convertedKeyframes.Count - 2];
lastCue = _convertedKeyframes.Last(); lastCue = _convertedKeyframes[_convertedKeyframes.Count - 1];
} }
else else
{ {
firstCue = _convertedKeyframes.Last(j => j.Key <= t); (double time, int index) maxval = (0.0d, 0);
lastCue = _convertedKeyframes.First(j => j.Key >= t); 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 else
{ {
firstCue = _convertedKeyframes.First(); firstCue = _convertedKeyframes[0];
lastCue = _convertedKeyframes.Last(); lastCue = _convertedKeyframes[1];
} }
double t0 = firstCue.Key; double t0 = firstCue.Cue.CueValue;
double t1 = lastCue.Key; double t1 = lastCue.Cue.CueValue;
var intraframeTime = (t - t0) / (t1 - t0); var intraframeTime = (t - t0) / (t1 - t0);
var firstFrameData = (firstCue.Value.frame.GetTypedValue<T>(), firstCue.Value.isNeutral); var firstFrameData = (firstCue.GetTypedValue<T>(), firstCue.isNeutral);
var lastFrameData = (lastCue.Value.frame.GetTypedValue<T>(), lastCue.Value.isNeutral); var lastFrameData = (lastCue.GetTypedValue<T>(), lastCue.isNeutral);
return (intraframeTime, new KeyFramePair<T>(firstFrameData, lastFrameData)); return (intraframeTime, new KeyFramePair<T>(firstFrameData, lastFrameData));
} }
/// <summary> /// <summary>
/// Runs the KeyFrames Animation. /// Runs the KeyFrames Animation.
/// </summary> /// </summary>
private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete) private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete)
{ {
var stateMachine = new AnimatorStateMachine<T>(animation, control, this, onComplete); var instance = new AnimationInstance<T>(animation, control, this, onComplete, DoInterpolation);
return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
Timing.AnimationStateTimer
.TakeWhile(_ => !stateMachine._unsubscribe)
.Subscribe(p => stateMachine.Step(p, DoInterpolation));
return control.Bind(Property, stateMachine, BindingPriority.Animation);
} }
/// <summary> /// <summary>
@ -114,18 +113,26 @@ namespace Avalonia.Animation
protected abstract T DoInterpolation(double time, T neutralValue); protected abstract T DoInterpolation(double time, T neutralValue);
/// <summary> /// <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> /// </summary>
private void VerifyConvertKeyFrames() private void VerifyConvertKeyFrames()
{ {
foreach (AnimatorKeyFrame keyframe in this) foreach (AnimatorKeyFrame keyframe in this)
{ {
_convertedKeyframes.Add(keyframe.Cue.CueValue, (keyframe, false)); _convertedKeyframes.Add(keyframe);
} }
AddNeutralKeyFramesIfNeeded(); 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() private void AddNeutralKeyFramesIfNeeded()
@ -133,14 +140,14 @@ namespace Avalonia.Animation
bool hasStartKey, hasEndKey; bool hasStartKey, hasEndKey;
hasStartKey = hasEndKey = false; hasStartKey = hasEndKey = false;
// Make start and end keyframe mandatory. // Check if there's start and end keyframes.
foreach (var converted in _convertedKeyframes.Keys) foreach (var frame in _convertedKeyframes)
{ {
if (DoubleUtils.AboutEqual(converted, 0.0)) if (frame.Cue.CueValue == 0.0d)
{ {
hasStartKey = true; hasStartKey = true;
} }
else if (DoubleUtils.AboutEqual(converted, 1.0)) else if (frame.Cue.CueValue == 1.0d)
{ {
hasEndKey = true; hasEndKey = true;
} }
@ -154,12 +161,12 @@ namespace Avalonia.Animation
{ {
if (!hasStartKey) 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) 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;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Text;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
/// A Cue object for <see cref="KeyFrame"/>. /// Determines the time index for a <see cref="KeyFrame"/>.
/// </summary> /// </summary>
[TypeConverter(typeof(CueTypeConverter))] [TypeConverter(typeof(CueTypeConverter))]
public readonly struct Cue : IEquatable<Cue>, IEquatable<double> public readonly struct Cue : IEquatable<Cue>, IEquatable<double>
@ -84,5 +82,4 @@ namespace Avalonia.Animation
return Cue.Parse((string)value, culture); return Cue.Parse((string)value, culture);
} }
} }
}
}

11
src/Avalonia.Animation/DoubleAnimator.cs

@ -1,13 +1,4 @@
using System; namespace Avalonia.Animation
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
{ {
/// <summary> /// <summary>
/// Animator that handles <see cref="double"/> properties. /// 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. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Metadata;
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;

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

@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Animation.Utils; using Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings namespace Avalonia.Animation.Easings

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

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

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

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

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

@ -1,8 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Animation.Utils;
using System; using System;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings namespace Avalonia.Animation.Easings
{ {

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

@ -1,8 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Animation.Utils;
using System; using System;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings namespace Avalonia.Animation.Easings
{ {

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

@ -1,8 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Animation.Utils;
using System; using System;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings namespace Avalonia.Animation.Easings
{ {

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

@ -1,8 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Animation.Utils;
using System; using System;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation.Easings namespace Avalonia.Animation.Easings
{ {

6
src/Avalonia.Animation/FillMode.cs

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

1
src/Avalonia.Animation/FloatTransition.cs

@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Metadata;
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;

2
src/Avalonia.Animation/IAnimation.cs

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

1
src/Avalonia.Animation/IAnimator.cs

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

2
src/Avalonia.Animation/ITransition.cs

@ -1,9 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Metadata;
using System; using System;
using System.Reactive.Linq;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {

1
src/Avalonia.Animation/IntegerTransition.cs

@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Metadata;
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;

3
src/Avalonia.Animation/KeyFrame.cs

@ -1,8 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using Avalonia.Metadata;
using Avalonia.Collections; using Avalonia.Collections;
namespace Avalonia.Animation 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 namespace Avalonia.Animation
{ {
/// <summary> /// <summary>

8
src/Avalonia.Animation/PlayState.cs

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

6
src/Avalonia.Animation/PlaybackDirection.cs

@ -1,8 +1,4 @@
using System; namespace Avalonia.Animation
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
/// Determines the playback direction of an animation. /// 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. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Linq;
namespace Avalonia.Animation 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. // 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. // 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;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
@ -20,4 +19,4 @@ namespace Avalonia.Animation
return RepeatCount.Parse((string)value); 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Threading; using Avalonia.Threading;
namespace Avalonia.Animation namespace Avalonia.Animation
@ -15,9 +13,6 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public static class Timing public static class Timing
{ {
static ulong _transitionsFrameCount;
static PlayState _globalState = PlayState.Run;
/// <summary> /// <summary>
/// The number of frames per second. /// The number of frames per second.
/// </summary> /// </summary>
@ -32,41 +27,16 @@ namespace Avalonia.Animation
/// Initializes static members of the <see cref="Timing"/> class. /// Initializes static members of the <see cref="Timing"/> class.
/// </summary> /// </summary>
static Timing() static Timing()
{ {
var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance);
AnimationStateTimer = globalTimer AnimationsTimer = globalTimer
.Select(_ => .Select(_ => GetTickCount())
{
return _globalState;
})
.Publish() .Publish()
.RefCount(); .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> internal static TimeSpan GetTickCount() => TimeSpan.FromMilliseconds(Environment.TickCount);
/// Gets the animation play state for all animations
/// </summary>
public static PlayState GetGlobalPlayState()
{
Dispatcher.UIThread.VerifyAccess();
return _globalState;
}
/// <summary> /// <summary>
/// Gets the animation timer. /// Gets the animation timer.
@ -76,48 +46,9 @@ namespace Avalonia.Animation
/// defined in <see cref="FramesPerSecond"/>. /// defined in <see cref="FramesPerSecond"/>.
/// The parameter passed to a subsciber is the current playstate of the animation. /// The parameter passed to a subsciber is the current playstate of the animation.
/// </remarks> /// </remarks>
internal static IObservable<PlayState> AnimationStateTimer internal static IObservable<TimeSpan> AnimationsTimer
{ {
get; 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. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Metadata;
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
@ -24,17 +24,7 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Gets the easing class to be used. /// Gets the easing class to be used.
/// </summary> /// </summary>
public Easing Easing public Easing Easing { get; set; } = new LinearEasing();
{
get
{
return _easing ?? (_easing = new LinearEasing());
}
set
{
_easing = value;
}
}
/// <inheritdocs/> /// <inheritdocs/>
public AvaloniaProperty Property public AvaloniaProperty Property
@ -61,8 +51,10 @@ namespace Avalonia.Animation
/// <inheritdocs/> /// <inheritdocs/>
public virtual IDisposable Apply(Animatable control, object oldValue, object newValue) 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); var transition = DoTransition(new TransitionInstance(Duration), (T)oldValue, (T)newValue);
return control.Bind(Property, transition, Data.BindingPriority.Animation); 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> /// <summary>
/// Half of <see cref="Math.PI"/> /// Half of <see cref="Math.PI"/>
/// </summary> /// </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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins namespace Avalonia.Data.Core.Plugins
{ {

7
src/Avalonia.Base/PriorityBindingEntry.cs

@ -50,6 +50,11 @@ namespace Avalonia
get; get;
} }
/// <summary>
/// Gets a value indicating whether the binding has completed.
/// </summary>
public bool HasCompleted { get; private set; }
/// <summary> /// <summary>
/// The current value of the binding. /// The current value of the binding.
/// </summary> /// </summary>
@ -129,6 +134,8 @@ namespace Avalonia
private void Completed() private void Completed()
{ {
HasCompleted = true;
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {
_owner.Completed(this); _owner.Completed(this);

14
src/Avalonia.Base/PriorityLevel.cs

@ -112,12 +112,16 @@ namespace Avalonia
return Disposable.Create(() => return Disposable.Create(() =>
{ {
Bindings.Remove(node); if (!entry.HasCompleted)
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
{ {
ActivateFirstBinding(); Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
{
ActivateFirstBinding();
}
} }
}); });
} }

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

@ -92,6 +92,20 @@ namespace Avalonia.Threading
return _jobRunner.InvokeAsync(function, priority); 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/> /// <inheritdoc/>
public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) 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="function">The method.</param>
/// <param name="priority">The priority with which to invoke 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); 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);
} }
} }

8
src/Avalonia.Controls/Border.cs

@ -43,8 +43,12 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static Border() static Border()
{ {
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); AffectsRender<Border>(
AffectsMeasure(BorderThicknessProperty); BackgroundProperty,
BorderBrushProperty,
BorderThicknessProperty,
CornerRadiusProperty);
AffectsMeasure<Border>(BorderThicknessProperty);
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/Button.cs

@ -80,7 +80,7 @@ namespace Avalonia.Controls
FocusableProperty.OverrideDefaultValue(typeof(Button), true); FocusableProperty.OverrideDefaultValue(typeof(Button), true);
CommandProperty.Changed.Subscribe(CommandChanged); CommandProperty.Changed.Subscribe(CommandChanged);
IsDefaultProperty.Changed.Subscribe(IsDefaultChanged); IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
PseudoClass(IsPressedProperty, ":pressed"); PseudoClass<Button>(IsPressedProperty, ":pressed");
} }
/// <summary> /// <summary>

6
src/Avalonia.Controls/ButtonSpinner.cs

@ -85,8 +85,8 @@ namespace Avalonia.Controls
static ButtonSpinner() static ButtonSpinner()
{ {
AllowSpinProperty.Changed.Subscribe(AllowSpinChanged); AllowSpinProperty.Changed.Subscribe(AllowSpinChanged);
PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left"); PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left");
PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right"); PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right");
} }
/// <summary> /// <summary>
@ -260,4 +260,4 @@ namespace Avalonia.Controls
} }
} }
} }
} }

26
src/Avalonia.Controls/Canvas.cs

@ -48,7 +48,7 @@ namespace Avalonia.Controls
static Canvas() static Canvas()
{ {
ClipToBoundsProperty.OverrideDefaultValue<Canvas>(false); ClipToBoundsProperty.OverrideDefaultValue<Canvas>(false);
AffectsCanvasArrange(LeftProperty, TopProperty, RightProperty, BottomProperty); AffectsParentArrange<Canvas>(LeftProperty, TopProperty, RightProperty, BottomProperty);
} }
/// <summary> /// <summary>
@ -207,29 +207,5 @@ namespace Avalonia.Controls
return finalSize; 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() static ContentControl()
{ {
ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren); ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren);
PseudoClass<ContentControl, object>(ContentProperty, x => x != null, ":valid");
PseudoClass<ContentControl, object>(ContentProperty, x => x == null, ":invalid");
} }
/// <summary> /// <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();
}
}
}

2
src/Avalonia.Controls/Decorator.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static Decorator() static Decorator()
{ {
AffectsMeasure(ChildProperty, PaddingProperty); AffectsMeasure<Decorator>(ChildProperty, PaddingProperty);
ChildProperty.Changed.AddClassHandler<Decorator>(x => x.ChildChanged); ChildProperty.Changed.AddClassHandler<Decorator>(x => x.ChildChanged);
} }

4
src/Avalonia.Controls/DockPanel.cs

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

8
src/Avalonia.Controls/DrawingPresenter.cs

@ -8,8 +8,8 @@ namespace Avalonia.Controls
{ {
static DrawingPresenter() static DrawingPresenter()
{ {
AffectsMeasure(DrawingProperty); AffectsMeasure<DrawingPresenter>(DrawingProperty);
AffectsRender(DrawingProperty); AffectsRender<DrawingPresenter>(DrawingProperty);
} }
public static readonly StyledProperty<Drawing> DrawingProperty = public static readonly StyledProperty<Drawing> DrawingProperty =
@ -49,11 +49,11 @@ namespace Avalonia.Controls
if (Drawing != null) if (Drawing != null)
{ {
using (context.PushPreTransform(_transform)) using (context.PushPreTransform(_transform))
using (context.PushClip(Bounds)) using (context.PushClip(new Rect(Bounds.Size)))
{ {
Drawing.Draw(context); Drawing.Draw(context);
} }
} }
} }
} }
} }

10
src/Avalonia.Controls/Expander.cs

@ -35,12 +35,12 @@ namespace Avalonia.Controls
static Expander() static Expander()
{ {
PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down"); PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down");
PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Up, ":up"); PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Up, ":up");
PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Left, ":left"); PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Left, ":left");
PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Right, ":right"); PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Right, ":right");
PseudoClass(IsExpandedProperty, ":expanded"); PseudoClass<Expander>(IsExpandedProperty, ":expanded");
IsExpandedProperty.Changed.AddClassHandler<Expander>(x => x.OnIsExpandedChanged); IsExpandedProperty.Changed.AddClassHandler<Expander>(x => x.OnIsExpandedChanged);
} }

5
src/Avalonia.Controls/Grid.cs

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

3
src/Avalonia.Controls/Image.cs

@ -25,8 +25,7 @@ namespace Avalonia.Controls
static Image() static Image()
{ {
AffectsRender(SourceProperty); AffectsRender<Image>(SourceProperty, StretchProperty);
AffectsRender(StretchProperty);
} }
/// <summary> /// <summary>

38
src/Avalonia.Controls/MenuItem.cs

@ -99,6 +99,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach<MenuItem>(IsSelectedProperty); SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
CommandProperty.Changed.Subscribe(CommandChanged); CommandProperty.Changed.Subscribe(CommandChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true); FocusableProperty.OverrideDefaultValue<MenuItem>(true);
HeaderProperty.Changed.AddClassHandler<MenuItem>(x => x.HeaderChanged);
IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged); IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
IsSelectedProperty.Changed.AddClassHandler<MenuItem>(x => x.IsSelectedChanged); IsSelectedProperty.Changed.AddClassHandler<MenuItem>(x => x.IsSelectedChanged);
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel); ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
@ -357,10 +358,21 @@ namespace Avalonia.Controls
{ {
base.OnTemplateApplied(e); base.OnTemplateApplied(e);
_popup = e.NameScope.Get<Popup>("PART_Popup"); if (_popup != null)
_popup.DependencyResolver = DependencyResolver.Instance; {
_popup.Opened += PopupOpened; _popup.Opened -= PopupOpened;
_popup.Closed += PopupClosed; _popup.Closed -= PopupClosed;
_popup.DependencyResolver = null;
}
_popup = e.NameScope.Find<Popup>("PART_Popup");
if (_popup != null)
{
_popup.DependencyResolver = DependencyResolver.Instance;
_popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
}
} }
/// <summary> /// <summary>
@ -408,6 +420,24 @@ namespace Avalonia.Controls
IsEnabled = Command == null || Command.CanExecute(CommandParameter); IsEnabled = Command == null || Command.CanExecute(CommandParameter);
} }
/// <summary>
/// Called when the <see cref="Header"/> property changes.
/// </summary>
/// <param name="e">The property change event.</param>
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.NewValue is string newValue && newValue == "-")
{
PseudoClasses.Add(":separator");
Focusable = false;
}
else if (e.OldValue is string oldValue && oldValue == "-")
{
PseudoClasses.Remove(":separator");
Focusable = true;
}
}
/// <summary> /// <summary>
/// Called when the <see cref="Icon"/> property changes. /// Called when the <see cref="Icon"/> property changes.
/// </summary> /// </summary>

43
src/Avalonia.Controls/Panel.cs

@ -30,6 +30,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static Panel() static Panel()
{ {
AffectsRender<Panel>(BackgroundProperty);
ClipToBoundsProperty.OverrideDefaultValue<Panel>(true); ClipToBoundsProperty.OverrideDefaultValue<Panel>(true);
} }
@ -72,6 +73,32 @@ namespace Avalonia.Controls
base.Render(context); 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> /// <summary>
/// Called when the <see cref="Children"/> collection changes. /// Called when the <see cref="Children"/> collection changes.
/// </summary> /// </summary>
@ -116,5 +143,21 @@ namespace Avalonia.Controls
InvalidateMeasure(); 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();
}
} }
} }

4
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -90,8 +90,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
static ContentPresenter() static ContentPresenter()
{ {
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure(BorderThicknessProperty, PaddingProperty); AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged); ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged); ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged); TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);

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

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

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

@ -72,7 +72,7 @@ namespace Avalonia.Controls.Presenters
{ {
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true); ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>(x => x.ChildChanged); ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>(x => x.ChildChanged);
AffectsArrange(OffsetProperty); AffectsArrange<ScrollContentPresenter>(OffsetProperty);
} }
/// <summary> /// <summary>

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

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

2
src/Avalonia.Controls/Primitives/AccessText.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
static AccessText() static AccessText()
{ {
AffectsRender(ShowAccessKeyProperty); AffectsRender<AccessText>(ShowAccessKeyProperty);
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -54,8 +54,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
static ScrollBar() static ScrollBar()
{ {
PseudoClass(OrientationProperty, o => o == Orientation.Vertical, ":vertical"); PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal"); PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragDelta, RoutingStrategies.Bubble); Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragDelta, RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragComplete, RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragComplete, RoutingStrategies.Bubble);

6
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -24,9 +24,9 @@ namespace Avalonia.Controls.Primitives
static ToggleButton() static ToggleButton()
{ {
PseudoClass(IsCheckedProperty, c => c == true, ":checked"); PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == true, ":checked");
PseudoClass(IsCheckedProperty, c => c == false, ":unchecked"); PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == false, ":unchecked");
PseudoClass(IsCheckedProperty, c => c == null, ":indeterminate"); PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == null, ":indeterminate");
} }
public bool? IsChecked public bool? IsChecked

4
src/Avalonia.Controls/Primitives/Track.cs

@ -39,10 +39,12 @@ namespace Avalonia.Controls.Primitives
static Track() static Track()
{ {
PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
ThumbProperty.Changed.AddClassHandler<Track>(x => x.ThumbChanged); ThumbProperty.Changed.AddClassHandler<Track>(x => x.ThumbChanged);
IncreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged); IncreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged);
DecreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged); DecreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged);
AffectsArrange(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty); AffectsArrange<Track>(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty);
} }
public double Minimum public double Minimum

6
src/Avalonia.Controls/ProgressBar.cs

@ -33,9 +33,9 @@ namespace Avalonia.Controls
static ProgressBar() static ProgressBar()
{ {
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical"); PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal"); PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
PseudoClass(IsIndeterminateProperty, ":indeterminate"); PseudoClass<ProgressBar>(IsIndeterminateProperty, ":indeterminate");
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged); ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
} }

5
src/Avalonia.Controls/Shapes/Shape.cs

@ -30,11 +30,10 @@ namespace Avalonia.Controls.Shapes
private Geometry _renderedGeometry; private Geometry _renderedGeometry;
bool _calculateTransformOnArrange = false; bool _calculateTransformOnArrange = false;
static Shape() static Shape()
{ {
AffectsMeasure(StretchProperty, StrokeThicknessProperty); AffectsMeasure<Shape>(StretchProperty, StrokeThicknessProperty);
AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty); AffectsRender<Shape>(FillProperty, StrokeProperty, StrokeDashArrayProperty);
} }
public Geometry DefiningGeometry public Geometry DefiningGeometry

10
src/Avalonia.Controls/Slider.cs

@ -42,6 +42,8 @@ namespace Avalonia.Controls
static Slider() static Slider()
{ {
OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
Thumb.DragStartedEvent.AddClassHandler<Slider>(x => x.OnThumbDragStarted, RoutingStrategies.Bubble); Thumb.DragStartedEvent.AddClassHandler<Slider>(x => x.OnThumbDragStarted, RoutingStrategies.Bubble);
Thumb.DragDeltaEvent.AddClassHandler<Slider>(x => x.OnThumbDragDelta, RoutingStrategies.Bubble); Thumb.DragDeltaEvent.AddClassHandler<Slider>(x => x.OnThumbDragDelta, RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler<Slider>(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble);
@ -171,11 +173,7 @@ namespace Avalonia.Controls
/// <param name="value">Value that want to snap to closest Tick.</param> /// <param name="value">Value that want to snap to closest Tick.</param>
private void MoveToNextTick(double value) private void MoveToNextTick(double value)
{ {
double next = SnapToTick(Math.Max(Minimum, Math.Min(Maximum, value))); Value = SnapToTick(Math.Max(Minimum, Math.Min(Maximum, value)));
if (next != value)
{
Value = next;
}
} }
/// <summary> /// <summary>
@ -194,4 +192,4 @@ namespace Avalonia.Controls
return value; return value;
} }
} }
} }

4
src/Avalonia.Controls/StackPanel.cs

@ -29,8 +29,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static StackPanel() static StackPanel()
{ {
AffectsMeasure(SpacingProperty); AffectsMeasure<StackPanel>(SpacingProperty);
AffectsMeasure(OrientationProperty); AffectsMeasure<StackPanel>(OrientationProperty);
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/TabControl.cs

@ -44,7 +44,7 @@ namespace Avalonia.Controls
{ {
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected); SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
FocusableProperty.OverrideDefaultValue<TabControl>(false); FocusableProperty.OverrideDefaultValue<TabControl>(false);
AffectsMeasure(TabStripPlacementProperty); AffectsMeasure<TabControl>(TabStripPlacementProperty);
} }
/// <summary> /// <summary>

13
src/Avalonia.Controls/TextBlock.cs

@ -6,6 +6,7 @@ using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -65,7 +66,7 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<IBrush> ForegroundProperty = public static readonly AttachedProperty<IBrush> ForegroundProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, IBrush>( AvaloniaProperty.RegisterAttached<TextBlock, Control, IBrush>(
nameof(Foreground), nameof(Foreground),
new SolidColorBrush(0xff000000), Brushes.Black,
inherits: true); inherits: true);
/// <summary> /// <summary>
@ -99,10 +100,12 @@ namespace Avalonia.Controls
static TextBlock() static TextBlock()
{ {
ClipToBoundsProperty.OverrideDefaultValue<TextBlock>(true); ClipToBoundsProperty.OverrideDefaultValue<TextBlock>(true);
AffectsRender(ForegroundProperty); AffectsRender<TextBlock>(
AffectsRender(FontWeightProperty); BackgroundProperty,
AffectsRender(FontSizeProperty); ForegroundProperty,
AffectsRender(FontStyleProperty); FontWeightProperty,
FontSizeProperty,
FontStyleProperty);
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/TopLevel.cs

@ -59,7 +59,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static TopLevel() static TopLevel()
{ {
AffectsMeasure(ClientSizeProperty); AffectsMeasure<TopLevel>(ClientSizeProperty);
} }
/// <summary> /// <summary>

42
src/Avalonia.Controls/TreeView.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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.Linq;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
@ -31,6 +33,14 @@ namespace Avalonia.Controls
o => o.SelectedItem, o => o.SelectedItem,
(o, v) => o.SelectedItem = v); (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; private object _selectedItem;
/// <summary> /// <summary>
@ -41,6 +51,15 @@ namespace Avalonia.Controls
// HACK: Needed or SelectedItem property will not be found in Release build. // 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> /// <summary>
/// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view. /// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view.
/// </summary> /// </summary>
@ -74,6 +93,7 @@ namespace Avalonia.Controls
MarkContainerSelected(container, false); MarkContainerSelected(container, false);
} }
var oldItem = _selectedItem;
SetAndRaise(SelectedItemProperty, ref _selectedItem, value); SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
if (_selectedItem != null) if (_selectedItem != null)
@ -86,6 +106,28 @@ namespace Avalonia.Controls
container.BringIntoView(); 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> /// </summary>
public static readonly DirectProperty<TreeViewItem, bool> IsExpandedProperty = public static readonly DirectProperty<TreeViewItem, bool> IsExpandedProperty =
AvaloniaProperty.RegisterDirect<TreeViewItem, bool>( AvaloniaProperty.RegisterDirect<TreeViewItem, bool>(
"IsExpanded", nameof(IsExpanded),
o => o.IsExpanded, o => o.IsExpanded,
(o, v) => o.IsExpanded = v); (o, v) => o.IsExpanded = v);
@ -31,17 +31,25 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsSelectedProperty = public static readonly StyledProperty<bool> IsSelectedProperty =
ListBoxItem.IsSelectedProperty.AddOwner<TreeViewItem>(); 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 = private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel()); new FuncTemplate<IPanel>(() => new StackPanel());
private TreeView _treeView; private TreeView _treeView;
private bool _isExpanded; private bool _isExpanded;
private int _level;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TreeViewItem"/> class. /// Initializes static members of the <see cref="TreeViewItem"/> class.
/// </summary> /// </summary>
static TreeViewItem() static TreeViewItem()
{ {
SelectableMixin.Attach<TreeViewItem>(IsSelectedProperty); SelectableMixin.Attach<TreeViewItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue<TreeViewItem>(true); FocusableProperty.OverrideDefaultValue<TreeViewItem>(true);
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel); ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
@ -65,6 +73,15 @@ namespace Avalonia.Controls
set { SetValue(IsSelectedProperty, value); } 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> /// <summary>
/// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view. /// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view.
/// </summary> /// </summary>
@ -89,6 +106,8 @@ namespace Avalonia.Controls
base.OnAttachedToLogicalTree(e); base.OnAttachedToLogicalTree(e);
_treeView = this.GetLogicalAncestors().OfType<TreeView>().FirstOrDefault(); _treeView = this.GetLogicalAncestors().OfType<TreeView>().FirstOrDefault();
Level = CalculateDistanceFromLogicalParent<TreeView>(this) - 1;
if (ItemTemplate == null && _treeView?.ItemTemplate != null) if (ItemTemplate == null && _treeView?.ItemTemplate != null)
{ {
ItemTemplate = _treeView.ItemTemplate; ItemTemplate = _treeView.ItemTemplate;
@ -126,5 +145,18 @@ namespace Avalonia.Controls
// Don't call base.OnKeyDown - let events bubble up to containing TreeView. // 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;
}
} }
} }

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

Loading…
Cancel
Save