Browse Source

Merge branch 'master' into patch-1

pull/1241/head
ARSolog 9 years ago
committed by GitHub
parent
commit
ff50697a90
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      appveyor.yml
  2. 2
      build/MonoMac.props
  3. 5
      build/Splat.props
  4. 4
      packages.cake
  5. 1
      samples/BindingTest/BindingTest.csproj
  6. 6
      samples/ControlCatalog/ControlCatalog.csproj
  7. 1
      samples/ControlCatalog/MainView.xaml
  8. 47
      samples/ControlCatalog/Pages/CalendarPage.xaml
  9. 28
      samples/ControlCatalog/Pages/CalendarPage.xaml.cs
  10. 10
      samples/ControlCatalog/Pages/CarouselPage.xaml
  11. 5
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  12. 25
      samples/ControlCatalog/Pages/CheckBoxPage.xaml
  13. 2
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  14. 24
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  15. 1
      samples/RenderTest/RenderTest.csproj
  16. 1
      samples/VirtualizationTest/VirtualizationTest.csproj
  17. 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  18. 1
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  19. 2
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  20. 17
      src/Avalonia.Base/Data/IndexerBinding.cs
  21. 155
      src/Avalonia.Base/Data/InstancedBinding.cs
  22. 6
      src/Avalonia.Base/Threading/Dispatcher.cs
  23. 2132
      src/Avalonia.Controls/Calendar/Calendar.cs
  24. 215
      src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
  25. 193
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  26. 79
      src/Avalonia.Controls/Calendar/CalendarDateRange.cs
  27. 253
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  28. 21
      src/Avalonia.Controls/Calendar/CalendarExtensions.cs
  29. 1264
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  30. 155
      src/Avalonia.Controls/Calendar/DateTimeHelper.cs
  31. 361
      src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
  32. 2
      src/Avalonia.Controls/Classes.cs
  33. 4
      src/Avalonia.Controls/Control.cs
  34. 36
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  35. 9
      src/Avalonia.Controls/RadioButton.cs
  36. 46
      src/Avalonia.Controls/Shapes/Shape.cs
  37. 2
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
  38. 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  39. 53
      src/Avalonia.Diagnostics/LogManager.cs
  40. 1
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  41. 3
      src/Avalonia.Remote.Protocol/BsonStreamTransport.cs
  42. 60
      src/Avalonia.Styling/Styling/Setter.cs
  43. 30
      src/Avalonia.Themes.Default/Calendar.xaml
  44. 80
      src/Avalonia.Themes.Default/CalendarButton.xaml
  45. 116
      src/Avalonia.Themes.Default/CalendarDayButton.xaml
  46. 183
      src/Avalonia.Themes.Default/CalendarItem.xaml
  47. 31
      src/Avalonia.Themes.Default/CheckBox.xaml
  48. 4
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  49. 14
      src/Avalonia.Themes.Default/RadioButton.xaml
  50. 25
      src/Avalonia.Visuals/Animation/PageSlide.cs
  51. 3
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  52. 8
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  53. 3
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  54. 4
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  55. 35
      src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs
  56. 69
      src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs
  57. 27
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  58. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  59. 31
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleResourceExtension.cs
  60. 12
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  61. 63
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/XamlBinding.cs
  62. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  63. 88
      src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
  64. 46
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  65. 32
      src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs
  66. 33
      src/OSX/Avalonia.MonoMac/RenderLoop.cs
  67. 89
      src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
  68. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  69. 277
      tests/Avalonia.Controls.UnitTests/CalendarTests.cs
  70. 10
      tests/Avalonia.Controls.UnitTests/ClassesTests.cs
  71. 44
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  72. 57
      tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs
  73. 36
      tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs
  74. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  75. 113
      tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs
  76. 1
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  77. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs
  78. 19
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  79. 165
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  80. 73
      tests/Avalonia.Styling.UnitTests/SetterTests.cs
  81. 23
      tests/Avalonia.UnitTests/TestWithServicesBase.cs

4
appveyor.yml

@ -23,10 +23,6 @@ before_build:
- git submodule update --init
build_script:
- ps: .\build.ps1 -Target "AppVeyor" -Platform "$env:platform" -Configuration "$env:configuration"
after_build:
- "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%"
- pip install codecov
- codecov -f "./artifacts/coverage.xml"
test: off
artifacts:

2
build/MonoMac.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.3" />
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
</ItemGroup>
</Project>

5
build/Splat.props

@ -1,5 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Splat" Version="2.0.0" />
</ItemGroup>
</Project>

4
packages.cake

@ -107,7 +107,6 @@ public class Packages
context.Information("Setting NuGet package dependencies versions:");
var SerilogVersion = packageVersions["Serilog"].FirstOrDefault().Item1;
var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1;
@ -121,7 +120,6 @@ public class Packages
var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1;
context.Information("Package: Serilog, version: {0}", SerilogVersion);
context.Information("Package: Splat, version: {0}", SplatVersion);
context.Information("Package: Sprache, version: {0}", SpracheVersion);
context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
context.Information("Package: reactiveui, version: {0}", ReactiveUIVersion);
@ -245,7 +243,6 @@ public class Packages
Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Serilog", Version = SerilogVersion },
new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version },
@ -253,7 +250,6 @@ public class Packages
new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp2.0", Version = "4.3.0" },
new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp2.0", Version = "1.1.0" },
new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netcoreapp2.0", Version = "1.6.0" },
new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp2.0", Version = SplatVersion },
new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp2.0", Version = SerilogVersion },
new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp2.0", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp2.0", Version = SystemReactiveVersion },

1
samples/BindingTest/BindingTest.csproj

@ -152,7 +152,6 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

6
samples/ControlCatalog/ControlCatalog.csproj

@ -44,6 +44,9 @@
<EmbeddedResource Include="Pages\ButtonPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CalendarPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CanvasPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
@ -107,6 +110,9 @@
<Compile Include="Pages\ButtonPage.xaml.cs">
<DependentUpon>ButtonPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CalendarPage.xaml.cs">
<DependentUpon>CalendarPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CanvasPage.xaml.cs">
<DependentUpon>CanvasPage.xaml</DependentUpon>
</Compile>

1
samples/ControlCatalog/MainView.xaml

@ -7,6 +7,7 @@
</TabControl.Transition>
<TabItem Header="Border"><pages:BorderPage/></TabItem>
<TabItem Header="Button"><pages:ButtonPage/></TabItem>
<TabItem Header="Calendar"><pages:CalendarPage/></TabItem>
<TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
<TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>

47
samples/ControlCatalog/Pages/CalendarPage.xaml

@ -0,0 +1,47 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">Calendar</TextBlock>
<TextBlock Classes="h2">A calendar control for selecting dates</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<StackPanel Orientation="Vertical">
<TextBlock Text="SelectionMode: None"/>
<Calendar SelectionMode="None"
Margin="0,0,0,8"/>
<TextBlock Text="SelectionMode: SingleDate"/>
<Calendar SelectionMode="SingleDate"
Margin="0,0,0,8"/>
<TextBlock Text="Disabled"/>
<Calendar IsEnabled="False"
SelectionMode="SingleDate"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="SelectionMode: SingleRange"/>
<Calendar SelectionMode="SingleRange"
Margin="0,0,0,8"/>
<TextBlock Text="SelectionMode: MultipleRange"/>
<Calendar SelectionMode="MultipleRange"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="DisplayDates"/>
<Calendar Name="DisplayDatesCalendar"
SelectionMode="SingleDate"
Margin="0,0,0,8"/>
<TextBlock Text="BlackoutDates"/>
<Calendar Name="BlackoutDatesCalendar"
SelectionMode="SingleDate" />
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

28
samples/ControlCatalog/Pages/CalendarPage.xaml.cs

@ -0,0 +1,28 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System;
namespace ControlCatalog.Pages
{
public class CalendarPage : UserControl
{
public CalendarPage()
{
this.InitializeComponent();
var today = DateTime.Today;
var cal1 = this.FindControl<Calendar>("DisplayDatesCalendar");
cal1.DisplayDateStart = today.AddDays(-25);
cal1.DisplayDateEnd = today.AddDays(25);
var cal2 = this.FindControl<Calendar>("BlackoutDatesCalendar");
cal2.BlackoutDates.AddDatesInPast();
cal2.BlackoutDates.Add(new CalendarDateRange(today.AddDays(6)));
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

10
samples/ControlCatalog/Pages/CarouselPage.xaml

@ -9,7 +9,7 @@
</Button>
<Carousel Name="carousel">
<Carousel.Transition>
<PageSlide Duration="0.25"/>
<PageSlide Duration="0.25" Orientation="Vertical" />
</Carousel.Transition>
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"/>
<Image Source="resm:ControlCatalog.Assets.hirsch-899118_640.jpg"/>
@ -28,6 +28,14 @@
<DropDownItem>Crossfade</DropDownItem>
</DropDown>
</StackPanel>
<StackPanel Orientation="Horizontal" Gap="4">
<TextBlock VerticalAlignment="Center">Orientation</TextBlock>
<DropDown Name="orientation" SelectedIndex="1" VerticalAlignment="Center">
<DropDownItem>Horizontal</DropDownItem>
<DropDownItem>Vertical</DropDownItem>
</DropDown>
</StackPanel>
</StackPanel>
</UserControl>

5
samples/ControlCatalog/Pages/CarouselPage.xaml.cs

@ -11,6 +11,7 @@ namespace ControlCatalog.Pages
private Button _left;
private Button _right;
private DropDown _transition;
private DropDown _orientation;
public CarouselPage()
{
@ -18,6 +19,7 @@ namespace ControlCatalog.Pages
_left.Click += (s, e) => _carousel.Previous();
_right.Click += (s, e) => _carousel.Next();
_transition.SelectionChanged += TransitionChanged;
_orientation.SelectionChanged += TransitionChanged;
}
private void InitializeComponent()
@ -27,6 +29,7 @@ namespace ControlCatalog.Pages
_left = this.FindControl<Button>("left");
_right = this.FindControl<Button>("right");
_transition = this.FindControl<DropDown>("transition");
_orientation = this.FindControl<DropDown>("orientation");
}
private void TransitionChanged(object sender, SelectionChangedEventArgs e)
@ -37,7 +40,7 @@ namespace ControlCatalog.Pages
_carousel.Transition = null;
break;
case 1:
_carousel.Transition = new PageSlide(TimeSpan.FromSeconds(0.25));
_carousel.Transition = new PageSlide(TimeSpan.FromSeconds(0.25), _orientation.SelectedIndex == 0 ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical);
break;
case 2:
_carousel.Transition = new CrossFade(TimeSpan.FromSeconds(0.25));

25
samples/ControlCatalog/Pages/CheckBoxPage.xaml

@ -1,15 +1,28 @@
<UserControl xmlns="https://github.com/avaloniaui">
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">CheckBox</TextBlock>
<TextBlock Classes="h2">A check box control</TextBlock>
<StackPanel Orientation="Vertical"
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<CheckBox>Unchecked</CheckBox>
<CheckBox IsChecked="True">Checked</CheckBox>
<CheckBox IsChecked="True" IsEnabled="False">Disabled</CheckBox>
</StackPanel>
<StackPanel Orientation="Vertical"
Gap="16">
<CheckBox>Unchecked</CheckBox>
<CheckBox IsChecked="True">Checked</CheckBox>
<CheckBox IsChecked="{x:Null}">Indeterminate</CheckBox>
<CheckBox IsChecked="True" IsEnabled="False">Disabled</CheckBox>
</StackPanel>
<StackPanel Orientation="Vertical"
HorizontalAlignment="Center"
Gap="16">
<CheckBox IsChecked="False" IsThreeState="True">Three State: Unchecked</CheckBox>
<CheckBox IsChecked="True" IsThreeState="True">Three State: Checked</CheckBox>
<CheckBox IsChecked="{x:Null}" IsThreeState="True">Three State: Indeterminate</CheckBox>
<CheckBox IsChecked="{x:Null}" IsThreeState="True" IsEnabled="False">Three State: Disabled</CheckBox>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

2
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -36,7 +36,7 @@ namespace ControlCatalog.Pages
};
}
Window GetWindow() => this.FindControl<CheckBox>("IsModal").IsChecked ? (Window)this.VisualRoot : null;
Window GetWindow() => this.FindControl<CheckBox>("IsModal").IsChecked.Value ? (Window)this.VisualRoot : null;
private void InitializeComponent()
{

24
samples/ControlCatalog/Pages/RadioButtonPage.xaml

@ -1,15 +1,27 @@
<UserControl xmlns="https://github.com/avaloniaui">
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">RadioButton</TextBlock>
<TextBlock Classes="h2">Allows the selection of a single option of many</TextBlock>
<StackPanel Orientation="Vertical"
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<RadioButton IsChecked="True">Option 1</RadioButton>
<RadioButton>Option 2</RadioButton>
<RadioButton IsEnabled="False">Disabled</RadioButton>
</StackPanel>
<StackPanel Orientation="Vertical"
Gap="16">
<RadioButton IsChecked="True">Option 1</RadioButton>
<RadioButton>Option 2</RadioButton>
<RadioButton IsChecked="{x:Null}">Option 3</RadioButton>
<RadioButton IsEnabled="False">Disabled</RadioButton>
</StackPanel>
<StackPanel Orientation="Vertical"
Gap="16">
<RadioButton IsChecked="True" IsThreeState="True">Three States: Option 1</RadioButton>
<RadioButton IsChecked="False" IsThreeState="True">Three States: Option 2</RadioButton>
<RadioButton IsChecked="{x:Null}" IsThreeState="True">Three States: Option 3</RadioButton>
<RadioButton IsChecked="{x:Null}" IsThreeState="True" IsEnabled="False">Disabled</RadioButton>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

1
samples/RenderTest/RenderTest.csproj

@ -181,7 +181,6 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

1
samples/VirtualizationTest/VirtualizationTest.csproj

@ -148,7 +148,6 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -29,6 +29,5 @@
</ItemGroup>
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\..\build\Splat.props" />
<Import Project="..\..\..\build\Rx.props" />
</Project>

1
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -154,7 +154,6 @@
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Splat.props" />
<Import Project="..\..\..\build\Sprache.props" />
<Import Project="..\..\..\build\Rx.props" />
</Project>

2
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -326,7 +326,7 @@ namespace Avalonia
object anchor = null,
bool enableDataValidation = false)
{
return new InstancedBinding(_source);
return InstancedBinding.OneWay(_source);
}
}
}

17
src/Avalonia.Base/Data/IndexerBinding.cs

@ -31,13 +31,18 @@ namespace Avalonia.Data
targetProperty.GetMetadata(target.GetType()).DefaultBindingMode :
Mode;
if (mode == BindingMode.TwoWay)
switch (mode)
{
return new InstancedBinding(Source.GetSubject(Property), mode);
}
else
{
return new InstancedBinding(Source.GetObservable(Property), mode);
case BindingMode.OneTime:
return InstancedBinding.OneTime(Source.GetObservable(Property));
case BindingMode.OneWay:
return InstancedBinding.OneWay(Source.GetObservable(Property));
case BindingMode.OneWayToSource:
return InstancedBinding.OneWayToSource(Source.GetSubject(Property));
case BindingMode.TwoWay:
return InstancedBinding.TwoWay(Source.GetSubject(Property));
default:
throw new NotSupportedException("Unsupported BindingMode.");
}
}
}

