Browse Source

Merge branch 'issues/855_test' of https://github.com/donandren/Avalonia into issues/855_test

pull/856/head
Jeremy Koritzinsky 9 years ago
parent
commit
02e600863d
  1. 3
      Avalonia.sln
  2. 6
      appveyor.yml
  3. 2
      build/MonoMac.props
  4. 5
      build/Serilog.Sinks.Trace.props
  5. 4
      build/Serilog.props
  6. 5
      build/Splat.props
  7. 10
      packages.cake
  8. 13
      samples/BindingTest/App.xaml.cs
  9. 2
      samples/BindingTest/BindingTest.csproj
  10. 1
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  11. 15
      samples/ControlCatalog.Desktop/Program.cs
  12. 6
      samples/ControlCatalog/ControlCatalog.csproj
  13. 1
      samples/ControlCatalog/MainView.xaml
  14. 47
      samples/ControlCatalog/Pages/CalendarPage.xaml
  15. 28
      samples/ControlCatalog/Pages/CalendarPage.xaml.cs
  16. 10
      samples/ControlCatalog/Pages/CarouselPage.xaml
  17. 5
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  18. 25
      samples/ControlCatalog/Pages/CheckBoxPage.xaml
  19. 2
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  20. 24
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  21. 14
      samples/RenderTest/Program.cs
  22. 2
      samples/RenderTest/RenderTest.csproj
  23. 13
      samples/VirtualizationTest/Program.cs
  24. 2
      samples/VirtualizationTest/VirtualizationTest.csproj
  25. 2
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  26. 1
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  27. 2
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  28. 5
      src/Avalonia.Base/Collections/AvaloniaList.cs
  29. 15
      src/Avalonia.Base/Collections/IAvaloniaList.cs
  30. 17
      src/Avalonia.Base/Data/IndexerBinding.cs
  31. 155
      src/Avalonia.Base/Data/InstancedBinding.cs
  32. 6
      src/Avalonia.Base/Threading/Dispatcher.cs
  33. 2132
      src/Avalonia.Controls/Calendar/Calendar.cs
  34. 215
      src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
  35. 193
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  36. 79
      src/Avalonia.Controls/Calendar/CalendarDateRange.cs
  37. 253
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  38. 21
      src/Avalonia.Controls/Calendar/CalendarExtensions.cs
  39. 1264
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  40. 155
      src/Avalonia.Controls/Calendar/DateTimeHelper.cs
  41. 361
      src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
  42. 2
      src/Avalonia.Controls/Classes.cs
  43. 5
      src/Avalonia.Controls/Control.cs
  44. 33
      src/Avalonia.Controls/DropDown.cs
  45. 11
      src/Avalonia.Controls/ItemsControl.cs
  46. 11
      src/Avalonia.Controls/Panel.cs
  47. 2
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  48. 36
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  49. 5
      src/Avalonia.Controls/ProgressBar.cs
  50. 9
      src/Avalonia.Controls/RadioButton.cs
  51. 46
      src/Avalonia.Controls/Shapes/Shape.cs
  52. 2
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
  53. 12
      src/Avalonia.Controls/TextBox.cs
  54. 5
      src/Avalonia.Controls/TreeView.cs
  55. 8
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  56. 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  57. 53
      src/Avalonia.Diagnostics/LogManager.cs
  58. 6
      src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
  59. 1
      src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj
  60. 51
      src/Avalonia.Logging.Serilog/SerilogExtensions.cs
  61. 1
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  62. 3
      src/Avalonia.Remote.Protocol/BsonStreamTransport.cs
  63. 60
      src/Avalonia.Styling/Styling/Setter.cs
  64. 30
      src/Avalonia.Themes.Default/Calendar.xaml
  65. 80
      src/Avalonia.Themes.Default/CalendarButton.xaml
  66. 116
      src/Avalonia.Themes.Default/CalendarDayButton.xaml
  67. 183
      src/Avalonia.Themes.Default/CalendarItem.xaml
  68. 31
      src/Avalonia.Themes.Default/CheckBox.xaml
  69. 4
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  70. 14
      src/Avalonia.Themes.Default/RadioButton.xaml
  71. 25
      src/Avalonia.Visuals/Animation/PageSlide.cs
  72. 2
      src/Avalonia.Visuals/Media/PathMarkupParser.cs
  73. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  74. 13
      src/Avalonia.Visuals/Visual.cs
  75. 3
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  76. 111
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  77. 35
      src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs
  78. 2
      src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
  79. 69
      src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs
  80. 178
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  81. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  82. 31
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleResourceExtension.cs
  83. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  84. 12
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  85. 63
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/XamlBinding.cs
  86. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  87. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  88. 91
      src/Markup/Avalonia.Markup/ControlLocator.cs
  89. 10
      src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
  90. 88
      src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
  91. 46
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  92. 32
      src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs
  93. 33
      src/OSX/Avalonia.MonoMac/RenderLoop.cs
  94. 89
      src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
  95. 1
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  96. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  97. 4
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  98. 22
      tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
  99. 4
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
  100. 1
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