155
src/Avalonia.Base/Data/InstancedBinding.cs

@ -14,71 +14,35 @@ namespace Avalonia.Data
/// property on a control's DataContext"; this class represents a binding that has been
/// *instanced* by calling <see cref="IBinding.Initiate(IAvaloniaObject, AvaloniaProperty, object, bool)"/>
/// on a target object.
///
/// When a binding is initiated, it can return one of 3 possible sources for the binding:
/// - An <see cref="ISubject{Object}"/> which can be used for any type of binding.
/// - An <see cref="IObservable{Object}"/> which can be used for all types of bindings except
/// <see cref="BindingMode.OneWayToSource"/> and <see cref="BindingMode.TwoWay"/>.
/// - A plain object, which can only represent a <see cref="BindingMode.OneTime"/> binding.
/// </remarks>
public class InstancedBinding
{
/// <summary>
/// Initializes a new instance of the <see cref="InstancedBinding"/> class.
/// </summary>
/// <param name="value">
/// The value used for the <see cref="BindingMode.OneTime"/> binding.
/// </param>
/// <param name="priority">The binding priority.</param>
public InstancedBinding(object value,
BindingPriority priority = BindingPriority.LocalValue)
{
Mode = BindingMode.OneTime;
Priority = priority;
Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="InstancedBinding"/> class.
/// </summary>
/// <param name="observable">The observable for a one-way binding.</param>
/// <param name="subject">The binding source.</param>
/// <param name="mode">The binding mode.</param>
/// <param name="priority">The binding priority.</param>
public InstancedBinding(
IObservable<object> observable,
BindingMode mode = BindingMode.OneWay,
BindingPriority priority = BindingPriority.LocalValue)
/// <param name="priority">The priority of the binding.</param>
/// <remarks>
/// This constructor can be used to create any type of binding and as such requires an
/// <see cref="ISubject{Object}"/> as the binding source because this is the only binding
/// source which can be used for all binding modes. If you wish to create an instance with
/// something other than a subject, use one of the static creation methods on this class.
/// </remarks>
public InstancedBinding(ISubject<object> subject, BindingMode mode, BindingPriority priority)
{
Contract.Requires<ArgumentNullException>(observable != null);
if (mode == BindingMode.OneWayToSource || mode == BindingMode.TwoWay)
{
throw new ArgumentException(
"Invalid BindingResult mode: OneWayToSource and TwoWay bindings" +
"require a Subject.");
}
Contract.Requires<ArgumentNullException>(subject != null);
Mode = mode;
Priority = priority;
Observable = observable;
Value = subject;
}
/// <summary>
/// Initializes a new instance of the <see cref="InstancedBinding"/> class.
/// </summary>
/// <param name="subject">The subject for a two-way binding.</param>
/// <param name="mode">The binding mode.</param>
/// <param name="priority">The binding priority.</param>
public InstancedBinding(
ISubject<object> subject,
BindingMode mode = BindingMode.OneWay,
BindingPriority priority = BindingPriority.LocalValue)
private InstancedBinding(object value, BindingMode mode, BindingPriority priority)
{
Contract.Requires<ArgumentNullException>(subject != null);
Mode = mode;
Priority = priority;
Subject = subject;
Value = value;
}
/// <summary>
@ -92,18 +56,101 @@ namespace Avalonia.Data
public BindingPriority Priority { get; }
/// <summary>
/// Gets the value used for a <see cref="BindingMode.OneTime"/> binding.
/// Gets the value or source of the binding.
/// </summary>
public object Value { get; }
/// <summary>
/// Gets the observable for a one-way binding.
/// Gets the <see cref="Value"/> as an observable.
/// </summary>
public IObservable<object> Observable { get; }
public IObservable<object> Observable => Value as IObservable<object>;
/// <summary>
/// Gets the subject for a two-way binding.
/// Gets the <see cref="Value"/> as a subject.
/// </summary>
public ISubject<object> Subject { get; }
public ISubject<object> Subject => Value as ISubject<object>;
/// <summary>
/// Creates a new one-time binding with a fixed value.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding OneTime(
object value,
BindingPriority priority = BindingPriority.LocalValue)
{
return new InstancedBinding(value, BindingMode.OneTime, priority);
}
/// <summary>
/// Creates a new one-time binding.
/// </summary>
/// <param name="observable">The source observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding OneTime(
IObservable<object> observable,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(observable != null);
return new InstancedBinding(observable, BindingMode.OneTime, priority);
}
/// <summary>
/// Creates a new one-way binding.
/// </summary>
/// <param name="observable">The source observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding OneWay(
IObservable<object> observable,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(observable != null);
return new InstancedBinding(observable, BindingMode.OneWay, priority);
}
/// <summary>
/// Creates a new one-way to source binding.
/// </summary>
/// <param name="subject">The binding source.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding OneWayToSource(
ISubject<object> subject,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(subject != null);
return new InstancedBinding(subject, BindingMode.OneWayToSource, priority);
}
/// <summary>
/// Creates a new two-way binding.
/// </summary>
/// <param name="subject">The binding source.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding TwoWay(
ISubject<object> subject,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires<ArgumentNullException>(subject != null);
return new InstancedBinding(subject, BindingMode.TwoWay, priority);
}
/// <summary>
/// Creates a copy of the <see cref="InstancedBinding"/> with a different priority.
/// </summary>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public InstancedBinding WithPriority(BindingPriority priority)
{
return new InstancedBinding(Value, Mode, priority);
}
}
}

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

@ -72,6 +72,12 @@ namespace Avalonia.Threading
_jobRunner?.RunJobs(null);
}
/// <summary>
/// Use this method to ensure that more prioritized tasks are executed
/// </summary>
/// <param name="minimumPriority"></param>
public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority);
/// <inheritdoc/>
public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{

2132
src/Avalonia.Controls/Calendar/Calendar.cs

File diff suppressed because it is too large

215
src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs

@ -0,0 +1,215 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using Avalonia.Threading;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
namespace Avalonia.Controls.Primitives
{
public sealed class CalendarBlackoutDatesCollection : ObservableCollection<CalendarDateRange>
{
/// <summary>
/// The Calendar whose dates this object represents.
/// </summary>
private Calendar _owner;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Primitives.CalendarBlackoutDatesCollection" />
/// class.
/// </summary>
/// <param name="owner">
/// The <see cref="T:Avalonia.Controls.Calendar" /> whose dates
/// this object represents.
/// </param>
public CalendarBlackoutDatesCollection(Calendar owner)
{
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
}
/// <summary>
/// Adds all dates before <see cref="P:System.DateTime.Today" /> to the
/// collection.
/// </summary>
public void AddDatesInPast()
{
Add(new CalendarDateRange(DateTime.MinValue, DateTime.Today.AddDays(-1)));
}
/// <summary>
/// Returns a value that represents whether this collection contains the
/// specified date.
/// </summary>
/// <param name="date">The date to search for.</param>
/// <returns>
/// True if the collection contains the specified date; otherwise,
/// false.
/// </returns>
public bool Contains(DateTime date)
{
int count = Count;
for (int i = 0; i < count; i++)
{
if (DateTimeHelper.InRange(date, this[i]))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns a value that represents whether this collection contains the
/// specified range of dates.
/// </summary>
/// <param name="start">The start of the date range.</param>
/// <param name="end">The end of the date range.</param>
/// <returns>
/// True if all dates in the range are contained in the collection;
/// otherwise, false.
/// </returns>
public bool Contains(DateTime start, DateTime end)
{
DateTime rangeStart;
DateTime rangeEnd;
if (DateTime.Compare(end, start) > -1)
{
rangeStart = DateTimeHelper.DiscardTime(start).Value;
rangeEnd = DateTimeHelper.DiscardTime(end).Value;
}
else
{
rangeStart = DateTimeHelper.DiscardTime(end).Value;
rangeEnd = DateTimeHelper.DiscardTime(start).Value;
}
int count = Count;
for (int i = 0; i < count; i++)
{
CalendarDateRange range = this[i];
if (DateTime.Compare(range.Start, rangeStart) == 0 && DateTime.Compare(range.End, rangeEnd) == 0)
{
return true;
}
}
return false;
}
/// <summary>
/// Returns a value that represents whether this collection contains any
/// date in the specified range.
/// </summary>
/// <param name="range">The range of dates to search for.</param>
/// <returns>
/// True if any date in the range is contained in the collection;
/// otherwise, false.
/// </returns>
public bool ContainsAny(CalendarDateRange range)
{
return this.Any(r => r.ContainsAny(range));
}
/// <summary>
/// Removes all items from the collection.
/// </summary>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void ClearItems()
{
EnsureValidThread();
base.ClearItems();
_owner.UpdateMonths();
}
/// <summary>
/// Inserts an item into the collection at the specified index.
/// </summary>
/// <param name="index">
/// The zero-based index at which item should be inserted.
/// </param>
/// <param name="item">The object to insert.</param>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void InsertItem(int index, CalendarDateRange item)
{
EnsureValidThread();
if (!IsValid(item))
{
throw new ArgumentOutOfRangeException("Value is not valid.");
}
base.InsertItem(index, item);
_owner.UpdateMonths();
}
/// <summary>
/// Removes the item at the specified index of the collection.
/// </summary>
/// <param name="index">
/// The zero-based index of the element to remove.
/// </param>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void RemoveItem(int index)
{
EnsureValidThread();
base.RemoveItem(index);
_owner.UpdateMonths();
}
/// <summary>
/// Replaces the element at the specified index.
/// </summary>
/// <param name="index">
/// The zero-based index of the element to replace.
/// </param>
/// <param name="item">
/// The new value for the element at the specified index.
/// </param>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void SetItem(int index, CalendarDateRange item)
{
EnsureValidThread();
if (!IsValid(item))
{
throw new ArgumentOutOfRangeException("Value is not valid.");
}
base.SetItem(index, item);
_owner.UpdateMonths();
}
private bool IsValid(CalendarDateRange item)
{
foreach (DateTime day in _owner.SelectedDates)
{
if (DateTimeHelper.InRange(day, item))
{
return false;
}
}
return true;
}
private void EnsureValidThread()
{
Dispatcher.UIThread.VerifyAccess();
}
}
}

193
src/Avalonia.Controls/Calendar/CalendarButton.cs

@ -0,0 +1,193 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using Avalonia.Input;
using System;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Represents a button on a
/// <see cref="T:Avalonia.Controls.Calendar" />.
/// </summary>
public sealed class CalendarButton : Button
{
/// <summary>
/// A value indicating whether the button is focused.
/// </summary>
private bool _isCalendarButtonFocused;
/// <summary>
/// A value indicating whether the button is inactive.
/// </summary>
private bool _isInactive;
/// <summary>
/// A value indicating whether the button is selected.
/// </summary>
private bool _isSelected;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Primitives.CalendarButton" />
/// class.
/// </summary>
public CalendarButton()
: base()
{
Content = DateTimeHelper.GetCurrentDateFormat().AbbreviatedMonthNames[0];
}
/// <summary>
/// Gets or sets the Calendar associated with this button.
/// </summary>
internal Calendar Owner { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the button is focused.
/// </summary>
internal bool IsCalendarButtonFocused
{
get { return _isCalendarButtonFocused; }
set
{
if (_isCalendarButtonFocused != value)
{
_isCalendarButtonFocused = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the button is inactive.
/// </summary>
internal bool IsInactive
{
get { return _isInactive; }
set
{
if (_isInactive != value)
{
_isInactive = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the button is selected.
/// </summary>
internal bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Builds the visual tree for the
/// <see cref="T:System.Windows.Controls.Primitives.CalendarButton" />
/// when a new template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
SetPseudoClasses();
}
/// <summary>
/// Sets PseudoClasses based on current state.
/// </summary>
private void SetPseudoClasses()
{
PseudoClasses.Set(":selected", IsSelected);
PseudoClasses.Set(":inactive", IsInactive);
PseudoClasses.Set(":btnfocused", IsCalendarButtonFocused && IsEnabled);
}
/// <summary>
/// Occurs when the left mouse button is pressed (or when the tip of the
/// stylus touches the tablet PC) while the mouse pointer is over a
/// UIElement.
/// </summary>
public event EventHandler<PointerPressedEventArgs> CalendarLeftMouseButtonDown;
/// <summary>
/// Occurs when the left mouse button is released (or the tip of the
/// stylus is removed from the tablet PC) while the mouse (or the
/// stylus) is over a UIElement (or while a UIElement holds mouse
/// capture).
/// </summary>
public event EventHandler<PointerReleasedEventArgs> CalendarLeftMouseButtonUp;
/// <summary>
/// Provides class handling for the MouseLeftButtonDown event that
/// occurs when the left mouse button is pressed while the mouse pointer
/// is over this control.
/// </summary>
/// <param name="e">The event data. </param>
/// <exception cref="System.ArgumentNullException">
/// e is a null reference (Nothing in Visual Basic).
/// </exception>
/// <remarks>
/// This method marks the MouseLeftButtonDown event as handled by
/// setting the MouseButtonEventArgs.Handled property of the event data
/// to true when the button is enabled and its ClickMode is not set to
/// Hover. Since this method marks the MouseLeftButtonDown event as
/// handled in some situations, you should use the Click event instead
/// to detect a button click.
/// </remarks>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
if (e.MouseButton == MouseButton.Left)
CalendarLeftMouseButtonDown?.Invoke(this, e);
}
/// <summary>
/// Provides handling for the MouseLeftButtonUp event that occurs when
/// the left mouse button is released while the mouse pointer is over
/// this control.
/// </summary>
/// <param name="e">The event data.</param>
/// <exception cref="System.ArgumentNullException">
/// e is a null reference (Nothing in Visual Basic).
/// </exception>
/// <remarks>
/// This method marks the MouseLeftButtonUp event as handled by setting
/// the MouseButtonEventArgs.Handled property of the event data to true
/// when the button is enabled and its ClickMode is not set to Hover.
/// Since this method marks the MouseLeftButtonUp event as handled in
/// some situations, you should use the Click event instead to detect a
/// button click.
/// </remarks>
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (e.MouseButton == MouseButton.Left)
CalendarLeftMouseButtonUp?.Invoke(this, e);
}
/// <summary>
/// We need to simulate the MouseLeftButtonUp event for the
/// CalendarButton that stays in Pressed state after MouseCapture is
/// released since there is no actual MouseLeftButtonUp event for the
/// release.
/// </summary>
/// <param name="e">Event arguments.</param>
internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
{
e.Handled = false;
base.OnPointerReleased(e);
}
}
}

79
src/Avalonia.Controls/Calendar/CalendarDateRange.cs

@ -0,0 +1,79 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Diagnostics;
namespace Avalonia.Controls
{
public sealed class CalendarDateRange
{
/// <summary>
/// Gets the first date in the represented range.
/// </summary>
/// <value>The first date in the represented range.</value>
public DateTime Start { get; private set; }
/// <summary>
/// Gets the last date in the represented range.
/// </summary>
/// <value>The last date in the represented range.</value>
public DateTime End { get; private set; }
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:System.Windows.Controls.CalendarDateRange" /> class
/// with a single date.
/// </summary>
/// <param name="day">The date to be represented by the range.</param>
public CalendarDateRange(DateTime day)
{
Start = day;
End = day;
}
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:System.Windows.Controls.CalendarDateRange" /> class
/// with a range of dates.
/// </summary>
/// <param name="start">
/// The start of the range to be represented.
/// </param>
/// <param name="end">The end of the range to be represented.</param>
public CalendarDateRange(DateTime start, DateTime end)
{
if (DateTime.Compare(end, start) >= 0)
{
Start = start;
End = end;
}
else
{
// Always use the start for ranges on the same day
Start = start;
End = start;
}
}
/// <summary>
/// Returns true if any day in the given DateTime range is contained in
/// the current CalendarDateRange.
/// </summary>
/// <param name="range">Inherited code: Requires comment 1.</param>
/// <returns>Inherited code: Requires comment 2.</returns>
internal bool ContainsAny(CalendarDateRange range)
{
Debug.Assert(range != null, "range should not be null!");
int start = DateTime.Compare(Start, range.Start);
// Check if any part of the supplied range is contained by this
// range or if the supplied range completely covers this range.
return (start <= 0 && DateTime.Compare(End, range.Start) >= 0) ||
(start >= 0 && DateTime.Compare(Start, range.End) <= 0);
}
}
}

253
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@ -0,0 +1,253 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using Avalonia.Input;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Avalonia.Controls.Primitives
{
public sealed class CalendarDayButton : Button
{
/// <summary>
/// Default content for the CalendarDayButton.
/// </summary>
private const int DefaultContent = 1;
private bool _isCurrent;
private bool _ignoringMouseOverState;
private bool _isBlackout;
private bool _isToday;
private bool _isInactive;
private bool _isSelected;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Primitives.CalendarDayButton" />
/// class.
/// </summary>
public CalendarDayButton()
: base()
{
//Focusable = false;
Content = DefaultContent.ToString(CultureInfo.CurrentCulture);
}
/// <summary>
/// Gets or sets the Calendar associated with this button.
/// </summary>
internal Calendar Owner { get; set; }
internal int Index { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the button is the focused
/// element on the Calendar control.
/// </summary>
internal bool IsCurrent
{
get { return _isCurrent; }
set
{
if (_isCurrent != value)
{
_isCurrent = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Ensure the button is not in the MouseOver state.
/// </summary>
/// <remarks>
/// If a button is in the MouseOver state when a Popup is closed (as is
/// the case when you select a date in the DatePicker control), it will
/// continue to think it's in the mouse over state even when the Popup
/// opens again and it's not. This method is used to forcibly clear the
/// state by changing the CommonStates state group.
/// </remarks>
internal void IgnoreMouseOverState()
{
// TODO: Investigate whether this needs to be done by changing the
// state everytime we change any state, or if it can be done once
// to properly reset the control.
_ignoringMouseOverState = false;
// If the button thinks it's in the MouseOver state (which can
// happen when a Popup is closed before the button can change state)
// we will override the state so it shows up as normal.
if (IsPointerOver)
{
_ignoringMouseOverState = true;
SetPseudoClasses();
}
}
/// <summary>
/// Gets or sets a value indicating whether this is a blackout date.
/// </summary>
internal bool IsBlackout
{
get { return _isBlackout; }
set
{
if (_isBlackout != value)
{
_isBlackout = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether this button represents
/// today.
/// </summary>
internal bool IsToday
{
get { return _isToday; }
set
{
if (_isToday != value)
{
_isToday = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the button is inactive.
/// </summary>
internal bool IsInactive
{
get { return _isInactive; }
set
{
if (_isInactive != value)
{
_isInactive = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the button is selected.
/// </summary>
internal bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
SetPseudoClasses();
}
}
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
SetPseudoClasses();
}
private void SetPseudoClasses()
{
if (_ignoringMouseOverState)
{
PseudoClasses.Set(":pressed", IsPressed);
PseudoClasses.Set(":disabled", !IsEnabled);
}
PseudoClasses.Set(":selected", IsSelected);
PseudoClasses.Set(":inactive", IsInactive);
PseudoClasses.Set(":today", IsToday);
PseudoClasses.Set(":blackout", IsBlackout);
PseudoClasses.Set(":dayfocused", IsCurrent && IsEnabled);
}
/// <summary>
/// Occurs when the left mouse button is pressed (or when the tip of the
/// stylus touches the tablet PC) while the mouse pointer is over a
/// UIElement.
/// </summary>
public event EventHandler<PointerPressedEventArgs> CalendarDayButtonMouseDown;
/// <summary>
/// Occurs when the left mouse button is released (or the tip of the
/// stylus is removed from the tablet PC) while the mouse (or the
/// stylus) is over a UIElement (or while a UIElement holds mouse
/// capture).
/// </summary>
public event EventHandler<PointerReleasedEventArgs> CalendarDayButtonMouseUp;
/// <summary>
/// Provides class handling for the MouseLeftButtonDown event that
/// occurs when the left mouse button is pressed while the mouse pointer
/// is over this control.
/// </summary>
/// <param name="e">The event data. </param>
/// <exception cref="System.ArgumentNullException">
/// e is a null reference (Nothing in Visual Basic).
/// </exception>
/// <remarks>
/// This method marks the MouseLeftButtonDown event as handled by
/// setting the MouseButtonEventArgs.Handled property of the event data
/// to true when the button is enabled and its ClickMode is not set to
/// Hover. Since this method marks the MouseLeftButtonDown event as
/// handled in some situations, you should use the Click event instead
/// to detect a button click.
/// </remarks>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
if (e.MouseButton == MouseButton.Left)
CalendarDayButtonMouseDown?.Invoke(this, e);
}
/// <summary>
/// Provides handling for the MouseLeftButtonUp event that occurs when
/// the left mouse button is released while the mouse pointer is over
/// this control.
/// </summary>
/// <param name="e">The event data.</param>
/// <exception cref="System.ArgumentNullException">
/// e is a null reference (Nothing in Visual Basic).
/// </exception>
/// <remarks>
/// This method marks the MouseLeftButtonUp event as handled by setting
/// the MouseButtonEventArgs.Handled property of the event data to true
/// when the button is enabled and its ClickMode is not set to Hover.
/// Since this method marks the MouseLeftButtonUp event as handled in
/// some situations, you should use the Click event instead to detect a
/// button click.
/// </remarks>
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (e.MouseButton == MouseButton.Left)
CalendarDayButtonMouseUp?.Invoke(this, e);
}
/// <summary>
/// We need to simulate the MouseLeftButtonUp event for the
/// CalendarDayButton that stays in Pressed state after MouseCapture is
/// released since there is no actual MouseLeftButtonUp event for the
/// release.
/// </summary>
/// <param name="e">Event arguments.</param>
internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
{
e.Handled = false;
base.OnPointerReleased(e);
}
}
}

21
src/Avalonia.Controls/Calendar/CalendarExtensions.cs

@ -0,0 +1,21 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Collections.Generic;
using Avalonia.Input;
using System.Diagnostics;
namespace Avalonia.Controls.Primitives
{
internal static class CalendarExtensions
{
public static void GetMetaKeyState(InputModifiers modifiers, out bool ctrl, out bool shift)
{
ctrl = (modifiers & InputModifiers.Control) == InputModifiers.Control;
shift = (modifiers & InputModifiers.Shift) == InputModifiers.Shift;
}
}
}

1264
src/Avalonia.Controls/Calendar/CalendarItem.cs

File diff suppressed because it is too large

155
src/Avalonia.Controls/Calendar/DateTimeHelper.cs

@ -0,0 +1,155 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Diagnostics;
using System.Globalization;
namespace Avalonia.Controls
{
internal static class DateTimeHelper
{
public static DateTime? AddDays(DateTime time, int days)
{
System.Globalization.Calendar cal = new GregorianCalendar();
try
{
return cal.AddDays(time, days);
}
catch (ArgumentException)
{
return null;
}
}
public static DateTime? AddMonths(DateTime time, int months)
{
System.Globalization.Calendar cal = new GregorianCalendar();
try
{
return cal.AddMonths(time, months);
}
catch (ArgumentException)
{
return null;
}
}
public static DateTime? AddYears(DateTime time, int years)
{
System.Globalization.Calendar cal = new GregorianCalendar();
try
{
return cal.AddYears(time, years);
}
catch (ArgumentException)
{
return null;
}
}
public static int CompareDays(DateTime dt1, DateTime dt2)
{
return DateTime.Compare(DiscardTime(dt1).Value, DiscardTime(dt2).Value);
}
public static int CompareYearMonth(DateTime dt1, DateTime dt2)
{
return (dt1.Year - dt2.Year) * 12 + (dt1.Month - dt2.Month);
}
public static int DecadeOfDate(DateTime date)
{
return date.Year - (date.Year % 10);
}
public static DateTime DiscardDayTime(DateTime d)
{
int year = d.Year;
int month = d.Month;
DateTime newD = new DateTime(year, month, 1, 0, 0, 0);
return newD;
}
public static DateTime? DiscardTime(DateTime? d)
{
if (d == null)
{
return null;
}
return d.Value.Date;
}
public static int EndOfDecade(DateTime date)
{
return DecadeOfDate(date) + 9;
}
public static DateTimeFormatInfo GetCurrentDateFormat()
{
if (CultureInfo.CurrentCulture.Calendar is GregorianCalendar)
{
return CultureInfo.CurrentCulture.DateTimeFormat;
}
else
{
foreach (System.Globalization.Calendar cal in CultureInfo.CurrentCulture.OptionalCalendars)
{
if (cal is GregorianCalendar)
{
// if the default calendar is not Gregorian, return the
// first supported GregorianCalendar dtfi
DateTimeFormatInfo dtfi = new CultureInfo(CultureInfo.CurrentCulture.Name).DateTimeFormat;
dtfi.Calendar = cal;
return dtfi;
}
}
// if there are no GregorianCalendars in the OptionalCalendars
// list, use the invariant dtfi
DateTimeFormatInfo dt = new CultureInfo(CultureInfo.InvariantCulture.Name).DateTimeFormat;
dt.Calendar = new GregorianCalendar();
return dt;
}
}
public static bool InRange(DateTime date, CalendarDateRange range)
{
Debug.Assert(DateTime.Compare(range.Start, range.End) < 1, "The range should start before it ends!");
if (CompareDays(date, range.Start) > -1 && CompareDays(date, range.End) < 1)
{
return true;
}
return false;
}
public static string ToYearMonthPatternString(DateTime date)
{
string result = string.Empty;
DateTimeFormatInfo format = GetCurrentDateFormat();
if (format != null)
{
result = date.ToString(format.YearMonthPattern, format);
}
return result;
}
public static string ToYearString(DateTime date)
{
string result = string.Empty;
DateTimeFormatInfo format = GetCurrentDateFormat();
if (format != null)
{
result = date.Year.ToString(format);
}
return result;
}
}
}

361
src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs

@ -0,0 +1,361 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using Avalonia.Threading;
using System;
using System.Collections.ObjectModel;
using System.Threading;
namespace Avalonia.Controls.Primitives
{
public sealed class SelectedDatesCollection : ObservableCollection<DateTime>
{
/// <summary>
/// Inherited code: Requires comment.
/// </summary>
private Collection<DateTime> _addedItems;
/// <summary>
/// Inherited code: Requires comment.
/// </summary>
private bool _isCleared;
/// <summary>
/// Inherited code: Requires comment.
/// </summary>
private bool _isRangeAdded;
/// <summary>
/// Inherited code: Requires comment.
/// </summary>
private Calendar _owner;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Primitives.SelectedDatesCollection" />
/// class.
/// </summary>
/// <param name="owner">
/// The <see cref="T:Avalonia.Controls.Calendar" /> associated
/// with this object.
/// </param>
public SelectedDatesCollection(Calendar owner)
{
_owner = owner;
_addedItems = new Collection<DateTime>();
}
private void InvokeCollectionChanged(System.Collections.IList removedItems, System.Collections.IList addedItems)
{
_owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(null, addedItems, removedItems));
}
/// <summary>
/// Adds all the dates in the specified range, which includes the first
/// and last dates, to the collection.
/// </summary>
/// <param name="start">The first date to add to the collection.</param>
/// <param name="end">The last date to add to the collection.</param>
public void AddRange(DateTime start, DateTime end)
{
DateTime? rangeStart;
// increment parameter specifies if the Days were selected in
// Descending order or Ascending order based on this value, we add
// the days in the range either in Ascending order or in Descending
// order
int increment = (DateTime.Compare(end, start) >= 0) ? 1 : -1;
_addedItems.Clear();
rangeStart = start;
_isRangeAdded = true;
if (_owner.IsMouseSelection)
{
// In Mouse Selection we allow the user to be able to add
// multiple ranges in one action in MultipleRange Mode. In
// SingleRange Mode, we only add the first selected range.
while (rangeStart.HasValue && DateTime.Compare(end, rangeStart.Value) != -increment)
{
if (Calendar.IsValidDateSelection(_owner, rangeStart))
{
Add(rangeStart.Value);
}
else
{
if (_owner.SelectionMode == CalendarSelectionMode.SingleRange)
{
_owner.HoverEnd = rangeStart.Value.AddDays(-increment);
break;
}
}
rangeStart = DateTimeHelper.AddDays(rangeStart.Value, increment);
}
}
else
{
// If CalendarSelectionMode.SingleRange and a user
// programmatically tries to add multiple ranges, we will throw
// away the old range and replace it with the new one. In order
// to provide the removed items without an additional event, we
// are calling ClearInternal
if (_owner.SelectionMode == CalendarSelectionMode.SingleRange && Count > 0)
{
foreach (DateTime item in this)
{
_owner.RemovedItems.Add(item);
}
ClearInternal();
}
while (rangeStart.HasValue && DateTime.Compare(end, rangeStart.Value) != -increment)
{
Add(rangeStart.Value);
rangeStart = DateTimeHelper.AddDays(rangeStart.Value, increment);
}
}
_owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(null, _addedItems, _owner.RemovedItems));
_owner.RemovedItems.Clear();
_owner.UpdateMonths();
_isRangeAdded = false;
}
/// <summary>
/// Removes all items from the collection.
/// </summary>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void ClearItems()
{
EnsureValidThread();
Collection<DateTime> addedItems = new Collection<DateTime>();
Collection<DateTime> removedItems = new Collection<DateTime>();
foreach (DateTime item in this)
{
removedItems.Add(item);
}
base.ClearItems();
// The event fires after SelectedDate changes
if (_owner.SelectionMode != CalendarSelectionMode.None && _owner.SelectedDate != null)
{
_owner.SelectedDate = null;
}
if (removedItems.Count != 0)
{
InvokeCollectionChanged(removedItems, addedItems);
}
_owner.UpdateMonths();
}
/// <summary>
/// Inserts an item into the collection at the specified index.
/// </summary>
/// <param name="index">
/// The zero-based index at which item should be inserted.
/// </param>
/// <param name="item">The object to insert.</param>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void InsertItem(int index, DateTime item)
{
EnsureValidThread();
if (!Contains(item))
{
Collection<DateTime> addedItems = new Collection<DateTime>();
if (CheckSelectionMode())
{
if (Calendar.IsValidDateSelection(_owner, item))
{
// If the Collection is cleared since it is SingleRange
// and it had another range set the index to 0
if (_isCleared)
{
index = 0;
_isCleared = false;
}
base.InsertItem(index, item);
// The event fires after SelectedDate changes
if (index == 0 && !(_owner.SelectedDate.HasValue && DateTime.Compare(_owner.SelectedDate.Value, item) == 0))
{
_owner.SelectedDate = item;
}
if (!_isRangeAdded)
{
addedItems.Add(item);
InvokeCollectionChanged(_owner.RemovedItems, addedItems);
_owner.RemovedItems.Clear();
int monthDifference = DateTimeHelper.CompareYearMonth(item, _owner.DisplayDateInternal);
if (monthDifference < 2 && monthDifference > -2)
{
_owner.UpdateMonths();
}
}
else
{
_addedItems.Add(item);
}
}
else
{
throw new ArgumentOutOfRangeException("SelectedDate value is not valid.");
}
}
}
}
/// <summary>
/// Removes the item at the specified index of the collection.
/// </summary>
/// <param name="index">
/// The zero-based index of the element to remove.
/// </param>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void RemoveItem(int index)
{
EnsureValidThread();
if (index >= Count)
{
base.RemoveItem(index);
}
else
{
Collection<DateTime> addedItems = new Collection<DateTime>();
Collection<DateTime> removedItems = new Collection<DateTime>();
int monthDifference = DateTimeHelper.CompareYearMonth(this[index], _owner.DisplayDateInternal);
removedItems.Add(this[index]);
base.RemoveItem(index);
// The event fires after SelectedDate changes
if (index == 0)
{
if (Count > 0)
{
_owner.SelectedDate = this[0];
}
else
{
_owner.SelectedDate = null;
}
}
InvokeCollectionChanged(removedItems, addedItems);
if (monthDifference < 2 && monthDifference > -2)
{
_owner.UpdateMonths();
}
}
}
/// <summary>
/// Replaces the element at the specified index.
/// </summary>
/// <param name="index">
/// The zero-based index of the element to replace.
/// </param>
/// <param name="item">
/// The new value for the element at the specified index.
/// </param>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void SetItem(int index, DateTime item)
{
EnsureValidThread();
if (!Contains(item))
{
Collection<DateTime> addedItems = new Collection<DateTime>();
Collection<DateTime> removedItems = new Collection<DateTime>();
if (index >= Count)
{
base.SetItem(index, item);
}
else
{
if (item != null && DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item))
{
removedItems.Add(this[index]);
base.SetItem(index, item);
addedItems.Add(item);
// The event fires after SelectedDate changes
if (index == 0 && !(_owner.SelectedDate.HasValue && DateTime.Compare(_owner.SelectedDate.Value, item) == 0))
{
_owner.SelectedDate = item;
}
InvokeCollectionChanged(removedItems, addedItems);
int monthDifference = DateTimeHelper.CompareYearMonth(item, _owner.DisplayDateInternal);
if (monthDifference < 2 && monthDifference > -2)
{
_owner.UpdateMonths();
}
}
}
}
}
internal void ClearInternal()
{
base.ClearItems();
}
private bool CheckSelectionMode()
{
if (_owner.SelectionMode == CalendarSelectionMode.None)
{
throw new InvalidOperationException("The SelectedDate property cannot be set when the selection mode is None.");
}
if (_owner.SelectionMode == CalendarSelectionMode.SingleDate && Count > 0)
{
throw new InvalidOperationException("The SelectedDates collection can be changed only in a multiple selection mode. Use the SelectedDate in a single selection mode.");
}
// if user tries to add an item into the SelectedDates in
// SingleRange mode, we throw away the old range and replace it with
// the new one in order to provide the removed items without an
// additional event, we are calling ClearInternal
if (_owner.SelectionMode == CalendarSelectionMode.SingleRange && !_isRangeAdded && Count > 0)
{
foreach (DateTime item in this)
{
_owner.RemovedItems.Add(item);
}
ClearInternal();
_isCleared = true;
}
return true;
}
private void EnsureValidThread()
{
Dispatcher.UIThread.VerifyAccess();
}
}
}

2
src/Avalonia.Controls/Classes.cs

@ -179,7 +179,7 @@ namespace Avalonia.Controls
{
ThrowIfPseudoclass(name, "removed");
if (!Contains(name))
if (Contains(name))
{
c.Add(name);
}

4
src/Avalonia.Controls/Control.cs

@ -774,7 +774,9 @@ namespace Avalonia.Controls
foreach (var child in control.LogicalChildren)
{
if (child is Control c && !c.IsSet(DataContextProperty))
if (child is Control c &&
c.InheritanceParent == control &&
!c.IsSet(DataContextProperty))
{
DataContextNotifying(c, notifying);
}

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

@ -9,26 +9,37 @@ namespace Avalonia.Controls.Primitives
{
public class ToggleButton : Button
{
public static readonly DirectProperty<ToggleButton, bool> IsCheckedProperty =
AvaloniaProperty.RegisterDirect<ToggleButton, bool>(
"IsChecked",
public static readonly DirectProperty<ToggleButton, bool?> IsCheckedProperty =
AvaloniaProperty.RegisterDirect<ToggleButton, bool?>(
nameof(IsChecked),
o => o.IsChecked,
(o,v) => o.IsChecked = v,
(o, v) => o.IsChecked = v,
defaultBindingMode: BindingMode.TwoWay);
private bool _isChecked;
public static readonly StyledProperty<bool> IsThreeStateProperty =
AvaloniaProperty.Register<ToggleButton, bool>(nameof(IsThreeState));
private bool? _isChecked = false;
static ToggleButton()
{
PseudoClass(IsCheckedProperty, ":checked");
PseudoClass(IsCheckedProperty, c => c == true, ":checked");
PseudoClass(IsCheckedProperty, c => c == false, ":unchecked");
PseudoClass(IsCheckedProperty, c => c == null, ":indeterminate");
}
public bool IsChecked
public bool? IsChecked
{
get { return _isChecked; }
set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
}
public bool IsThreeState
{
get => GetValue(IsThreeStateProperty);
set => SetValue(IsThreeStateProperty, value);
}
protected override void OnClick()
{
Toggle();
@ -37,7 +48,16 @@ namespace Avalonia.Controls.Primitives
protected virtual void Toggle()
{
IsChecked = !IsChecked;
if (IsChecked.HasValue)
if (IsChecked.Value)
if (IsThreeState)
IsChecked = null;
else
IsChecked = false;
else
IsChecked = true;
else
IsChecked = false;
}
}
}

9
src/Avalonia.Controls/RadioButton.cs

@ -17,17 +17,17 @@ namespace Avalonia.Controls
protected override void Toggle()
{
if (!IsChecked)
if (!IsChecked.GetValueOrDefault())
{
IsChecked = true;
}
}
private void IsCheckedChanged(bool value)
private void IsCheckedChanged(bool? value)
{
var parent = this.GetVisualParent();
if (value && parent != null)
if (value.GetValueOrDefault() && parent != null)
{
var siblings = parent
.GetVisualChildren()
@ -36,7 +36,8 @@ namespace Avalonia.Controls
foreach (var sibling in siblings)
{
sibling.IsChecked = false;
if (sibling.IsChecked.GetValueOrDefault())
sibling.IsChecked = false;
}
}
}

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

@ -29,6 +29,8 @@ namespace Avalonia.Controls.Shapes
private Matrix _transform = Matrix.Identity;
private Geometry _definingGeometry;
private Geometry _renderedGeometry;
bool _calculateTransformOnArrange = false;
static Shape()
{
@ -150,13 +152,53 @@ namespace Avalonia.Controls.Shapes
this._definingGeometry = null;
InvalidateMeasure();
}
protected override Size MeasureOverride(Size availableSize)
{
bool deferCalculateTransform;
switch (Stretch)
{
case Stretch.Fill:
case Stretch.UniformToFill:
deferCalculateTransform = double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height);
break;
case Stretch.Uniform:
deferCalculateTransform = double.IsInfinity(availableSize.Width) && double.IsInfinity(availableSize.Height);
break;
case Stretch.None:
default:
deferCalculateTransform = false;
break;
}
if (deferCalculateTransform)
{
_calculateTransformOnArrange = true;
return DefiningGeometry.Bounds.Size;
}
else
{
_calculateTransformOnArrange = false;
return CalculateShapeSizeAndSetTransform(availableSize);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
if(_calculateTransformOnArrange)
{
_calculateTransformOnArrange = false;
CalculateShapeSizeAndSetTransform(finalSize);
}
return finalSize;
}
private Size CalculateShapeSizeAndSetTransform(Size availableSize)
{
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
if (_transform != transform)
{
_transform = transform;

2
src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs

@ -62,7 +62,7 @@ namespace Avalonia.Controls.Templates
/// <returns>The child items, or null if no child items.</returns>
public InstancedBinding ItemsSelector(object item)
{
return new InstancedBinding(this?._itemsSelector(item));
return InstancedBinding.OneTime(this?._itemsSelector(item));
}
/// <summary>

1
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -50,5 +50,4 @@
</EmbeddedResource>
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Splat.props" />
</Project>

53
src/Avalonia.Diagnostics/LogManager.cs

@ -1,53 +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 Avalonia.Layout;
using Splat;
namespace Avalonia.Diagnostics
{
public class LogManager : ILogManager
{
private static LogManager s_instance;
public static LogManager Instance => s_instance ?? (s_instance = new LogManager());
public ILogger Logger
{
get;
set;
}
public bool LogPropertyMessages
{
get;
set;
}
public bool LogLayoutMessages
{
get;
set;
}
public static void Enable(ILogger logger)
{
Instance.Logger = logger;
Locator.CurrentMutable.Register(() => Instance, typeof(ILogManager));
}
public IFullLogger GetLogger(Type type)
{
if ((type == typeof(AvaloniaObject) && LogPropertyMessages) ||
(type == typeof(Layoutable) && LogLayoutMessages))
{
return new WrappingFullLogger(Logger, type);
}
else
{
return new WrappingFullLogger(new NullLogger(), type);
}
}
}
}

1
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -16,5 +16,4 @@
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
<Import Project="..\..\build\Splat.props" />
</Project>

3
src/Avalonia.Remote.Protocol/BsonStreamTransport.cs

@ -64,7 +64,6 @@ namespace Avalonia.Remote.Protocol
async Task Reader()
{
Task.Yield();
try
{
while (true)
@ -147,4 +146,4 @@ namespace Avalonia.Remote.Protocol
public event Action<IAvaloniaRemoteTransportConnection, object> OnMessage;
public event Action<IAvaloniaRemoteTransportConnection, Exception> OnException;
}
}
}

60
src/Avalonia.Styling/Styling/Setter.cs

@ -135,45 +135,47 @@ namespace Avalonia.Styling
private InstancedBinding Clone(InstancedBinding sourceInstance, IStyle style, IObservable<bool> activator)
{
InstancedBinding cloned;
if (activator != null)
{
var description = style?.ToString();
if (sourceInstance.Mode == BindingMode.TwoWay || sourceInstance.Mode == BindingMode.OneWayToSource)
{
var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
cloned = new InstancedBinding(activated, sourceInstance.Mode, BindingPriority.StyleTrigger);
}
else if (sourceInstance.Mode == BindingMode.OneTime)
{
var activated = new ActivatedValue(activator, sourceInstance.Value, description);
cloned = new InstancedBinding(activated, BindingMode.OneWay, BindingPriority.StyleTrigger);
}
else
switch (sourceInstance.Mode)
{
var activated = new ActivatedObservable(activator, sourceInstance.Observable ?? sourceInstance.Subject, description);
cloned = new InstancedBinding(activated, sourceInstance.Mode, BindingPriority.StyleTrigger);
case BindingMode.OneTime:
if (sourceInstance.Observable != null)
{
var activated = new ActivatedObservable(activator, sourceInstance.Observable, description);
return InstancedBinding.OneTime(activated, BindingPriority.StyleTrigger);
}
else
{
var activated = new ActivatedValue(activator, sourceInstance.Value, description);
return InstancedBinding.OneTime(activated, BindingPriority.StyleTrigger);
}
case BindingMode.OneWay:
{
var activated = new ActivatedObservable(activator, sourceInstance.Observable, description);
return InstancedBinding.OneWay(activated, BindingPriority.StyleTrigger);
}
case BindingMode.OneWayToSource:
{
var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
return InstancedBinding.OneWayToSource(activated, BindingPriority.StyleTrigger);
}
case BindingMode.TwoWay:
{
var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
return InstancedBinding.TwoWay(activated, BindingPriority.StyleTrigger);
}
default:
throw new NotSupportedException("Unsupported BindingMode.");
}
}
else
{
if (sourceInstance.Subject != null)
{
cloned = new InstancedBinding(sourceInstance.Subject, sourceInstance.Mode, BindingPriority.Style);
}
else if (sourceInstance.Observable != null)
{
cloned = new InstancedBinding(sourceInstance.Observable, sourceInstance.Mode, BindingPriority.Style);
}
else
{
cloned = new InstancedBinding(sourceInstance.Value, BindingPriority.Style);
}
return sourceInstance.WithPriority(BindingPriority.Style);
}
return cloned;
}
}
}

30
src/Avalonia.Themes.Default/Calendar.xaml

@ -0,0 +1,30 @@
<!--
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-->
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="Calendar">
<!--<Setter Property="Focusable" Value="False" />-->
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderDarkBrush}" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
<Setter Property="HeaderBackground" Value="{DynamicResource ThemeAccentBrush2}" />
<Setter Property="Template">
<ControlTemplate>
<StackPanel Name="Root"
HorizontalAlignment="Center">
<CalendarItem Name="CalendarItem"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HeaderBackground="{TemplateBinding HeaderBackground}"/>
</StackPanel>
</ControlTemplate>
</Setter>
</Style>
</Styles>

80
src/Avalonia.Themes.Default/CalendarButton.xaml

@ -0,0 +1,80 @@
<!--
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-->
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="CalendarButton">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}" />
<Setter Property="FontSize" Value="{DynamicResource FontSizeSmall}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="MinWidth" Value="37" />
<Setter Property="MinHeight" Value="38" />
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<ControlTemplate>
<Grid>
<Rectangle Name="SelectedBackground"
Opacity="0.75"
Fill="{TemplateBinding Background}"/>
<Rectangle Name="Background"
Opacity="0.5"
Fill="{TemplateBinding Background}"/>
<!--Focusable="False"-->
<ContentControl Name="Content"
Foreground="#FF333333"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
Margin="1,0,1,1"/>
<Rectangle Name="FocusVisual"
StrokeThickness="1"
Stroke="{DynamicResource HighlightBrush}"
IsHitTestVisible="False"/>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="CalendarButton /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CalendarButton:pointerover /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarButton:pressed /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarButton /template/ Rectangle#SelectedBackground">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CalendarButton:selected /template/ Rectangle#SelectedBackground">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarButton /template/ ContentControl#Content">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="CalendarButton:inactive /template/ ContentControl#Content">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLightBrush}"/>
</Style>
<Style Selector="CalendarButton /template/ Rectangle#FocusVisual">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CalendarButton:btnfocused /template/ Rectangle#FocusVisual">
<Setter Property="IsVisible" Value="True"/>
</Style>
</Styles>

116
src/Avalonia.Themes.Default/CalendarDayButton.xaml

@ -0,0 +1,116 @@
<!--
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-->
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="CalendarDayButton">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}" />
<Setter Property="FontSize" Value="{DynamicResource FontSizeSmall}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="MinWidth" Value="5" />
<Setter Property="MinHeight" Value="5" />
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<ControlTemplate>
<Panel Background="Transparent">
<Rectangle Name="TodayBackground"
Fill="{DynamicResource HighlightBrush}"/>
<Rectangle Name="SelectedBackground"
Opacity="0.75"
Fill="{TemplateBinding Background}"/>
<Rectangle Name="Background"
Opacity="0.5"
Fill="{TemplateBinding Background}"/>
<ContentControl Name="Content"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
Margin="5,1,5,1"/>
<Path Name="BlackoutVisual"
Margin="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
RenderTransformOrigin="0.5,0.5"
Fill="#FF000000"
Stretch="Fill"
Data="M8.1772461,11.029181 L10.433105,11.029181 L11.700684,12.801641 L12.973633,11.029181 L15.191895,11.029181 L12.844727,13.999395 L15.21875,17.060919 L12.962891,17.060919 L11.673828,15.256231 L10.352539,17.060919 L8.1396484,17.060919 L10.519043,14.042364 z" />
<Rectangle Name="FocusVisual"
StrokeThickness="1"
Stroke="{DynamicResource HighlightBrush}"
IsHitTestVisible="False"/>
</Panel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="CalendarDayButton /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CalendarDayButton:pointerover /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarDayButton:pressed /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarDayButton /template/ Rectangle#SelectedBackground">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CalendarDayButton:selected /template/ Rectangle#SelectedBackground">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarDayButton /template/ ContentControl#Content">
<Setter Property="Opacity" Value="1"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="CalendarDayButton:disabled /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CalendarDayButton:disabled /template/ ContentControl#Content">
<Setter Property="Opacity" Value="0.3"/>
</Style>
<Style Selector="CalendarDayButton /template/ Rectangle#FocusVisual">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CalendarDayButton:dayfocused /template/ Rectangle#FocusVisual">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarDayButton /template/ Rectangle#TodayBackground">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CalendarDayButton:today /template/ Rectangle#TodayBackground">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarDayButton:inactive /template/ ContentControl#Content">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLightBrush}"/>
</Style>
<Style Selector="CalendarDayButton:today /template/ ContentControl#Content">
<Setter Property="Foreground" Value="#FFFFFFFF"/>
</Style>
<Style Selector="CalendarDayButton /template/ Path#BlackoutVisual">
<Setter Property="Opacity" Value="0"/>
</Style>
<Style Selector="CalendarDayButton:blackout /template/ Path#BlackoutVisual">
<Setter Property="Opacity" Value="0.3"/>
</Style>
</Styles>

183
src/Avalonia.Themes.Default/CalendarItem.xaml

@ -0,0 +1,183 @@
<!--
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-->
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="CalendarItem">
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Margin="0,2,0,2"
CornerRadius="1">
<Border CornerRadius="1"
BorderBrush="{DynamicResource ThemeBackgroundBrush}"
BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.Styles>
<Style Selector="Button.CalendarHeader">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
<Style Selector="Button.CalendarHeader:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="Button.CalendarNavigation">
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="28"/>
</Style>
<Style Selector="Button.CalendarNavigation > Path">
<Setter Property="Fill" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="Button.CalendarNavigation:pointerover > Path">
<Setter Property="Fill" Value="{DynamicResource HighlightBrush}"/>
</Style>
<Style Selector="Button#HeaderButton:pointerover">
<Setter Property="Foreground" Value="{DynamicResource HighlightBrush}"/>
</Style>
</Grid.Styles>
<Rectangle Grid.ColumnSpan="3"
Fill="{TemplateBinding HeaderBackground}"
Stretch="Fill"
VerticalAlignment="Top"
Height="22"/>
<Button Name="PreviousButton"
Classes="CalendarHeader CalendarNavigation"
IsVisible="False"
HorizontalAlignment="Left">
<Path Margin="14,-6,0,0"
Height="10"
Width="6"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Stretch="Fill"
Data="M288.75,232.25 L288.75,240.625 L283,236.625 z" />
</Button>
<Button Name="HeaderButton"
Classes="CalendarHeader"
Grid.Column="1"
FontWeight="Bold"
FontSize="10.5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Padding="1,5,1,9"/>
<Button Name="NextButton"
Classes="CalendarHeader CalendarNavigation"
Grid.Column="2"
IsVisible="False"
HorizontalAlignment="Right" >
<Path Margin="0,-6,14,0"
Height="10"
Width="6"
Stretch="Fill"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Data="M282.875,231.875 L282.875,240.375 L288.625,236 z" />
</Button>
<Grid Name="MonthView"
Grid.Row="1"
Grid.ColumnSpan="3"
IsVisible="False"
Margin="6,-1,6,6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
<Grid Name="YearView"
Grid.Row="1"
Grid.ColumnSpan="3"
IsVisible="False"
Margin="6,-3,7,6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
</Grid>
</Border>
</Border>
<Rectangle Name="DisabledVisual"
Stretch="Fill"
Fill="#FFFFFFFF"
Opacity="{DynamicResource ThemeDisabledOpacity}"
Margin="0,2,0,2" />
</Panel>
</ControlTemplate>
</Setter>
<Setter Property="DayTitleTemplate">
<Template>
<TextBlock FontWeight="Bold"
FontSize="9.5"
Margin="0,4,0,4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding}" />
</Template>
</Setter>
</Style>
<Style Selector="CalendarItem /template/ Rectangle#DisabledVisual">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CalendarItem:calendardisabled /template/ Rectangle#DisabledVisual">
<Setter Property="IsVisible" Value="True"/>
</Style>
</Styles>

31
src/Avalonia.Themes.Default/CheckBox.xaml

@ -12,14 +12,23 @@
Width="18"
Height="18"
VerticalAlignment="Center">
<Path Name="checkMark"
Fill="{DynamicResource HighlightBrush}"
Width="11"
Height="10"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 1145.607177734375,430 C1145.607177734375,430 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1138,434.5538330078125 1138,434.5538330078125 1138,434.5538330078125 1141.482177734375,438 1141.482177734375,438 1141.482177734375,438 1141.96875,437.9375 1141.96875,437.9375 1141.96875,437.9375 1147,431.34619140625 1147,431.34619140625 1147,431.34619140625 1145.607177734375,430 1145.607177734375,430 z"/>
<Panel>
<Path Name="checkMark"
Fill="{DynamicResource HighlightBrush}"
Width="11"
Height="10"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 1145.607177734375,430 C1145.607177734375,430 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1138,434.5538330078125 1138,434.5538330078125 1138,434.5538330078125 1141.482177734375,438 1141.482177734375,438 1141.482177734375,438 1141.96875,437.9375 1141.96875,437.9375 1141.96875,437.9375 1147,431.34619140625 1147,431.34619140625 1147,431.34619140625 1145.607177734375,430 1145.607177734375,430 z"/>
<Rectangle Name="indeterminateMark"
Fill="{DynamicResource HighlightBrush}"
Width="10"
Height="10"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Panel>
</Border>
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
@ -37,9 +46,15 @@
<Style Selector="CheckBox /template/ Path#checkMark">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CheckBox /template/ Rectangle#indeterminateMark">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CheckBox:checked /template/ Path#checkMark">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CheckBox:indeterminate /template/ Rectangle#indeterminateMark">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CheckBox:disabled /template/ Border#border">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style>

4
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -36,4 +36,8 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.TreeViewItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Window.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.EmbeddableControlRoot.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.CalendarButton.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.CalendarDayButton.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.CalendarItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Calendar.xaml?assembly=Avalonia.Themes.Default"/>
</Styles>

14
src/Avalonia.Themes.Default/RadioButton.xaml

@ -20,6 +20,14 @@
UseLayoutRounding="False"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Ellipse Name="indeterminateMark"
Fill="{DynamicResource ThemeAccentBrush}"
Width="10"
Height="10"
Stretch="Uniform"
UseLayoutRounding="False"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
@ -36,9 +44,15 @@
<Style Selector="RadioButton /template/ Ellipse#checkMark">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="RadioButton /template/ Ellipse#indeterminateMark">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="RadioButton:checked /template/ Ellipse#checkMark">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="RadioButton:indeterminate /template/ Ellipse#indeterminateMark">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="RadioButton:disabled /template/ Ellipse#border">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style>

25
src/Avalonia.Visuals/Animation/PageSlide.cs

@ -15,6 +15,15 @@ namespace Avalonia.Animation
/// </summary>
public class PageSlide : IPageTransition
{
/// <summary>
/// The axis on which the PageSlide should occur
/// </summary>
public enum SlideAxis
{
Horizontal,
Vertical
}
/// <summary>
/// Initializes a new instance of the <see cref="PageSlide"/> class.
/// </summary>
@ -26,9 +35,11 @@ namespace Avalonia.Animation
/// Initializes a new instance of the <see cref="PageSlide"/> class.
/// </summary>
/// <param name="duration">The duration of the animation.</param>
public PageSlide(TimeSpan duration)
/// <param name="orientation">The axis on which the animation should occur</param>
public PageSlide(TimeSpan duration, SlideAxis orientation = SlideAxis.Horizontal)
{
Duration = duration;
Orientation = orientation;
}
/// <summary>
@ -36,6 +47,11 @@ namespace Avalonia.Animation
/// </summary>
public TimeSpan Duration { get; set; }
/// <summary>
/// Gets the duration of the animation.
/// </summary>
public SlideAxis Orientation { get; set; }
/// <summary>
/// Starts the animation.
/// </summary>
@ -55,7 +71,8 @@ namespace Avalonia.Animation
{
var tasks = new List<Task>();
var parent = GetVisualParent(from, to);
var distance = parent.Bounds.Width;
var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty;
if (from != null)
{
@ -63,7 +80,7 @@ namespace Avalonia.Animation
from.RenderTransform = transform;
tasks.Add(Animate.Property(
transform,
TranslateTransform.XProperty,
translateProperty,
0.0,
forward ? -distance : distance,
LinearEasing.For<double>(),
@ -77,7 +94,7 @@ namespace Avalonia.Animation
to.IsVisible = true;
tasks.Add(Animate.Property(
transform,
TranslateTransform.XProperty,
translateProperty,
forward ? distance : -distance,
0.0,
LinearEasing.For<double>(),

3
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -35,6 +35,9 @@ namespace Avalonia.Gtk3
X11.XInitThreads();
}catch{}
Resolver.Resolve();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
using (var backends = new Utf8Buffer("x11"))
Native.GdkSetAllowedBackends?.Invoke(backends);
Native.GtkInit(0, IntPtr.Zero);
var disp = Native.GdkGetDefaultDisplay();
DisplayClassName =

8
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -48,10 +48,13 @@ namespace Avalonia.Gtk3.Interop
public delegate void gtk_main_iteration();
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate GtkWindow gtk_window_new(GtkWindowType windowType);
public delegate GtkWindow gtk_window_new(GtkWindowType windowType);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_init(int argc, IntPtr argv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)]
public delegate IntPtr gdk_set_allowed_backends (Utf8Buffer backends);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_present(GtkWindow gtkWindow);
@ -393,6 +396,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_new GtkWindowNew;
public static D.gtk_window_set_icon GtkWindowSetIcon;
public static D.gtk_window_set_modal GtkWindowSetModal;
public static D.gdk_set_allowed_backends GdkSetAllowedBackends;
public static D.gtk_init GtkInit;
public static D.gtk_window_present GtkWindowPresent;
public static D.gtk_widget_hide GtkWidgetHide;

3
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -39,7 +39,6 @@
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
<Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
<Compile Include="PortableXaml\AvaloniaXamlContext.cs" />
<Compile Include="PortableXaml\XamlBinding.cs" />
<Compile Include="PortableXaml\AttributeExtensions.cs" />
<Compile Include="PortableXaml\AvaloniaMemberAttributeProvider.cs" />
<Compile Include="PortableXaml\AvaloniaNameScope.cs" />
@ -73,8 +72,6 @@
<Compile Include="Data\DelayedBinding.cs" />
<Compile Include="Data\MultiBinding.cs" />
<Compile Include="Data\RelativeSource.cs" />
<Compile Include="Data\StyleResourceBinding.cs" />
<Compile Include="MarkupExtensions\StyleResourceExtension.cs" />
<Compile Include="MarkupExtensions\BindingExtension.cs" />
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />

4
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@ -83,6 +83,8 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary>
public object Source { get; set; }
internal WeakReference DefaultAnchor { get; set; }
/// <inheritdoc/>
public InstancedBinding Initiate(
IAvaloniaObject target,
@ -92,6 +94,8 @@ namespace Avalonia.Markup.Xaml.Data
{
Contract.Requires<ArgumentNullException>(target != null);
anchor = anchor ?? DefaultAnchor?.Target;
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;

35
src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs

@ -62,41 +62,20 @@ namespace Avalonia.Markup.Xaml.Data
}
var targetType = targetProperty?.PropertyType ?? typeof(object);
var result = new BehaviorSubject<object>(AvaloniaProperty.UnsetValue);
var children = Bindings.Select(x => x.Initiate(target, null));
var input = children.Select(x => x.Subject).CombineLatest().Select(x => ConvertValue(x, targetType));
input.Subscribe(result);
return new InstancedBinding(result, Mode, Priority);
}
/// <summary>
/// Applies a binding subject to a property on an instance.
/// </summary>
/// <param name="target">The target instance.</param>
/// <param name="property">The target property.</param>
/// <param name="subject">The binding subject.</param>
internal void Bind(IAvaloniaObject target, AvaloniaProperty property, ISubject<object> subject)
{
var mode = Mode == BindingMode.Default ?
property.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
targetProperty.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
switch (mode)
{
case BindingMode.Default:
case BindingMode.OneWay:
target.Bind(property, subject, Priority);
break;
case BindingMode.TwoWay:
throw new NotSupportedException("TwoWay MultiBinding not currently supported.");
case BindingMode.OneTime:
target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
{
subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority));
});
break;
case BindingMode.OneWayToSource:
target.GetObservable(property).Subscribe(subject);
break;
return InstancedBinding.OneTime(input, Priority);
case BindingMode.OneWay:
return InstancedBinding.OneWay(input, Priority);
default:
throw new NotSupportedException(
"MultiBinding currently only supports OneTime and OneWay BindingMode.");
}
}

69
src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs

@ -1,69 +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.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.Data
{
public class StyleResourceBinding : IBinding
{
/// <summary>
/// Initializes a new instance of the <see cref="StyleResourceBinding"/> class.
/// </summary>
/// <param name="name">The resource name.</param>
public StyleResourceBinding(string name)
{
Name = name;
}
/// <inheritdoc/>
public BindingMode Mode => BindingMode.OneTime;
/// <summary>
/// Gets the resource name.
/// </summary>
public string Name { get; }
/// <inheritdoc/>
public BindingPriority Priority => BindingPriority.LocalValue;
/// <inheritdoc/>
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
bool enableDataValidation = false)
{
var host = (target as IControl) ?? (anchor as IControl);
var style = anchor as IStyle;
var resource = AvaloniaProperty.UnsetValue;
if (host != null)
{
resource = host.FindResource(Name);
}
else if (style != null)
{
if (!style.TryGetResource(Name, out resource))
{
resource = AvaloniaProperty.UnsetValue;
}
}
if (resource != AvaloniaProperty.UnsetValue)
{
return new InstancedBinding(resource, Priority);
}
else
{
return null;
}
}
}
}

27
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -7,8 +7,13 @@ using System;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
using Avalonia.Controls;
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
using PortableXaml;
using System.ComponentModel;
[MarkupExtensionReturnType(typeof(IBinding))]
public class BindingExtension : MarkupExtension
@ -24,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public override object ProvideValue(IServiceProvider serviceProvider)
{
var b = new Binding
return new Binding
{
Converter = Converter,
ConverterParameter = ConverterParameter,
@ -33,10 +38,26 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Mode = Mode,
Path = Path,
Priority = Priority,
RelativeSource = RelativeSource
RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
};
}
private static object GetDefaultAnchor(ITypeDescriptorContext context)
{
object anchor = null;
// The target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
anchor = context.GetFirstAmbientValue<IControl>();
return XamlBinding.FromMarkupExtensionContext(b, serviceProvider);
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
context.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
context.GetLastOrDefaultAmbientValue<IStyle>();
}
public IValueConverter Converter { get; set; }

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -51,7 +51,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
if (control != null)
{
return new InstancedBinding(control.GetResourceObservable(ResourceKey));
return InstancedBinding.OneWay(control.GetResourceObservable(ResourceKey));
}
return null;

31
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleResourceExtension.cs

@ -1,31 +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 Avalonia.Data;
using Avalonia.Markup.Xaml.Data;
using System;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
using Portable.Xaml.Markup;
using PortableXaml;
[MarkupExtensionReturnType(typeof(IBinding))]
public class StyleResourceExtension : MarkupExtension
{
public StyleResourceExtension(string name)
{
Name = name;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return XamlBinding.FromMarkupExtensionContext(
new StyleResourceBinding(Name),
serviceProvider);
}
[ConstructorArgument("name")]
public string Name { get; set; }
}
}

12
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs

@ -212,11 +212,6 @@ namespace Avalonia.Markup.Xaml.PortableXaml
value);
}
if (value is XamlBinding)
{
value = (value as XamlBinding).Value;
}
if (UpdateListInsteadSet &&
value != null &&
UpdateListInsteadSetValue(instance, value))
@ -317,9 +312,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
if (!Member.AssignBinding)
ApplyBinding(obj, (IBinding)value);
else
obj.SetValue(Property, value is XamlBinding ?
(value as XamlBinding).Value :
value);
obj.SetValue(Property, value);
}
else
{
@ -348,12 +341,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
{
var control = obj as IControl;
var property = Property;
var xamlBinding = binding as XamlBinding;
if (control != null && property != Control.DataContextProperty)
DelayedBinding.Add(control, property, binding);
else if (xamlBinding != null)
obj.Bind(property, xamlBinding.Value, xamlBinding.Anchor?.Target);
else
obj.Bind(property, binding);
}

63
src/Markup/Avalonia.Markup.Xaml/PortableXaml/XamlBinding.cs

@ -1,63 +0,0 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using Portable.Xaml.Markup;
using System;
namespace Avalonia.Markup.Xaml.PortableXaml
{
internal class XamlBinding : IBinding
{
public static IBinding FromMarkupExtensionContext(
IBinding binding,
IServiceProvider serviceProvider)
{
var context = (ITypeDescriptorContext)serviceProvider;
var pvt = context.GetService<IProvideValueTarget>();
if (pvt.TargetObject is IControl) return binding;
object anchor = GetDefaultAnchor(context);
if (anchor == null) return binding;
return new XamlBinding(binding, anchor);
}
private static object GetDefaultAnchor(ITypeDescriptorContext context)
{
object anchor = null;
// The target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
anchor = context.GetFirstAmbientValue<IControl>();
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
context.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
context.GetLastOrDefaultAmbientValue<IStyle>();
}
private XamlBinding(IBinding binding, object anchor)
{
Value = binding;
Anchor = new WeakReference(anchor);
}
public WeakReference Anchor { get; }
public IBinding Value { get; }
public InstancedBinding Initiate(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor = null, bool enableDataValidation = false)
{
return Value.Initiate(target, targetProperty,
anchor ?? Anchor.Target, enableDataValidation);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -42,7 +42,7 @@ namespace Avalonia.Markup.Xaml.Templates
if (ItemsSource != null)
{
var obs = new ExpressionObserver(item, ItemsSource.Path);
return new InstancedBinding(obs, BindingMode.OneWay, BindingPriority.Style);
return InstancedBinding.OneWay(obs, BindingPriority.Style);
}
return null;

88
src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs

@ -2,44 +2,80 @@
using Avalonia.Platform;
using MonoMac.AppKit;
using System.Runtime.InteropServices;
using Avalonia.Threading;
using MonoMac.CoreGraphics;
namespace Avalonia.MonoMac
{
class EmulatedFramebuffer : ILockedFramebuffer
{
private readonly TopLevelImpl.TopLevelView _view;
private readonly CGSize _logicalSize;
public EmulatedFramebuffer(NSView view)
private readonly bool _isDeferred;
[DllImport("libc")]
static extern void memset(IntPtr p, int c, IntPtr size);
public EmulatedFramebuffer(TopLevelImpl.TopLevelView view)
{
_logicalSize = view.Frame.Size;
var pixelSize = view.ConvertSizeToBacking(_logicalSize);
_view = view;
_isDeferred = !Dispatcher.UIThread.CheckAccess();
_logicalSize = _view.LogicalSize;
var pixelSize = _view.PixelSize;
Width = (int)pixelSize.Width;
Height = (int)pixelSize.Height;
RowBytes = Width * 4;
Dpi = new Vector(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height);
Format = PixelFormat.Rgba8888;
Address = Marshal.AllocHGlobal(Height * RowBytes);
var size = Height * RowBytes;
Address = Marshal.AllocHGlobal(size);
memset(Address, 0, new IntPtr(size));
}
public void Dispose()
{
if (Address == IntPtr.Zero)
return;
var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast;
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4,
colorSpace, (CGImageAlphaInfo) nfo))
using (var image = bContext.ToImage())
using (var nscontext = NSGraphicsContext.CurrentContext)
using (var context = nscontext.GraphicsPort)
CGImage image = null;
try
{
context.SetFillColor(255, 255, 255, 255);
context.FillRect(new CGRect(default(CGPoint), _logicalSize));
context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image);
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4,
colorSpace, (CGImageAlphaInfo)nfo))
image = bContext.ToImage();
lock (_view.SyncRoot)
{
if(!_isDeferred)
{
using (var nscontext = NSGraphicsContext.CurrentContext)
using (var context = nscontext.GraphicsPort)
{
context.SetFillColor(255, 255, 255, 255);
context.FillRect(new CGRect(default(CGPoint), _view.LogicalSize));
context.TranslateCTM(0, _view.LogicalSize.Height - _logicalSize.Height);
context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image);
context.Flush();
nscontext.FlushGraphics();
}
}
}
}
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
finally
{
if (image != null)
{
if (!_isDeferred)
image.Dispose();
else
_view.SetBackBufferImage(new SavedImage(image, _logicalSize));
}
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
}
}
public IntPtr Address { get; private set; }
@ -49,4 +85,22 @@ namespace Avalonia.MonoMac
public Vector Dpi { get; }
public PixelFormat Format { get; }
}
class SavedImage : IDisposable
{
public CGImage Image { get; private set; }
public CGSize LogicalSize { get; }
public SavedImage(CGImage image, CGSize logicalSize)
{
Image = image;
LogicalSize = logicalSize;
}
public void Dispose()
{
Image?.Dispose();
Image = null;
}
}
}

46
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@ -1,10 +1,13 @@
using System;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Rendering;
using MonoMac.AppKit;
using MonoMac.Foundation;
using MonoMac.ObjCRuntime;
namespace Avalonia.MonoMac
{
@ -16,9 +19,11 @@ namespace Avalonia.MonoMac
internal static NSApplication App;
private static bool s_monoMacInitialized;
private static bool s_showInDock = true;
private static IRenderLoop s_renderLoop;
void DoInitialize()
{
InitializeMonoMac();
AvaloniaLocator.CurrentMutable
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
@ -27,9 +32,8 @@ namespace Avalonia.MonoMac
.Bind<IPlatformSettings>().ToConstant(this)
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsImpl>()
.Bind<IRenderLoop>().ToConstant(s_renderLoop)
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
InitializeMonoMac();
}
public static void Initialize()
@ -39,13 +43,44 @@ namespace Avalonia.MonoMac
}
/// <summary>
/// See "Using POSIX Threads in a Cocoa Application" section here:
/// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024
/// </summary>
class ThreadHelper : NSObject
{
private readonly AutoResetEvent _event = new AutoResetEvent(false);
private const string InitThreadingName = "initThreading";
[Export(InitThreadingName)]
public void DoNothing()
{
_event.Set();
}
public static void InitializeCocoaThreadingLocks()
{
var helper = new ThreadHelper();
var thread = new NSThread(helper, Selector.FromHandle(Selector.GetHandle(InitThreadingName)), new NSObject());
thread.Start();
helper._event.WaitOne();
helper._event.Dispose();
if (!NSThread.IsMultiThreaded)
{
throw new Exception("Unable to initialize Cocoa threading");
}
}
}
void InitializeMonoMac()
{
if(s_monoMacInitialized)
return;
NSApplication.Init();
ThreadHelper.InitializeCocoaThreadingLocks();
App = NSApplication.SharedApplication;
UpdateActivationPolicy();
s_renderLoop = new RenderLoop(); //TODO: use CVDisplayLink
s_monoMacInitialized = true;
}
@ -64,6 +99,7 @@ namespace Avalonia.MonoMac
}
}
public static bool UseDeferredRendering { get; set; } = true;
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromSeconds(NSEvent.DoubleClickInterval);
@ -87,9 +123,11 @@ namespace Avalonia
{
public static class MonoMacPlatformExtensions
{
public static T UseMonoMac<T>(this T builder) where T : AppBuilderBase<T>, new()
public static T UseMonoMac<T>(this T builder, bool? useDeferredRendering = null) where T : AppBuilderBase<T>, new()
{
return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize);
if (useDeferredRendering.HasValue)
MonoMac.MonoMacPlatform.UseDeferredRendering = useDeferredRendering.Value;
return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize, "MonoMac");
}
}
}

32
src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs

@ -3,14 +3,18 @@ using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
using MonoMac.AppKit;
using MonoMac.CoreFoundation;
using MonoMac.CoreGraphics;
using MonoMac.Foundation;
using MonoMac.ObjCRuntime;
namespace Avalonia.MonoMac
{
class PlatformThreadingInterface : IPlatformThreadingInterface
class PlatformThreadingInterface : NSObject, IPlatformThreadingInterface
{
private bool _signaled;
private const string SignaledSelectorName = "avaloniauiSignaled";
private readonly Selector _signaledSelector = new Selector(SignaledSelectorName);
public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread;
@ -27,18 +31,25 @@ namespace Avalonia.MonoMac
return;
_signaled = true;
}
NSApplication.SharedApplication.BeginInvokeOnMainThread(() =>
{
lock (this)
PerformSelector(_signaledSelector, NSThread.MainThread, this, false,
new[]
{
if (!_signaled)
return;
_signaled = false;
}
Signaled?.Invoke(null);
});
NSRunLoop.NSDefaultRunLoopMode, NSRunLoop.NSRunLoopEventTracking, NSRunLoop.NSRunLoopModalPanelMode,
NSRunLoop.NSRunLoopCommonModes, NSRunLoop.NSRunLoopConnectionReplyMode
});
}
[Export(SignaledSelectorName)]
public void CallSignaled()
{
lock (this)
{
if (!_signaled)
return;
_signaled = false;
}
Signaled?.Invoke(null);
}
public void RunLoop(CancellationToken cancellationToken)
@ -55,6 +66,7 @@ namespace Avalonia.MonoMac
var ev = app.NextEvent(NSEventMask.AnyEvent, NSDate.DistantFuture, NSRunLoop.NSDefaultRunLoopMode, true);
if (ev != null)
{
Console.WriteLine("NSEVENT");
app.SendEvent(ev);
ev.Dispose();
}

33
src/OSX/Avalonia.MonoMac/RenderLoop.cs

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Platform;
using Avalonia.Rendering;
using MonoMac.Foundation;
namespace Avalonia.MonoMac
{
//TODO: Switch to using CVDisplayLink
public class RenderLoop : IRenderLoop
{
private readonly object _lock = new object();
private readonly IDisposable _timer;
public RenderLoop()
{
_timer = AvaloniaLocator.Current.GetService<IRuntimePlatform>().StartSystemTimer(new TimeSpan(0, 0, 0, 0, 1000 / 60),
() =>
{
lock (_lock)
{
using (new NSAutoreleasePool())
{
Tick?.Invoke(this, EventArgs.Empty);
}
}
});
}
public event EventHandler<EventArgs> Tick;
}
}

89
src/OSX/Avalonia.MonoMac/TopLevelImpl.cs

@ -5,8 +5,9 @@ using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Rendering;
using Avalonia.Threading;
using MonoMac.AppKit;
using MonoMac.CoreFoundation;
using MonoMac.CoreGraphics;
using MonoMac.Foundation;
using MonoMac.ObjCRuntime;
@ -24,6 +25,7 @@ namespace Avalonia.MonoMac
protected virtual void OnInput(RawInputEventArgs args)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(args);
}
@ -36,6 +38,14 @@ namespace Avalonia.MonoMac
private readonly IKeyboardDevice _keyboard;
private NSTrackingArea _area;
private NSCursor _cursor;
private bool _nonUiRedrawQueued;
public CGSize PixelSize { get; set; }
public CGSize LogicalSize { get; set; }
private SavedImage _backBuffer;
public object SyncRoot { get; } = new object();
public TopLevelView(TopLevelImpl tl)
{
@ -44,17 +54,75 @@ namespace Avalonia.MonoMac
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_backBuffer?.Dispose();
_backBuffer = null;
}
base.Dispose(disposing);
}
public override bool ConformsToProtocol(IntPtr protocol)
{
var rv = base.ConformsToProtocol(protocol);
return rv;
}
public override bool IsOpaque => false;
public override void DrawRect(CGRect dirtyRect)
{
lock (SyncRoot)
_nonUiRedrawQueued = false;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
lock (SyncRoot)
{
if (_backBuffer != null)
{
using (var context = NSGraphicsContext.CurrentContext.GraphicsPort)
{
context.SetFillColor(255, 255, 255, 255);
context.FillRect(new CGRect(default(CGPoint), LogicalSize));
context.TranslateCTM(0, LogicalSize.Height - _backBuffer.LogicalSize.Height);
context.DrawImage(new CGRect(default(CGPoint), _backBuffer.LogicalSize), _backBuffer.Image);
context.Flush();
NSGraphicsContext.CurrentContext.FlushGraphics();
}
}
}
_tl.Paint?.Invoke(dirtyRect.ToAvaloniaRect());
}
public void SetBackBufferImage(SavedImage image)
{
lock (SyncRoot)
{
_backBuffer?.Dispose();
_backBuffer = image;
if (image == null)
return;
if (_nonUiRedrawQueued)
return;
_nonUiRedrawQueued = true;
Dispatcher.UIThread.InvokeAsync(
() =>
{
lock (SyncRoot)
{
if (!_nonUiRedrawQueued)
return;
_nonUiRedrawQueued = false;
}
SetNeedsDisplayInRect(Frame);
Display();
}, DispatcherPriority.Render);
}
}
[Export("viewDidChangeBackingProperties:")]
public void ViewDidChangeBackingProperties()
{
@ -78,7 +146,12 @@ namespace Avalonia.MonoMac
public override void SetFrameSize(CGSize newSize)
{
base.SetFrameSize(newSize);
lock (SyncRoot)
{
base.SetFrameSize(newSize);
LogicalSize = Frame.Size;
PixelSize = ConvertSizeToBacking(LogicalSize);
}
if (_area != null)
{
@ -92,6 +165,7 @@ namespace Avalonia.MonoMac
AddTrackingArea(_area);
UpdateCursor();
_tl?.Resized?.Invoke(_tl.ClientSize);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
}
InputModifiers GetModifiers(NSEventModifierMask mod)
@ -348,9 +422,16 @@ namespace Avalonia.MonoMac
View.Dispose();
}
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);
public IRenderer CreateRenderer(IRenderRoot root) =>
MonoMacPlatform.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>())
: (IRenderer) new ImmediateRenderer(root);
public void Invalidate(Rect rect) => View.SetNeedsDisplayInRect(View.Frame);
public void Invalidate(Rect rect)
{
if (!MonoMacPlatform.UseDeferredRendering)
View.SetNeedsDisplayInRect(View.Frame);
}
public abstract Point PointToClient(Point point);