3
Avalonia.sln

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.10
VisualStudioVersion = 15.0.27004.2008
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@ -155,7 +155,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\ReactiveUI.props = build\ReactiveUI.props
build\Rx.props = build\Rx.props
build\Serilog.props = build\Serilog.props
build\Serilog.Sinks.Trace.props = build\Serilog.Sinks.Trace.props
build\SharpDX.props = build\SharpDX.props
build\SkiaSharp.Desktop.props = build\SkiaSharp.Desktop.props
build\SkiaSharp.props = build\SkiaSharp.props

6
appveyor.yml

@ -1,4 +1,4 @@
os: Visual Studio 2017
os: Previous Visual Studio 2017
platform:
- Any CPU
skip_branch_with_pr: true
@ -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/Serilog.Sinks.Trace.props

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

4
build/Serilog.props

@ -1,5 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Serilog" Version="2.4.0" />
<PackageReference Include="Serilog" Version="2.5.0" />
<PackageReference Include="Serilog.Sinks.Trace" Version="2.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="1.0.0" />
</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>

10
packages.cake

@ -107,7 +107,8 @@ public class Packages
context.Information("Setting NuGet package dependencies versions:");
var SerilogVersion = packageVersions["Serilog"].FirstOrDefault().Item1;
var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
var SerilogSinksDebugVersion = packageVersions["Serilog.Sinks.Debug"].FirstOrDefault().Item1;
var SerilogSinksTraceVersion = packageVersions["Serilog.Sinks.Trace"].FirstOrDefault().Item1;
var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1;
@ -121,7 +122,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 +245,8 @@ public class Packages
Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Serilog", Version = SerilogVersion },
new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
new NuSpecDependency() { Id = "Serilog.Sinks.Debug", Version = SerilogSinksDebugVersion },
new NuSpecDependency() { Id = "Serilog.Sinks.Trace", Version = SerilogSinksTraceVersion },
new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version },
@ -253,8 +254,9 @@ 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 = "Serilog.Sinks.Debug", TargetFramework = "netcoreapp2.0", Version = SerilogSinksDebugVersion },
new NuSpecDependency() { Id = "Serilog.Sinks.Trace", TargetFramework = "netcoreapp2.0", Version = SerilogSinksTraceVersion },
new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp2.0", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp2.0", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", TargetFramework = "netcoreapp2.0", Version = parameters.Version },

13
samples/BindingTest/App.xaml.cs

@ -16,22 +16,11 @@ namespace BindingTest
private static void Main()
{
InitializeLogging();
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.LogToDebug()
.Start<MainWindow>();
}
private static void InitializeLogging()
{
#if DEBUG
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Warning()
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
#endif
}
}
}

2
samples/BindingTest/BindingTest.csproj

@ -151,8 +151,6 @@
</ItemGroup>
<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/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@ -135,5 +135,4 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\SkiaSharp.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
</Project>

15
samples/ControlCatalog.Desktop/Program.cs