2
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -428,7 +428,7 @@ namespace Avalonia.Base.UnitTests
object anchor = null,
bool enableDataValidation = false)
{
return new InstancedBinding(_source, BindingMode.OneTime);
return InstancedBinding.OneTime(_source);
}
}
}

277
tests/Avalonia.Controls.UnitTests/CalendarTests.cs

@ -0,0 +1,277 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Xunit;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Controls.UnitTests
{
public class CalendarTests
{
private static bool CompareDates(DateTime first, DateTime second)
{
return first.Year == second.Year &&
first.Month == second.Month &&
first.Day == second.Day;
}
[Fact]
public void SelectedDatesChanged_Should_Fire_When_SelectedDate_Set()
{
bool handled = false;
Calendar calendar = new Calendar();
calendar.SelectionMode = CalendarSelectionMode.SingleDate;
calendar.SelectedDatesChanged += new EventHandler<SelectionChangedEventArgs>(delegate
{
handled = true;
});
DateTime value = new DateTime(2000, 10, 10);
calendar.SelectedDate = value;
Assert.True(handled);
}
[Fact]
public void DisplayDateChanged_Should_Fire_When_DisplayDate_Set()
{
bool handled = false;
Calendar calendar = new Calendar();
calendar.SelectionMode = CalendarSelectionMode.SingleDate;
calendar.DisplayDateChanged += new EventHandler<CalendarDateChangedEventArgs>(delegate
{
handled = true;
});
DateTime value = new DateTime(2000, 10, 10);
calendar.DisplayDate = value;
Assert.True(handled);
}
[Fact]
public void Setting_Selected_Date_To_Blackout_Date_Should_Throw()
{
Calendar calendar = new Calendar();
calendar.BlackoutDates.AddDatesInPast();
Assert.ThrowsAny<ArgumentOutOfRangeException>(
() => calendar.SelectedDate = DateTime.Today.AddDays(-1));
}
[Fact]
public void Setting_Selected_Date_To_Blackout_Date_Should_Throw_Range()
{
Calendar calendar = new Calendar();
calendar.BlackoutDates.Add(new CalendarDateRange(DateTime.Today, DateTime.Today.AddDays(10)));
calendar.SelectedDate = DateTime.Today.AddDays(-1);
Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(-1)));
Assert.True(CompareDates(calendar.SelectedDate.Value, calendar.SelectedDates[0]));
calendar.SelectedDate = DateTime.Today.AddDays(11);
Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(11)));
Assert.True(CompareDates(calendar.SelectedDate.Value, calendar.SelectedDates[0]));
Assert.ThrowsAny<ArgumentOutOfRangeException>(
() => calendar.SelectedDate = DateTime.Today.AddDays(5));
}
[Fact]
public void Adding_Blackout_Dates_Containing_Selected_Date_Should_Throw()
{
Calendar calendar = new Calendar();
calendar.SelectedDate = DateTime.Today.AddDays(5);
Assert.ThrowsAny<ArgumentOutOfRangeException>(
() => calendar.BlackoutDates.Add(new CalendarDateRange(DateTime.Today, DateTime.Today.AddDays(10))));
}
[Fact]
public void DisplayDateStartEnd_Should_Constrain_Display_Date()
{
Calendar calendar = new Calendar();
calendar.SelectionMode = CalendarSelectionMode.SingleDate;
calendar.DisplayDateStart = new DateTime(2005, 12, 30);
DateTime value = new DateTime(2005, 12, 15);
calendar.DisplayDate = value;
Assert.True(CompareDates(calendar.DisplayDate, calendar.DisplayDateStart.Value));
value = new DateTime(2005, 12, 30);
calendar.DisplayDate = value;
Assert.True(CompareDates(calendar.DisplayDate, value));
value = DateTime.MaxValue;
calendar.DisplayDate = value;
Assert.True(CompareDates(calendar.DisplayDate, value));
calendar.DisplayDateEnd = new DateTime(2010, 12, 30);
Assert.True(CompareDates(calendar.DisplayDate, calendar.DisplayDateEnd.Value));
}
[Fact]
public void Setting_DisplayDateEnd_Should_Alter_DispalyDate_And_DisplayDateStart()
{
Calendar calendar = new Calendar();
DateTime value = new DateTime(2000, 1, 30);
calendar.DisplayDate = value;
calendar.DisplayDateEnd = value;
calendar.DisplayDateStart = value;
Assert.True(CompareDates(calendar.DisplayDateStart.Value, value));
Assert.True(CompareDates(calendar.DisplayDateEnd.Value, value));
value = value.AddMonths(2);
calendar.DisplayDateStart = value;
Assert.True(CompareDates(calendar.DisplayDateStart.Value, value));
Assert.True(CompareDates(calendar.DisplayDateEnd.Value, value));
Assert.True(CompareDates(calendar.DisplayDate, value));
}
[Fact]
public void Display_Date_Range_End_Will_Contain_SelectedDate()
{
Calendar calendar = new Calendar();
calendar.SelectionMode = CalendarSelectionMode.SingleDate;
calendar.SelectedDate = DateTime.MaxValue;
Assert.True(CompareDates((DateTime)calendar.SelectedDate, DateTime.MaxValue));
calendar.DisplayDateEnd = DateTime.MaxValue.AddDays(-1);
Assert.True(CompareDates((DateTime)calendar.DisplayDateEnd, DateTime.MaxValue));
}
/// <summary>
/// The days added to the SelectedDates collection.
/// </summary>
private IList<object> _selectedDatesChangedAddedDays;
/// <summary>
/// The days removed from the SelectedDates collection.
/// </summary>
private IList<object> _selectedDateChangedRemovedDays;
/// <summary>
/// The number of times the SelectedDatesChanged event has been fired.
/// </summary>
private int _selectedDatesChangedCount;
/// <summary>
/// Handle the SelectedDatesChanged event.
/// </summary>
/// <param name="sender">The calendar.</param>
/// <param name="e">Event arguments.</param>
private void OnSelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
_selectedDatesChangedAddedDays =
e.AddedItems
.Cast<object>()
.ToList();
_selectedDateChangedRemovedDays =
e.RemovedItems
.Cast<object>()
.ToList();
_selectedDatesChangedCount++;
}
/// <summary>
/// Clear the variables used to track the SelectedDatesChanged event.
/// </summary>
private void ResetSelectedDatesChanged()
{
if (_selectedDatesChangedAddedDays != null)
{
_selectedDatesChangedAddedDays.Clear();
}
if (_selectedDateChangedRemovedDays != null)
{
_selectedDateChangedRemovedDays.Clear();
}
_selectedDatesChangedCount = 0;
}
[Fact]
public void SingleDate_Selection_Behavior()
{
ResetSelectedDatesChanged();
Calendar calendar = new Calendar();
calendar.SelectedDatesChanged += new EventHandler<SelectionChangedEventArgs>(OnSelectedDatesChanged);
calendar.SelectionMode = CalendarSelectionMode.SingleDate;
calendar.SelectedDate = DateTime.Today;
Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today));
Assert.True(calendar.SelectedDates.Count == 1);
Assert.True(CompareDates(calendar.SelectedDates[0], DateTime.Today));
Assert.True(_selectedDatesChangedCount == 1);
Assert.True(_selectedDatesChangedAddedDays.Count == 1);
Assert.True(_selectedDateChangedRemovedDays.Count == 0);
ResetSelectedDatesChanged();
calendar.SelectedDate = DateTime.Today;
Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today));
Assert.True(calendar.SelectedDates.Count == 1);
Assert.True(CompareDates(calendar.SelectedDates[0], DateTime.Today));
Assert.True(_selectedDatesChangedCount == 0);
calendar.ClearValue(Calendar.SelectedDateProperty);
calendar.SelectionMode = CalendarSelectionMode.None;
Assert.True(calendar.SelectedDates.Count == 0);
Assert.Null(calendar.SelectedDate);
calendar.SelectionMode = CalendarSelectionMode.SingleDate;
calendar.SelectedDates.Add(DateTime.Today.AddDays(1));
Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(1)));
Assert.True(calendar.SelectedDates.Count == 1);
Assert.ThrowsAny<InvalidOperationException>(
() => calendar.SelectedDates.Add(DateTime.Today.AddDays(2)));
}
[Fact]
public void SingleRange_Selection_Behavior()
{
ResetSelectedDatesChanged();
Calendar calendar = new Calendar();
calendar.SelectedDatesChanged += new EventHandler<SelectionChangedEventArgs>(OnSelectedDatesChanged);
calendar.SelectionMode = CalendarSelectionMode.SingleRange;
calendar.SelectedDate = DateTime.Today;
Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today));
Assert.True(calendar.SelectedDates.Count == 1);
Assert.True(CompareDates(calendar.SelectedDates[0], DateTime.Today));
Assert.True(_selectedDatesChangedCount == 1);
Assert.True(_selectedDatesChangedAddedDays.Count == 1);
Assert.True(_selectedDateChangedRemovedDays.Count == 0);
ResetSelectedDatesChanged();
calendar.SelectedDates.Clear();
Assert.Null(calendar.SelectedDate);
ResetSelectedDatesChanged();
calendar.SelectedDates.AddRange(DateTime.Today, DateTime.Today.AddDays(10));
Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today));
Assert.True(calendar.SelectedDates.Count == 11);
ResetSelectedDatesChanged();
calendar.SelectedDates.AddRange(DateTime.Today, DateTime.Today.AddDays(10));
Assert.True(calendar.SelectedDates.Count == 11);
Assert.True(_selectedDatesChangedCount == 0);
ResetSelectedDatesChanged();
calendar.SelectedDates.AddRange(DateTime.Today.AddDays(-20), DateTime.Today);
Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(-20)));
Assert.True(calendar.SelectedDates.Count == 21);
Assert.True(_selectedDatesChangedCount == 1);
Assert.True(_selectedDatesChangedAddedDays.Count == 21);
Assert.True(_selectedDateChangedRemovedDays.Count == 11);
ResetSelectedDatesChanged();
calendar.SelectedDates.Add(DateTime.Today.AddDays(100));
Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(100)));
Assert.True(calendar.SelectedDates.Count == 1);
}
}
}

10
tests/Avalonia.Controls.UnitTests/ClassesTests.cs

@ -161,5 +161,15 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { ":baz" }, target);
}
[Fact]
public void RemoveAll_Should_Remove_Classes()
{
var target = new Classes("foo", "bar", "baz");
target.RemoveAll(new[] { "bar", "baz" });
Assert.Equal(new[] { "foo" }, target);
}
}
}

44
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -249,6 +249,37 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
[Fact]
public void DataContextBeginUpdate_Should_Not_Be_Called_For_Controls_That_Dont_Inherit()
{
using (CreateServices())
{
TestControl child;
var popup = new Popup
{
Child = child = new TestControl(),
DataContext = "foo",
};
var beginCalled = false;
child.DataContextBeginUpdate += (s, e) => beginCalled = true;
// Test for #1245. Here, the child's logical parent is the popup but it's not yet
// attached to a visual tree because the popup hasn't been opened.
Assert.Same(popup, ((ILogical)child).LogicalParent);
Assert.Same(popup, child.InheritanceParent);
Assert.Null(child.GetVisualRoot());
popup.Open();
// #1245 was caused by the fact that DataContextBeginUpdate was called on `target`
// when the PopupRoot was created, even though PopupRoot isn't the
// InheritanceParent of child.
Assert.False(beginCalled);
}
}
private static IDisposable CreateServices()
{
var result = AvaloniaLocator.EnterScope();
@ -304,5 +335,18 @@ namespace Avalonia.Controls.UnitTests.Primitives
private class PopupContentControl : ContentControl
{
}
private class TestControl : Decorator
{
public event EventHandler DataContextBeginUpdate;
public new IAvaloniaObject InheritanceParent => base.InheritanceParent;
protected override void OnDataContextBeginUpdate()
{
DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
base.OnDataContextBeginUpdate();
}
}
}
}