@ -12,8 +12,6 @@ namespace ControlCatalog
{
static void Main(string[] args)
{
InitializeLogging();
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args
// again.
BuildAvaloniaApp().Start<MainWindow>();
@ -23,18 +21,7 @@ namespace ControlCatalog
/// This method is needed for IDE previewer infrastructure
/// </summary>
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>().UsePlatformDetect();
// This will be made into a runtime configuration extension soon!
private static void InitializeLogging()
{
#if DEBUG
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Warning()
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
#endif
}
=> AppBuilder.Configure<App>().LogToDebug().UsePlatformDetect();
private static void ConfigureAssetAssembly(AppBuilder builder)
{

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>

14
samples/RenderTest/Program.cs

@ -12,25 +12,13 @@ namespace RenderTest
{
static void Main(string[] args)
{
InitializeLogging();
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args
// again.
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.LogToDebug()
.Start<MainWindow>();
}
// This will be made into a runtime configuration extension soon!
private static void InitializeLogging()
{
#if DEBUG
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Warning()
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
#endif
}
}
}

2
samples/RenderTest/RenderTest.csproj

@ -180,8 +180,6 @@
</ItemGroup>
<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>

13
samples/VirtualizationTest/Program.cs

@ -13,22 +13,11 @@ namespace VirtualizationTest
{
static void Main(string[] args)
{
InitializeLogging();
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.LogToDebug()
.Start<MainWindow>();
}
private static void InitializeLogging()
{
#if DEBUG
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Warning()
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
#endif
}
}
}

2
samples/VirtualizationTest/VirtualizationTest.csproj

@ -147,8 +147,6 @@
</ItemGroup>
<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>

2
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -28,7 +28,5 @@
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
</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

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

5
src/Avalonia.Base/Collections/AvaloniaList.cs

@ -350,14 +350,15 @@ namespace Avalonia.Collections
public void MoveRange(int oldIndex, int count, int newIndex)
{
var items = _inner.GetRange(oldIndex, count);
var modifiedNewIndex = newIndex;
_inner.RemoveRange(oldIndex, count);
if (newIndex > oldIndex)
{
newIndex -= count;
modifiedNewIndex -= count;
}
_inner.InsertRange(newIndex, items);
_inner.InsertRange(modifiedNewIndex, items);
if (_collectionChanged != null)
{

15
src/Avalonia.Base/Collections/IAvaloniaList.cs

@ -36,6 +36,21 @@ namespace Avalonia.Collections
/// <param name="items">The items.</param>
void InsertRange(int index, IEnumerable<T> items);
/// <summary>
/// Moves an item to a new index.
/// </summary>
/// <param name="oldIndex">The index of the item to move.</param>
/// <param name="newIndex">The index to move the item to.</param>
void Move(int oldIndex, int newIndex);
/// <summary>
/// Moves multiple items to a new index.
/// </summary>
/// <param name="oldIndex">The first index of the items to move.</param>
/// <param name="count">The number of items to move.</param>
/// <param name="newIndex">The index to move the items to.</param>
void MoveRange(int oldIndex, int count, int newIndex);
/// <summary>
/// Removes multiple items from the collection.
/// </summary>

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

5
src/Avalonia.Controls/Control.cs

@ -621,7 +621,6 @@ namespace Avalonia.Controls
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(selector != null);
Contract.Requires<ArgumentNullException>(className != null);
Contract.Requires<ArgumentNullException>(property != null);
if (string.IsNullOrWhiteSpace(className))
{
@ -774,7 +773,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);
}

33
src/Avalonia.Controls/DropDown.cs

@ -96,6 +96,16 @@ namespace Avalonia.Controls
this.UpdateSelectionBoxItem(this.SelectedItem);
}
protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
if (!e.Handled && e.NavigationMethod == NavigationMethod.Directional)
{
e.Handled = UpdateSelectionFromEventSource(e.Source);
}
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
@ -104,7 +114,7 @@ namespace Avalonia.Controls
if (!e.Handled)
{
if (e.Key == Key.F4 ||
(e.Key == Key.Down && ((e.Modifiers & InputModifiers.Alt) != 0)))
((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
@ -114,6 +124,27 @@ namespace Avalonia.Controls
IsDropDownOpen = false;
e.Handled = true;
}
if (!IsDropDownOpen)
{
if (e.Key == Key.Down)
{
if (SelectedIndex == -1)
SelectedIndex = 0;
if (++SelectedIndex >= ItemCount)
SelectedIndex = 0;
e.Handled = true;
}
else if (e.Key == Key.Up)
{
if (--SelectedIndex < 0)
SelectedIndex = ItemCount - 1;
e.Handled = true;
}
}
}
}

11
src/Avalonia.Controls/ItemsControl.cs

@ -11,6 +11,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
@ -106,6 +107,12 @@ namespace Avalonia.Controls
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
public int ItemCount
{
get;
private set;
}
/// <summary>
/// Gets or sets the panel used to display the items.
/// </summary>
@ -352,6 +359,10 @@ namespace Avalonia.Controls
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
int? count = (Items as IList)?.Count;
if (count != null)
ItemCount = (int)count;
var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0);