57
tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs

@ -0,0 +1,57 @@
using Avalonia.Markup.Xaml.Data;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.Primitives.UnitTests
{
public class ToggleButtonTests
{
private const string uncheckedClass = ":unchecked";
private const string checkedClass = ":checked";
private const string indeterminateClass = ":indeterminate";
[Theory]
[InlineData(false, uncheckedClass, false)]
[InlineData(false, uncheckedClass, true)]
[InlineData(true, checkedClass, false)]
[InlineData(true, checkedClass, true)]
[InlineData(null, indeterminateClass, false)]
[InlineData(null, indeterminateClass, true)]
public void ToggleButton_Has_Correct_Class_According_To_Is_Checked(bool? isChecked, string expectedClass, bool isThreeState)
{
var toggleButton = new ToggleButton();
toggleButton.IsThreeState = isThreeState;
toggleButton.IsChecked = isChecked;
Assert.Contains(expectedClass, toggleButton.Classes);
}
[Fact]
public void ToggleButton_Is_Checked_Binds_To_Bool()
{
var toggleButton = new ToggleButton();
var source = new Class1();
toggleButton.DataContext = source;
toggleButton.Bind(ToggleButton.IsCheckedProperty, new Binding("Foo"));
source.Foo = true;
Assert.True(toggleButton.IsChecked);
source.Foo = false;
Assert.False(toggleButton.IsChecked);
}
private class Class1 : NotifyingBase
{
private bool _foo;
public bool Foo
{
get { return _foo; }
set { _foo = value; RaisePropertyChanged(); }
}
}
}
}