11
src/Avalonia.Controls/Panel.cs

@ -115,6 +115,11 @@ namespace Avalonia.Controls
VisualChildren.AddRange(e.NewItems.OfType<Visual>());
break;
case NotifyCollectionChangedAction.Move:
LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
controls = e.OldItems.OfType<Control>().ToList();
LogicalChildren.RemoveAll(controls);
@ -132,11 +137,7 @@ namespace Avalonia.Controls
break;
case NotifyCollectionChangedAction.Reset:
controls = e.OldItems.OfType<Control>().ToList();
LogicalChildren.Clear();
VisualChildren.Clear();
VisualChildren.AddRange(_children);
break;
throw new NotSupportedException();
}
InvalidateMeasure();

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

@ -305,7 +305,7 @@ namespace Avalonia.Controls.Primitives
.OfType<IControl>()
.FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
return item as IControl;
return item;
}
/// <inheritdoc/>

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

5
src/Avalonia.Controls/ProgressBar.cs

@ -28,9 +28,6 @@ namespace Avalonia.Controls
{
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
HorizontalAlignmentProperty.OverrideDefaultValue<ProgressBar>(HorizontalAlignment.Left);
VerticalAlignmentProperty.OverrideDefaultValue<ProgressBar>(VerticalAlignment.Top);
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
OrientationProperty.Changed.AddClassHandler<ProgressBar>(
@ -112,8 +109,10 @@ namespace Avalonia.Controls
private void UpdateIsIndeterminate(bool isIndeterminate)
{
if (isIndeterminate)
{
if (_indeterminateAnimation == null || _indeterminateAnimation.Disposed)
_indeterminateAnimation = IndeterminateAnimation.StartAnimation(this);
}
else
_indeterminateAnimation?.Dispose();
}

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>

12
src/Avalonia.Controls/TextBox.cs

@ -97,6 +97,7 @@ namespace Avalonia.Controls
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _ignoreTextChanges;
private IEnumerable<Exception> _dataValidationErrors;
private static readonly string[] invalidCharacters = new String[1]{"\u007f"};
static TextBox()
{
@ -280,6 +281,7 @@ namespace Avalonia.Controls
{
if (!IsReadOnly)
{
input = RemoveInvalidCharacters(input);
string text = Text ?? string.Empty;
int caretIndex = CaretIndex;
if (!string.IsNullOrEmpty(input))
@ -295,6 +297,16 @@ namespace Avalonia.Controls
}
}
public string RemoveInvalidCharacters(string text)
{
for (var i = 0; i < invalidCharacters.Length; i++)
{
text = text.Replace(invalidCharacters[i], string.Empty);
}
return text;
}
private async void Copy()
{
await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)))

5
src/Avalonia.Controls/TreeView.cs

@ -176,10 +176,7 @@ namespace Avalonia.Controls
SelectedItem = item;
if (SelectedItem != null)
{
MarkContainerSelected(container, true);
}
MarkContainerSelected(container, true);
}
}

8
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -134,12 +134,14 @@ namespace Avalonia.Controls
protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
if (from == null)
return null;
var logicalScrollable = Parent as ILogicalScrollable;
var fromControl = from as IControl;
if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
if (logicalScrollable?.IsLogicalScrollEnabled == true)
{
return logicalScrollable.GetControlInDirection(direction, fromControl);
return logicalScrollable.GetControlInDirection(direction, from);
}
else
{

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

6
src/Avalonia.Diagnostics/Views/TreePage.xaml.cs

@ -27,6 +27,12 @@ namespace Avalonia.Diagnostics.Views
if (layer != null)
{
if (_adorner != null)
{
((Panel)_adorner.Parent).Children.Remove(_adorner);
_adorner = null;
}
_adorner = new Rectangle
{
Fill = new SolidColorBrush(0x80a0c5e8),

1
src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj

@ -28,6 +28,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
</ItemGroup>
<Import Project="..\..\build\Serilog.props" />
</Project>

51
src/Avalonia.Logging.Serilog/SerilogExtensions.cs

@ -0,0 +1,51 @@
using System;
using Avalonia.Controls;
using Serilog;
using SerilogLevel = Serilog.Events.LogEventLevel;
namespace Avalonia.Logging.Serilog
{
/// <summary>
/// Extension methods for Serilog logging.
/// </summary>
public static class SerilogExtensions
{
/// <summary>
/// Logs Avalonia events to the <see cref="System.Diagnostics.Debug"/> sink.
/// </summary>
/// <typeparam name="T">The application class type.</typeparam>
/// <param name="builder">The app builder instance.</param>
/// <param name="level">The minimum level to log.</param>
/// <returns>The app builder instance.</returns>
public static T LogToDebug<T>(
this T builder,
LogEventLevel level = LogEventLevel.Warning)
where T : AppBuilderBase<T>, new()
{
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Is((SerilogLevel)level)
.WriteTo.Debug(outputTemplate: "{Area}: {Message}")
.CreateLogger());
return builder;
}
/// <summary>
/// Logs Avalonia events to the <see cref="System.Diagnostics.Trace"/> sink.
/// </summary>
/// <typeparam name="T">The application class type.</typeparam>
/// <param name="builder">The app builder instance.</param>
/// <param name="level">The minimum level to log.</param>
/// <returns>The app builder instance.</returns>
public static T LogToTrace<T>(
this T builder,
LogEventLevel level = LogEventLevel.Warning)
where T : AppBuilderBase<T>, new()
{
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Is((SerilogLevel)level)
.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
.CreateLogger());
return builder;
}
}
}

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>(),

2
src/Avalonia.Visuals/Media/PathMarkupParser.cs

@ -320,7 +320,7 @@ namespace Avalonia.Media
if (c == 'E')
{
readSign = false;
readExponent = c == 'E';
readExponent = true;
}
}
else

2
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -192,7 +192,7 @@ namespace Avalonia.Rendering.SceneGraph
UpdateLayer(node, scene.Layers[node.LayerRoot]);
}
}
else if (!startLayer && node.LayerRoot == node.Visual && node.Parent != null)
else if (node.LayerRoot == node.Visual && node.Parent != null)
{
ClearLayer(scene, node);
}

13
src/Avalonia.Visuals/Visual.cs

@ -537,6 +537,19 @@ namespace Avalonia
v.SetVisualParent(null);
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (Visual v in e.OldItems)
{
v.SetVisualParent(null);
}
foreach (Visual v in e.NewItems)
{
v.SetVisualParent(this);
}
break;
}
}

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" />

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