36
tests/Avalonia.Controls.UnitTests/RadioButtonTests.cs

@ -0,0 +1,36 @@
using Avalonia.Markup.Xaml.Data;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class RadioButtonTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Indeterminate_RadioButton_Is_Not_Unchecked_After_Checking_Other_Radio_Button(bool isThreeState)
{
var panel = new Panel();
var radioButton1 = new RadioButton();
radioButton1.IsThreeState = false;
radioButton1.IsChecked = false;
var radioButton2 = new RadioButton();
radioButton2.IsThreeState = isThreeState;
radioButton2.IsChecked = null;
panel.Children.Add(radioButton1);
panel.Children.Add(radioButton2);
Assert.Null(radioButton2.IsChecked);
radioButton1.IsChecked = true;
Assert.True(radioButton1.IsChecked);
Assert.Null(radioButton2.IsChecked);
}
}
}

2
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -515,7 +515,7 @@ namespace Avalonia.Controls.UnitTests
public InstancedBinding ItemsSelector(object item)
{
var obs = new ExpressionObserver(item, nameof(Node.Children));
return new InstancedBinding(obs);
return InstancedBinding.OneWay(obs);
}
public bool Match(object data)

113
tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs

@ -0,0 +1,113 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Layout.UnitTests
{
public class ShapeLayoutTests : TestWithServicesBase
{
public ShapeLayoutTests()
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToSingleton<MockPlatformRenderInterface>();
}
[Fact]
public void Shape_Transformation_Calculation_Should_Be_Deferred_To_Arrange_When_Strech_Is_Fill_And_Aviable_Size_Is_Infinite()
{
var shape = new Polygon()
{
Points = new List<Point>
{
new Point(0, 0),
new Point(10, 5),
new Point(0, 10)
},
Stretch = Stretch.Fill
};
var availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
shape.Measure(availableSize);
Geometry postMeasureGeometry = shape.RenderedGeometry;
Transform postMeasureTransform = postMeasureGeometry.Transform;
var finalSize = new Size(100, 50);
var finalRect = new Rect(finalSize);
shape.Arrange(finalRect);
Geometry postArrangeGeometry = shape.RenderedGeometry;
Transform postArrangeTransform = postArrangeGeometry.Transform;
Assert.NotEqual(postMeasureGeometry, postArrangeGeometry);
Assert.NotEqual(postMeasureTransform, postArrangeTransform);
Assert.Equal(finalSize, shape.Bounds.Size);
}
[Fact]
public void Shape_Transformation_Calculation_Should_Not_Be_Deferred_To_Arrange_When_Strech_Is_Fill_And_Aviable_Size_Is_Finite()
{
var shape = new Polygon()
{
Points = new List<Point>
{
new Point(0, 0),
new Point(10, 5),
new Point(0, 10)
},
Stretch = Stretch.Fill
};
var availableSize = new Size(100, 50);
shape.Measure(availableSize);
Geometry postMeasureGeometry = shape.RenderedGeometry;
Transform postMeasureTransform = postMeasureGeometry.Transform;
var finalRect = new Rect(availableSize);
shape.Arrange(finalRect);
Geometry postArrangeGeometry = shape.RenderedGeometry;
Transform postArrangeTransform = postArrangeGeometry.Transform;
Assert.Equal(postMeasureGeometry, postArrangeGeometry);
Assert.Equal(postMeasureTransform, postArrangeTransform);
Assert.Equal(availableSize, shape.Bounds.Size);
}
[Fact]
public void Shape_Transformation_Calculation_Should_Not_Be_Deferred_To_Arrange_When_Strech_Is_None()
{
var shape = new Polygon()
{
Points = new List<Point>
{
new Point(0, 0),
new Point(10, 5),
new Point(0, 10)
},
Stretch = Stretch.None
};
var availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
shape.Measure(availableSize);
Geometry postMeasureGeometry = shape.RenderedGeometry;
Transform postMeasureTransform = postMeasureGeometry.Transform;
var finalSize = new Size(100, 50);
var finalRect = new Rect(finalSize);
shape.Arrange(finalRect);
Geometry postArrangeGeometry = shape.RenderedGeometry;
Transform postArrangeTransform = postArrangeGeometry.Transform;
Assert.Equal(postMeasureGeometry, postArrangeGeometry);
Assert.Equal(postMeasureTransform, postArrangeTransform);
Assert.Equal(finalSize, shape.Bounds.Size);
}
}
}