@ -66,7 +66,7 @@ namespace Avalonia.Markup.Xaml.Data
/// <summary>
/// Gets or sets the binding path.
/// </summary>
public string Path { get; set; }
public string Path { get; set; } = "";
/// <summary>
/// Gets or sets the binding priority.
@ -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,
@ -91,51 +93,53 @@ namespace Avalonia.Markup.Xaml.Data
bool enableDataValidation = false)
{
Contract.Requires<ArgumentNullException>(target != null);
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
anchor = anchor ?? DefaultAnchor?.Target;
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
ExpressionObserver observer;
if (pathInfo.ElementName != null || ElementName != null)
if (ElementName != null)
{
observer = CreateElementObserver(
(target as IControl) ?? (anchor as IControl),
pathInfo.ElementName ?? ElementName,
pathInfo.Path);
ElementName,
Path,
enableDataValidation);
}
else if (Source != null)
{
observer = CreateSourceObserver(Source, pathInfo.Path, enableDataValidation);
observer = CreateSourceObserver(Source, Path, enableDataValidation);
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
observer = CreateDataContexObserver(
target,
pathInfo.Path,
Path,
targetProperty == Control.DataContextProperty,
anchor,
enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.Self)
{
observer = CreateSourceObserver(target, pathInfo.Path, enableDataValidation);
observer = CreateSourceObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
observer = CreateTemplatedParentObserver(target, pathInfo.Path);
observer = CreateTemplatedParentObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
{
if (RelativeSource.AncestorType == null)
if (RelativeSource.Tree == TreeType.Visual && RelativeSource.AncestorType == null)
{
throw new InvalidOperationException("AncestorType must be set for RelativeSourceModel.FindAncestor.");
throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
}
observer = CreateFindAncestorObserver(
(target as IControl) ?? (anchor as IControl),
pathInfo.Path);
RelativeSource,
Path,
enableDataValidation);
}
else
{
@ -164,53 +168,6 @@ namespace Avalonia.Markup.Xaml.Data
return new InstancedBinding(subject, Mode, Priority);
}
private static PathInfo ParsePath(string path)
{
var result = new PathInfo();
if (string.IsNullOrWhiteSpace(path) || path == ".")
{
result.Path = string.Empty;
}
else if (path.StartsWith("#"))
{
var dot = path.IndexOf('.');
if (dot != -1)
{
result.Path = path.Substring(dot + 1);
result.ElementName = path.Substring(1, dot - 1);
}
else
{
result.Path = string.Empty;
result.ElementName = path.Substring(1);
}
}
else
{
result.Path = path;
}
return result;
}
private void ValidateState(PathInfo pathInfo)
{
if (pathInfo.ElementName != null && ElementName != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set when an #elementName path is provided.");
}
if ((pathInfo.ElementName != null || ElementName != null) &&
RelativeSource != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set with a RelativeSource.");
}
}
private ExpressionObserver CreateDataContexObserver(
IAvaloniaObject target,
string path,
@ -252,7 +209,11 @@ namespace Avalonia.Markup.Xaml.Data
}
}
private ExpressionObserver CreateElementObserver(IControl target, string elementName, string path)
private ExpressionObserver CreateElementObserver(
IControl target,
string elementName,
string path,
bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
@ -260,35 +221,39 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
ControlLocator.Track(target, elementName),
path,
false,
enableDataValidation,
description);
return result;
}
private ExpressionObserver CreateFindAncestorObserver(
IControl target,
string path)
RelativeSource relativeSource,
string path,
bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
return new ExpressionObserver(
ControlLocator.Track(target, RelativeSource.AncestorType, RelativeSource.AncestorLevel -1),
path);
ControlLocator.Track(target, relativeSource.Tree, relativeSource.AncestorLevel - 1, relativeSource.AncestorType),
path,
enableDataValidation);
}
private ExpressionObserver CreateSourceObserver(
object source,
string path,
bool enabledDataValidation)
bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(source != null);
return new ExpressionObserver(source, path, enabledDataValidation);
return new ExpressionObserver(source, path, enableDataValidation);
}
private ExpressionObserver CreateTemplatedParentObserver(
IAvaloniaObject target,
string path)
string path,
bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
@ -299,7 +264,8 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty),
path,
update);
update,
enableDataValidation);
return result;
}
@ -324,6 +290,7 @@ namespace Avalonia.Markup.Xaml.Data
{
public string Path { get; set; }
public string ElementName { get; set; }
public RelativeSource RelativeSource { get; set; }
}
}
}
}

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

2
src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs

@ -87,5 +87,7 @@ namespace Avalonia.Markup.Xaml.Data
/// Gets or sets a value that describes the type of relative source lookup.
/// </summary>
public RelativeSourceMode Mode { get; set; }
public TreeType Tree { get; set; } = TreeType.Visual;
}
}

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