1
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -8,7 +8,6 @@
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Sprache.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

4
tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs

@ -34,8 +34,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
var target = new Mock<IAvaloniaObject>().As<IControl>();
target.Setup(x => x.GetValue(Control.DataContextProperty)).Returns(source);
var subject = binding.Initiate(target.Object, null).Subject;
var result = await subject.Take(1);
var observable = binding.Initiate(target.Object, null).Observable;
var result = await observable.Take(1);
Assert.Equal("1,2,3", result);
}

19
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -411,8 +411,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'>
<Style Selector='CheckBox'>
<Setter Property='BorderBrush' Value='{StyleResource ThemeBorderMidBrush}'/>
<Setter Property='BorderThickness' Value='{StyleResource ThemeBorderThickness}'/>
<Setter Property='BorderBrush' Value='{DynamicResource ThemeBorderMidBrush}'/>
<Setter Property='BorderThickness' Value='{DynamicResource ThemeBorderThickness}'/>
<Setter Property='Template'>
<ControlTemplate>
<Grid ColumnDefinitions='Auto,*'>
@ -423,7 +423,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Height='18'
VerticalAlignment='Center'>
<Path Name='checkMark'
Fill='{StyleResource HighlightBrush}'
Fill='{StaticResource HighlightBrush}'
Width='11'
Height='10'
Stretch='Uniform'
@ -457,8 +457,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(CheckBox.BorderThicknessProperty, setters[1].Property);
Assert.Equal(CheckBox.TemplateProperty, setters[2].Property);
Assert.IsType<StyleResourceBinding>(setters[0].Value);
Assert.IsType<StyleResourceBinding>(setters[1].Value);
Assert.IsType<ControlTemplate>(setters[2].Value);
}
}
@ -772,15 +770,8 @@ do we need it?")]
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Window.Styles>
<Style>
<Style.Resources>
<x:Double x:Key='Double'>100</x:Double>
</Style.Resources>
</Style>
</Window.Styles>
<local:InitializationOrderTracker Width='100' Height='{StyleResource Double}'
Tag='{Binding Height, RelativeSource={RelativeSource Self}}' />
<local:InitializationOrderTracker Width='100' Height='100'
Tag='{Binding Height, RelativeSource={RelativeSource Self}}' />
</Window>";

165
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -61,171 +61,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
[Fact]
public void StyleResource_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Style.Resources>
</Style>
</UserControl.Styles>
<Border Name='border' Background='{StyleResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void StyleResource_Can_Be_Assigned_To_Setter()
{
//skip default theme and styles, they are not needed
using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles())))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Style.Resources>
</Style>
<Style Selector='Button'>
<Setter Property='Background' Value='{StyleResource brush}'/>
</Style>
</Window.Styles>
<Button Name='button'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void StyleResource_Can_Be_Assigned_To_StyleResource_Property()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style>
<Style.Resources>
<Color x:Key='color'>#ff506070</Color>
<SolidColorBrush x:Key='brush' Color='{StyleResource color}'/>
</Style.Resources>
</Style>
</Window.Styles>
<Button Name='button' Background='{StyleResource brush}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var brush = (ISolidColorBrush)window.FindResource("brush");
var button = window.FindControl<Button>("button");
DelayedBinding.ApplyBindings(button);
var buttonBrush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
Assert.Equal(0xff506070, buttonBrush.Color.ToUint32());
}
}
[Fact]
public void StyleResource_Can_Be_Found_In_TopLevel_Styles()
{
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style>
<Style.Resources>
<Color x:Key='color'>#ff506070</Color>
<SolidColorBrush x:Key='brush' Color='{StyleResource color}'/>
</Style.Resources>
</Style>
</Styles>";
var loader = new AvaloniaXamlLoader();
var styles = (Styles)loader.Load(xaml);
styles.TryGetResource("brush", out var brush);
Assert.Equal(0xff506070, ((SolidColorBrush)brush).Color.ToUint32());
}
[Fact]
public void StyleResource_Can_Be_Found_In_Sibling_Styles()
{
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style>
<Style.Resources>
<Color x:Key='color'>#ff506070</Color>
</Style.Resources>
</Style>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush' Color='{StyleResource color}'/>
</Style.Resources>
</Style>
</Styles>";
var loader = new AvaloniaXamlLoader();
var styles = (Styles)loader.Load(xaml);
styles.TryGetResource("brush", out var brush);
Assert.Equal(0xff506070, ((SolidColorBrush)brush).Color.ToUint32());
}
[Fact(Skip = "TODO: Issue #492")]
public void StyleResource_Can_Be_Found_Across_Xaml_Files()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='resm:Avalonia.Markup.Xaml.UnitTests.Xaml.Style1.xaml?assembly=Avalonia.Markup.Xaml.UnitTests'/>
<StyleInclude Source='resm:Avalonia.Markup.Xaml.UnitTests.Xaml.Style2.xaml?assembly=Avalonia.Markup.Xaml.UnitTests'/>
</Window.Styles>
<Border Name='border' Background='{StyleResource RedBrush}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var border = window.FindControl<Border>("border");
var borderBrush = (ISolidColorBrush)border.Background;
Assert.NotNull(borderBrush);
Assert.Equal(0xffff0000, borderBrush.Color.ToUint32());
}
}
[Fact]
public void StyleInclude_Is_Built()
{

73
tests/Avalonia.Styling.UnitTests/SetterTests.cs

@ -29,7 +29,7 @@ namespace Avalonia.Styling.UnitTests
{
var control = new TextBlock();
var subject = new BehaviorSubject<object>("foo");
var descriptor = new InstancedBinding(subject);
var descriptor = InstancedBinding.OneWay(subject);
var binding = Mock.Of<IBinding>(x => x.Initiate(control, TextBlock.TextProperty, null, false) == descriptor);
var style = Mock.Of<IStyle>();
var setter = new Setter(TextBlock.TextProperty, binding);
@ -86,6 +86,77 @@ namespace Avalonia.Styling.UnitTests
Assert.Null(control.Tag);
}
[Fact]
public void Setter_Should_Apply_Value_Without_Activator_With_Style_Priority()
{
var control = new Mock<IStyleable>();
var style = Mock.Of<Style>();
var setter = new Setter(TextBlock.TextProperty, "foo");
setter.Apply(style, control.Object, null);
control.Verify(x => x.Bind(
TextBlock.TextProperty,
It.IsAny<IObservable<object>>(),
BindingPriority.Style));
}
[Fact]
public void Setter_Should_Apply_Value_With_Activator_With_StyleTrigger_Priority()
{
var control = new Mock<IStyleable>();
var style = Mock.Of<Style>();
var setter = new Setter(TextBlock.TextProperty, "foo");
var activator = new Subject<bool>();
setter.Apply(style, control.Object, activator);
control.Verify(x => x.Bind(
TextBlock.TextProperty,
It.IsAny<IObservable<object>>(),
BindingPriority.StyleTrigger));
}
[Fact]
public void Setter_Should_Apply_Binding_Without_Activator_With_Style_Priority()
{
var control = new Mock<IStyleable>();
var style = Mock.Of<Style>();
var setter = new Setter(TextBlock.TextProperty, CreateMockBinding(TextBlock.TextProperty));
setter.Apply(style, control.Object, null);
control.Verify(x => x.Bind(
TextBlock.TextProperty,
It.IsAny<IObservable<object>>(),
BindingPriority.Style));
}
[Fact]
public void Setter_Should_Apply_Binding_With_Activator_With_StyleTrigger_Priority()
{
var control = new Mock<IStyleable>();
var style = Mock.Of<Style>();
var setter = new Setter(TextBlock.TextProperty, CreateMockBinding(TextBlock.TextProperty));
var activator = new Subject<bool>();
setter.Apply(style, control.Object, activator);
control.Verify(x => x.Bind(
TextBlock.TextProperty,
It.IsAny<IObservable<object>>(),
BindingPriority.StyleTrigger));
}
private IBinding CreateMockBinding(AvaloniaProperty property)
{
var subject = new Subject<object>();
var descriptor = InstancedBinding.OneWay(subject);
var binding = Mock.Of<IBinding>(x =>
x.Initiate(It.IsAny<IAvaloniaObject>(), property, null, false) == descriptor);
return binding;
}
private class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

23
tests/Avalonia.UnitTests/TestWithServicesBase.cs

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.UnitTests
{
public class TestWithServicesBase : IDisposable
{
private IDisposable _scope;
public TestWithServicesBase()
{
_scope = AvaloniaLocator.EnterScope();
}
public void Dispose()
{
_scope.Dispose();
}
}
}
Loading…
Cancel
Save