178
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,19 +29,182 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public override object ProvideValue(IServiceProvider serviceProvider)
{
var b = new Binding
var descriptorContext = (ITypeDescriptorContext)serviceProvider;
var pathInfo = ParsePath(Path, descriptorContext);
ValidateState(pathInfo);
return new Binding
{
Converter = Converter,
ConverterParameter = ConverterParameter,
ElementName = ElementName,
ElementName = pathInfo.ElementName ?? ElementName,
FallbackValue = FallbackValue,
Mode = Mode,
Path = Path,
Path = pathInfo.Path,
Priority = Priority,
RelativeSource = RelativeSource
RelativeSource = pathInfo.RelativeSource ?? RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
};
}
private class PathInfo
{
public string Path { get; set; }
public string ElementName { get; set; }
public RelativeSource RelativeSource { get; set; }
}
private void ValidateState(PathInfo pathInfo)
{
if (pathInfo.ElementName != null && ElementName != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set when an #elementName path is provided.");
}
if (pathInfo.RelativeSource != null && RelativeSource != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set when a $self or $parent path is provided.");
}
if ((pathInfo.ElementName != null || ElementName != null) &&
(pathInfo.RelativeSource != null || RelativeSource != null))
{
throw new InvalidOperationException(
"ElementName property cannot be set with a RelativeSource.");
}
}
private static PathInfo ParsePath(string path, ITypeDescriptorContext context)
{
var result = new PathInfo();
if (string.IsNullOrWhiteSpace(path) || path == ".")
{
result.Path = string.Empty;
}
else if (path.StartsWith("#"))
{
var dot = path.IndexOf('.');
if (dot != -1)
{
result.Path = path.Substring(dot + 1);
result.ElementName = path.Substring(1, dot - 1);
}
else
{
result.Path = string.Empty;
result.ElementName = path.Substring(1);
}
}
else if (path.StartsWith("$"))
{
var relativeSource = new RelativeSource
{
Tree = TreeType.Logical
};
result.RelativeSource = relativeSource;
var dot = path.IndexOf('.');
string relativeSourceMode;
if (dot != -1)
{
result.Path = path.Substring(dot + 1);
relativeSourceMode = path.Substring(1, dot - 1);
}
else
{
result.Path = string.Empty;
relativeSourceMode = path.Substring(1);
}
if (relativeSourceMode == "self")
{
relativeSource.Mode = RelativeSourceMode.Self;
}
else if (relativeSourceMode == "parent")
{
relativeSource.Mode = RelativeSourceMode.FindAncestor;
relativeSource.AncestorLevel = 1;
}
else if (relativeSourceMode.StartsWith("parent["))
{
relativeSource.Mode = RelativeSourceMode.FindAncestor;
var parentConfigStart = relativeSourceMode.IndexOf('[');
if (!relativeSourceMode.EndsWith("]"))
{
throw new InvalidOperationException("Invalid RelativeSource binding syntax. Expected matching ']' for '['.");
}
var parentConfigParams = relativeSourceMode.Substring(parentConfigStart + 1).TrimEnd(']').Split(';');
if (parentConfigParams.Length > 2 || parentConfigParams.Length == 0)
{
throw new InvalidOperationException("Expected either 1 or 2 parameters for RelativeSource binding syntax");
}
else if (parentConfigParams.Length == 1)
{
if (int.TryParse(parentConfigParams[0], out int level))
{
relativeSource.AncestorType = null;
relativeSource.AncestorLevel = level + 1;
}
else
{
relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
}
}
else
{
relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
relativeSource.AncestorLevel = int.Parse(parentConfigParams[1]) + 1;
}
}
else
{
throw new InvalidOperationException($"Invalid RelativeSource binding syntax: {relativeSourceMode}");
}
}
else
{
result.Path = path;
}
return result;
}
private static Type LookupAncestorType(string ancestorTypeName, ITypeDescriptorContext context)
{
var parts = ancestorTypeName.Split(':');
if (parts.Length == 0 || parts.Length > 2)
{
throw new InvalidOperationException("Invalid type name");
}
if (parts.Length == 1)
{
return context.ResolveType(string.Empty, parts[0]);
}
else
{
return context.ResolveType(parts[0], parts[1]);
}
}
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; }
}
}

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

@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
ElementName = ElementName,
Mode = Mode,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Path = Path,
Path = Path ?? string.Empty,
Priority = Priority,
};
}

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/PortableXaml/portable.xaml.github

@ -1 +1 @@
Subproject commit f226a516fe2bc191145c1fbf732f5a087f121a33
Subproject commit d50ae8335eb50d4b9606de6f5fa1cbbc78bfd72f

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;

91
src/Markup/Avalonia.Markup/ControlLocator.cs

@ -11,6 +11,21 @@ using Avalonia.VisualTree;
namespace Avalonia.Markup
{
/// <summary>
/// The type of tree via which to track a control.
/// </summary>
public enum TreeType
{
/// <summary>
/// The visual tree.
/// </summary>
Visual,
/// <summary>
/// The logical tree.
/// </summary>
Logical,
}
/// <summary>
/// Locates controls relative to other controls.
/// </summary>
@ -27,13 +42,13 @@ namespace Avalonia.Markup
{
var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.AttachedToLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree += x)
x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => ((IControl)x.Sender).FindNameScope())
.StartWith(relativeTo.FindNameScope());
var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree += x)
x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => (INameScope)null);
return attached.Merge(detached).Select(nameScope =>
@ -68,37 +83,75 @@ namespace Avalonia.Markup
/// <param name="relativeTo">
/// The control relative from which the other control should be found.
/// </param>
/// <param name="ancestorType">The type of the ancestor to find.</param>
/// <param name="tree">The tree via which to track the control.</param>
/// <param name="ancestorLevel">
/// The level of ancestor control to look for. Use 0 for the first ancestor of the
/// requested type.
/// </param>
public static IObservable<IControl> Track(IControl relativeTo, Type ancestorType, int ancestorLevel)
/// <param name="ancestorType">The type of the ancestor to find.</param>
public static IObservable<IControl> Track(IControl relativeTo, TreeType tree, int ancestorLevel, Type ancestorType = null)
{
return TrackAttachmentToTree(relativeTo, tree).Select(isAttachedToTree =>
{
if (isAttachedToTree)
{
if (tree == TreeType.Visual)
{
return relativeTo.GetVisualAncestors()
.Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(ancestorLevel) as IControl;
}
else
{
return relativeTo.GetLogicalAncestors()
.Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(ancestorLevel) as IControl;
}
}
else
{
return null;
}
});
}
private static IObservable<bool> TrackAttachmentToTree(IControl relativeTo, TreeType tree)
{
return tree == TreeType.Visual ? TrackAttachmentToVisualTree(relativeTo) : TrackAttachmentToLogicalTree(relativeTo);
}
private static IObservable<bool> TrackAttachmentToVisualTree(IControl relativeTo)
{
var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.AttachedToVisualTree += x,
x => relativeTo.DetachedFromVisualTree += x)
x => relativeTo.AttachedToVisualTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToVisualTree);
var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromVisualTree += x,
x => relativeTo.DetachedFromVisualTree += x)
x => relativeTo.DetachedFromVisualTree -= x)
.Select(x => false);
return attached.Merge(detached).Select(isAttachedToVisualTree =>
{
if (isAttachedToVisualTree)
{
return relativeTo.GetVisualAncestors()
.Where(x => ancestorType.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()))
.ElementAtOrDefault(ancestorLevel) as IControl;
}
else
{
return null;
}
});
var attachmentStatus = attached.Merge(detached);
return attachmentStatus;
}
private static IObservable<bool> TrackAttachmentToLogicalTree(IControl relativeTo)
{
var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.AttachedToLogicalTree += x,
x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToLogicalTree);
var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => false);
var attachmentStatus = attached.Merge(detached);
return attachmentStatus;
}
}
}

10
src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs

@ -51,15 +51,7 @@ namespace Avalonia.Markup.Data.Parsers
}
}
if (!r.End)
{
r.Take();
return result;
}
else
{
throw new ExpressionParseException(r.Position, "Expected ']'.");
}
throw new ExpressionParseException(r.Position, "Expected ']'.");
}
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);

1
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -54,7 +54,6 @@ namespace Avalonia.Direct2D1.Media
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
_swapChain = swapChain;
_renderTarget.BeginDraw();
}

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

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

4
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@ -113,8 +113,8 @@ namespace Avalonia.Base.UnitTests
{
var p1 = new TestProperty<string>("p1", typeof(Class1));
Assert.NotEqual(p1, null);
Assert.NotEqual(null, p1);
Assert.NotNull(p1);
Assert.NotNull(p1);
Assert.False(p1 == null);
Assert.False(null == p1);
Assert.False(p1.Equals(null));

22
tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs

@ -83,6 +83,28 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.Equal(new[] { 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 }, target);
}
[Fact]
public void MoveRange_Raises_Correct_CollectionChanged_Event()
{
var target = new AvaloniaList<int>(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
var raised = false;
target.CollectionChanged += (s, e) =>
{
Assert.Equal(NotifyCollectionChangedAction.Move, e.Action);
Assert.Equal(0, e.OldStartingIndex);
Assert.Equal(10, e.NewStartingIndex);
Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.OldItems);
Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.NewItems);
raised = true;
};
target.MoveRange(0, 9, 10);
Assert.True(raised);
Assert.Equal(new[] { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, target);
}
[Fact]
public void Adding_Item_Should_Raise_CollectionChanged()
{

4
tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

@ -206,7 +206,7 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(2, target.GetBindings().Count());
disposable.Dispose();
Assert.Equal(1, target.GetBindings().Count());
Assert.Single(target.GetBindings());
}
[Fact]
@ -248,7 +248,7 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(2, target.GetBindings().Count());
subject.OnCompleted();
Assert.Equal(1, target.GetBindings().Count());
Assert.Single(target.GetBindings());
}
[Fact]

1
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@ -49,6 +49,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Base\Properties.cs" />
<Compile Include="Layout\Measure.cs" />
<Compile Include="Styling\ApplyStyling.cs" />
<Compile Include="Program.cs" />

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

Loading…
Cancel
Save