Browse Source

Merge branch 'master' into refactor/remove_duplicate_RoundLayoutValue

pull/4136/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
0ca71a21bc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      Avalonia.sln
  2. 32
      NOTICE.md
  3. 1
      build/CoreLibraries.props
  4. 2
      build/Moq.props
  5. 7
      build/Serilog.props
  6. 4
      build/readme.md
  7. 37
      native/Avalonia.Native/src/OSX/window.mm
  8. 6
      readme.md
  9. 2
      samples/BindingDemo/App.xaml.cs
  10. 1
      samples/BindingDemo/BindingDemo.csproj
  11. 1
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  12. 2
      samples/ControlCatalog.Desktop/Program.cs
  13. 7
      samples/ControlCatalog.NetCore/Program.cs
  14. 1
      samples/ControlCatalog/ControlCatalog.csproj
  15. 1
      samples/ControlCatalog/MainView.xaml
  16. 6
      samples/ControlCatalog/Models/Person.cs
  17. 7
      samples/ControlCatalog/Pages/BorderPage.xaml
  18. 2
      samples/ControlCatalog/Pages/DialogsPage.xaml
  19. 15
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  20. 79
      samples/ControlCatalog/Pages/ToggleSwitchPage.xaml
  21. 19
      samples/ControlCatalog/Pages/ToggleSwitchPage.xaml.cs
  22. 3
      samples/ControlCatalog/SideBar.xaml
  23. 2
      samples/RenderDemo/App.xaml.cs
  24. 3
      samples/RenderDemo/MainWindow.xaml
  25. 115
      samples/RenderDemo/Pages/TransitionsPage.xaml
  26. 37
      samples/RenderDemo/Pages/TransitionsPage.xaml.cs
  27. 1
      samples/RenderDemo/RenderDemo.csproj
  28. 2
      samples/VirtualizationDemo/Program.cs
  29. 1
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  30. 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  31. 1
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  32. 36
      src/Avalonia.Animation/TransitionInstance.cs
  33. 9
      src/Avalonia.Animation/Transition`1.cs
  34. 12
      src/Avalonia.Base/AvaloniaObject.cs
  35. 3
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  36. 179
      src/Avalonia.Base/Logging/DebugLogSink.cs
  37. 5
      src/Avalonia.Base/Logging/ILogSink.cs
  38. 17
      src/Avalonia.Base/Logging/Logger.cs
  39. 32
      src/Avalonia.Base/Logging/ParametrizedLogger.cs
  40. 3
      src/Avalonia.Base/Reactive/TypedBindingAdapter.cs
  41. 30
      src/Avalonia.Base/Utilities/MathUtilities.cs
  42. 5
      src/Avalonia.Controls/Border.cs
  43. 64
      src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
  44. 4
      src/Avalonia.Controls/DropDown.cs
  45. 6
      src/Avalonia.Controls/LayoutTransformControl.cs
  46. 26
      src/Avalonia.Controls/LoggingExtensions.cs
  47. 5
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  48. 2
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  49. 15
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  50. 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  51. 100
      src/Avalonia.Controls/ScrollViewer.cs
  52. 1
      src/Avalonia.Controls/TabItem.cs
  53. 32
      src/Avalonia.Controls/TextBox.cs
  54. 33
      src/Avalonia.Controls/ToggleSwitch.cs
  55. 6
      src/Avalonia.Controls/TopLevel.cs
  56. 95
      src/Avalonia.Controls/Window.cs
  57. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  58. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  59. 8
      src/Avalonia.Layout/ILayoutManager.cs
  60. 5
      src/Avalonia.Layout/ILayoutRoot.cs
  61. 2
      src/Avalonia.Layout/ILayoutable.cs
  62. 20
      src/Avalonia.Layout/LayoutManager.cs
  63. 65
      src/Avalonia.Layout/Layoutable.cs
  64. 10
      src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj
  65. 103
      src/Avalonia.Logging.Serilog/SerilogExtensions.cs
  66. 151
      src/Avalonia.Logging.Serilog/SerilogLogger.cs
  67. 2
      src/Avalonia.Native/WindowImplBase.cs
  68. 2
      src/Avalonia.OpenGL/EglGlPlatformFeature.cs
  69. 8
      src/Avalonia.OpenGL/OpenGlControlBase.cs
  70. 3
      src/Avalonia.Styling/StyledElement.cs
  71. 2
      src/Avalonia.Themes.Default/ContextMenu.xaml
  72. 2
      src/Avalonia.Themes.Default/MenuItem.xaml
  73. 150
      src/Avalonia.Themes.Default/ScrollViewer.xaml
  74. 69
      src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
  75. 69
      src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
  76. 69
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  77. 73
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  78. 2
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  79. 117
      src/Avalonia.Themes.Fluent/TabControl.xaml
  80. 173
      src/Avalonia.Themes.Fluent/TabItem.xaml
  81. 14
      src/Avalonia.Themes.Fluent/TabStrip.xaml
  82. 110
      src/Avalonia.Themes.Fluent/TabStripItem.xaml
  83. 16
      src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
  84. 35
      src/Avalonia.Visuals/Animation/Animators/TransformOperationsAnimator.cs
  85. 28
      src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs
  86. 75
      src/Avalonia.Visuals/Matrix.cs
  87. 128
      src/Avalonia.Visuals/Media/Color.cs
  88. 6
      src/Avalonia.Visuals/Media/DrawingContext.cs
  89. 12
      src/Avalonia.Visuals/Media/IMutableTransform.cs
  90. 10
      src/Avalonia.Visuals/Media/ITransform.cs
  91. 5
      src/Avalonia.Visuals/Media/Transform.cs
  92. 23
      src/Avalonia.Visuals/Media/TransformConverter.cs
  93. 40
      src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
  94. 230
      src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs
  95. 258
      src/Avalonia.Visuals/Media/Transformation/TransformOperations.cs
  96. 444
      src/Avalonia.Visuals/Media/Transformation/TransformParser.cs
  97. 6
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  98. 1
      src/Avalonia.Visuals/Properties/AssemblyInfo.cs
  99. 2
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  100. 8
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

27
Avalonia.sln

@ -95,8 +95,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.UnitTests", "tests
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Benchmarks", "tests\Avalonia.Benchmarks\Avalonia.Benchmarks.csproj", "{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Benchmarks", "tests\Avalonia.Benchmarks\Avalonia.Benchmarks.csproj", "{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Logging.Serilog", "src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj", "{B61B66A3-B82D-4875-8001-89D3394FE0C9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport", "src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj", "{799A7BB5-3C2C-48B6-85A7-406A12C420DA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport", "src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj", "{799A7BB5-3C2C-48B6-85A7-406A12C420DA}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog", "samples\ControlCatalog\ControlCatalog.csproj", "{D0A739B9-3C68-4BA6-A328-41606954B6BD}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog", "samples\ControlCatalog\ControlCatalog.csproj", "{D0A739B9-3C68-4BA6-A328-41606954B6BD}"
@ -140,7 +138,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\ReactiveUI.props = build\ReactiveUI.props build\ReactiveUI.props = build\ReactiveUI.props
build\Rx.props = build\Rx.props build\Rx.props = build\Rx.props
build\SampleApp.props = build\SampleApp.props build\SampleApp.props = build\SampleApp.props
build\Serilog.props = build\Serilog.props
build\SharpDX.props = build\SharpDX.props build\SharpDX.props = build\SharpDX.props
build\SkiaSharp.props = build\SkiaSharp.props build\SkiaSharp.props = build\SkiaSharp.props
build\System.Memory.props = build\System.Memory.props build\System.Memory.props = build\System.Memory.props
@ -1027,30 +1024,6 @@ Global
{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhone.Build.0 = Release|Any CPU {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhone.Build.0 = Release|Any CPU
{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {410AC439-81A1-4EB5-B5E9-6A7FC6B77F4B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|Any CPU.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|iPhone.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|iPhone.Build.0 = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|Any CPU.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|iPhone.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|iPhone.Build.0 = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B61B66A3-B82D-4875-8001-89D3394FE0C9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU {799A7BB5-3C2C-48B6-85A7-406A12C420DA}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU

32
NOTICE.md

@ -271,3 +271,35 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE SOFTWARE
# Chromium
https://github.com/chromium/chromium
// Copyright 2015 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
build/CoreLibraries.props

@ -7,7 +7,6 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Input/Avalonia.Input.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Input/Avalonia.Input.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Interactivity/Avalonia.Interactivity.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Interactivity/Avalonia.Interactivity.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Layout/Avalonia.Layout.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Layout/Avalonia.Layout.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Visuals/Avalonia.Visuals.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Visuals/Avalonia.Visuals.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Styling/Avalonia.Styling.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Styling/Avalonia.Styling.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj" />

2
build/Moq.props

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

7
build/Serilog.props

@ -1,7 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<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>

4
build/readme.md

@ -9,8 +9,6 @@
<Import Project="..\..\build\Moq.props" /> <Import Project="..\..\build\Moq.props" />
<Import Project="..\..\build\NetCore.props" /> <Import Project="..\..\build\NetCore.props" />
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\SharpDX.props" /> <Import Project="..\..\build\SharpDX.props" />
<Import Project="..\..\build\SkiaSharp.Desktop.props" /> <Import Project="..\..\build\SkiaSharp.Desktop.props" />
<Import Project="..\..\build\SkiaSharp.props" /> <Import Project="..\..\build\SkiaSharp.props" />
@ -22,4 +20,4 @@
```XML ```XML
<Import Project="..\..\build\UnitTests.NetCore.targets" /> <Import Project="..\..\build\UnitTests.NetCore.targets" />
``` ```

37
native/Avalonia.Native/src/OSX/window.mm

@ -1107,7 +1107,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_lastPixelSize.Width = (int)fsize.width; _lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height; _lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget]; [self updateRenderTarget];
_parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
if(_parent != nullptr)
{
_parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
}
[super viewDidChangeBackingProperties]; [super viewDidChangeBackingProperties];
} }
@ -1166,7 +1170,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
auto modifiers = [self getModifiers:[event modifierFlags]]; auto modifiers = [self getModifiers:[event modifierFlags]];
[self becomeFirstResponder]; [self becomeFirstResponder];
_parent->BaseEvents->RawMouseEvent(type, timestamp, modifiers, point, delta);
if(_parent != nullptr)
{
_parent->BaseEvents->RawMouseEvent(type, timestamp, modifiers, point, delta);
}
[super mouseMoved:event]; [super mouseMoved:event];
} }
@ -1290,7 +1299,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
auto timestamp = [event timestamp] * 1000; auto timestamp = [event timestamp] * 1000;
auto modifiers = [self getModifiers:[event modifierFlags]]; auto modifiers = [self getModifiers:[event modifierFlags]];
_lastKeyHandled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key); if(_parent != nullptr)
{
_lastKeyHandled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
}
} }
- (BOOL)performKeyEquivalent:(NSEvent *)event - (BOOL)performKeyEquivalent:(NSEvent *)event
@ -1381,7 +1393,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{ {
if(!_lastKeyHandled) if(!_lastKeyHandled)
{ {
_lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(0, [string UTF8String]); if(_parent != nullptr)
{
_lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(0, [string UTF8String]);
}
} }
} }
@ -1677,7 +1692,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{ {
[self showWindowMenuWithAppMenu]; [self showWindowMenuWithAppMenu];
_parent->BaseEvents->Activated(); if(_parent != nullptr)
{
_parent->BaseEvents->Activated();
}
[super becomeKeyWindow]; [super becomeKeyWindow];
} }
} }
@ -1794,8 +1813,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)windowDidMove:(NSNotification *)notification - (void)windowDidMove:(NSNotification *)notification
{ {
AvnPoint position; AvnPoint position;
_parent->GetPosition(&position);
_parent->BaseEvents->PositionChanged(position); if(_parent != nullptr)
{
_parent->GetPosition(&position);
_parent->BaseEvents->PositionChanged(position);
}
} }
@end @end

6
readme.md

@ -2,7 +2,7 @@
<br /> <br />
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg)
<img src="https://user-images.githubusercontent.com/6759207/84750966-ac74ad80-afc4-11ea-97a4-067d99c15a7d.png" width="500"/> <img alt="Avalonia" src="https://user-images.githubusercontent.com/6759207/84897744-cab6d800-b0ae-11ea-8214-e5174d71f5c8.png" width="400"/>
## 📖 About AvaloniaUI ## 📖 About AvaloniaUI
@ -31,7 +31,9 @@ Install-Package Avalonia.Desktop
Examples of UIs built with Avalonia Examples of UIs built with Avalonia
![image](https://user-images.githubusercontent.com/4672627/84707589-5b69a880-af35-11ea-87a6-7ad57a31d314.png) ![image](https://user-images.githubusercontent.com/4672627/84707589-5b69a880-af35-11ea-87a6-7ad57a31d314.png)
![image](https://user-images.githubusercontent.com/4672627/84708576-28281900-af37-11ea-8c88-e29dfcfa0558.png) ![image](https://user-images.githubusercontent.com/4672627/85069644-d8419000-b18a-11ea-8732-be9055bb61fd.PNG)
![image](https://user-images.githubusercontent.com/4672627/85069659-dc6dad80-b18a-11ea-8375-39ef95315b5c.PNG)
![image](https://user-images.githubusercontent.com/4672627/84708947-c3b98980-af37-11ea-8c9d-503334615bbf.png) ![image](https://user-images.githubusercontent.com/4672627/84708947-c3b98980-af37-11ea-8c9d-503334615bbf.png)

2
samples/BindingDemo/App.xaml.cs

@ -1,10 +1,8 @@
using System; using System;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Serilog;
namespace BindingDemo namespace BindingDemo
{ {

1
samples/BindingDemo/BindingDemo.csproj

@ -10,7 +10,6 @@
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\EmbedXaml.props" /> <Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" /> <Import Project="..\..\build\ReactiveUI.props" />
<Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\build\NetFX.props" /> <Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\build\NetFX.props" />

1
samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@ -10,6 +10,5 @@
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\NetFX.props" /> <Import Project="..\..\build\NetFX.props" />
</Project> </Project>

2
samples/ControlCatalog.Desktop/Program.cs

@ -2,10 +2,8 @@ using System;
using System.Linq; using System.Linq;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Serilog;
namespace ControlCatalog namespace ControlCatalog
{ {

7
samples/ControlCatalog.NetCore/Program.cs

@ -1,13 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Avalonia; using Avalonia;
using Avalonia.ReactiveUI;
using Avalonia.Dialogs; using Avalonia.Dialogs;
using Avalonia.OpenGL; using Avalonia.ReactiveUI;
namespace ControlCatalog.NetCore namespace ControlCatalog.NetCore
{ {
@ -69,7 +67,8 @@ namespace ControlCatalog.NetCore
}) })
.UseSkia() .UseSkia()
.UseReactiveUI() .UseReactiveUI()
.UseManagedSystemDialogs(); .UseManagedSystemDialogs()
.LogToDebug();
static void SilenceConsole() static void SilenceConsole()
{ {

1
samples/ControlCatalog/ControlCatalog.csproj

@ -28,6 +28,5 @@
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />
</Project> </Project>

1
samples/ControlCatalog/MainView.xaml

@ -57,6 +57,7 @@
<TabItem Header="TabStrip"><pages:TabStripPage/></TabItem> <TabItem Header="TabStrip"><pages:TabStripPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem> <TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>
<TabItem Header="TextBlock"><pages:TextBlockPage/></TabItem> <TabItem Header="TextBlock"><pages:TextBlockPage/></TabItem>
<TabItem Header="ToggleSwitch"><pages:ToggleSwitchPage/></TabItem>
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem> <TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem> <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem> <TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>

6
samples/ControlCatalog/Models/Person.cs

@ -21,12 +21,12 @@ namespace ControlCatalog.Models
get => _firstName; get => _firstName;
set set
{ {
_firstName = value;
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
SetError(nameof(FirstName), "First Name Required"); SetError(nameof(FirstName), "First Name Required");
else else
SetError(nameof(FirstName), null); SetError(nameof(FirstName), null);
_firstName = value;
OnPropertyChanged(nameof(FirstName)); OnPropertyChanged(nameof(FirstName));
} }
@ -37,12 +37,12 @@ namespace ControlCatalog.Models
get => _lastName; get => _lastName;
set set
{ {
_lastName = value;
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
SetError(nameof(LastName), "Last Name Required"); SetError(nameof(LastName), "Last Name Required");
else else
SetError(nameof(LastName), null); SetError(nameof(LastName), null);
_lastName = value;
OnPropertyChanged(nameof(LastName)); OnPropertyChanged(nameof(LastName));
} }
} }
@ -95,4 +95,4 @@ namespace ControlCatalog.Models
return null; return null;
} }
} }
} }

7
samples/ControlCatalog/Pages/BorderPage.xaml

@ -29,6 +29,13 @@
Padding="16"> Padding="16">
<TextBlock>Rounded Corners</TextBlock> <TextBlock>Rounded Corners</TextBlock>
</Border> </Border>
<Border BorderBrush="{DynamicResource ThemeAccentBrush2}" Width="100" Height="100"
BorderThickness="0"
Background="White"
CornerRadius="100" ClipToBounds="True">
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" />
</Border>
<TextBlock Text="Border with Clipping" HorizontalAlignment="Center" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

2
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -11,5 +11,7 @@
<Button Name="DecoratedWindowDialog">Decorated window (dialog)</Button> <Button Name="DecoratedWindowDialog">Decorated window (dialog)</Button>
<Button Name="Dialog">Dialog</Button> <Button Name="Dialog">Dialog</Button>
<Button Name="DialogNoTaskbar">Dialog (No taskbar icon)</Button> <Button Name="DialogNoTaskbar">Dialog (No taskbar icon)</Button>
<Button Name="OwnedWindow">Owned window</Button>
<Button Name="OwnedWindowNoTaskbar">Owned window (No taskbar icon)</Button>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

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

@ -93,6 +93,21 @@ namespace ControlCatalog.Pages
window.ShowInTaskbar = false; window.ShowInTaskbar = false;
window.ShowDialog(GetWindow()); window.ShowDialog(GetWindow());
}; };
this.FindControl<Button>("OwnedWindow").Click += delegate
{
var window = CreateSampleWindow();
window.Show(GetWindow());
};
this.FindControl<Button>("OwnedWindowNoTaskbar").Click += delegate
{
var window = CreateSampleWindow();
window.ShowInTaskbar = false;
window.Show(GetWindow());
};
} }
private Window CreateSampleWindow() private Window CreateSampleWindow()

79
samples/ControlCatalog/Pages/ToggleSwitchPage.xaml

@ -0,0 +1,79 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ToggleSwitchPage" Margin="5">
<StackPanel Width="500" HorizontalAlignment="Center">
<TextBlock Text="Simple ToggleSwitch" Classes="header"/>
<Border Classes="Thin">
<StackPanel>
<ToggleSwitch Margin="10"/>
<TextBox Text="&lt;ToggleSwitch/&gt;" Classes="CodeBox"/>
</StackPanel>
</Border>
<TextBlock Text="headered ToggleSwitch" Classes="header"/>
<Border Classes="Thin">
<StackPanel>
<ToggleSwitch Content="headered" IsChecked="true" Margin="10"/>
<TextBox Classes="CodeBox"
Text="&lt;ToggleSwitch&gt;headered&lt;/ToggleSwitch&gt;"/>
</StackPanel>
</Border>
<TextBlock Text="Custom content ToggleSwitch" Classes="header"/>
<Border Classes="Thin">
<StackPanel>
<ToggleSwitch Content="Custom"
OnContent="On"
OffContent="Off"
Margin="10"/>
<TextBox Text="&lt;ToggleSwitch Content=&quot;Custom&quot;
ContentOn=&quot;On&quot;
ContentOff=&quot;Off&quot; /&gt;"
Classes="CodeBox"/>
</StackPanel>
</Border>
<TextBlock Text="Image content ToggleSwitch" Classes="header"/>
<Border Classes="Thin">
<StackPanel>
<ToggleSwitch Content="Just Click!" Margin="10">
<ToggleSwitch.OnContent>
<Image Source="/Assets/hirsch-899118_640.jpg" Height="32"/>
</ToggleSwitch.OnContent>
<ToggleSwitch.OffContent>
<Image Source="/Assets/delicate-arch-896885_640.jpg" Height="32"/>
</ToggleSwitch.OffContent>
</ToggleSwitch>
</StackPanel>
</Border>
</StackPanel>
<UserControl.Styles >
<Style Selector="TextBox.CodeBox" >
<Setter Property="Padding" Value="10"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="IsEnabled" Value="true"/>
</Style>
<Style Selector="TextBlock.header">
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="0 20 0 20"/>
</Style>
<Style Selector="Border.Thin">
<Setter Property="BorderBrush" Value="Gray"/>
<Setter Property="BorderThickness" Value="0.5"/>
<Setter Property="CornerRadius" Value="2"/>
</Style>
</UserControl.Styles>
</UserControl>

19
samples/ControlCatalog/Pages/ToggleSwitchPage.xaml.cs

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class ToggleSwitchPage : UserControl
{
public ToggleSwitchPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

3
samples/ControlCatalog/SideBar.xaml

@ -67,6 +67,9 @@
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Auto"/> <Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Auto"/>
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Auto"/> <Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Auto"/>
</Style> </Style>
<Style Selector="TabControl.sidebar > TabItem:selected /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover"> <Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Opacity" Value="1"/> <Setter Property="Opacity" Value="1"/>
</Style> </Style>

2
samples/RenderDemo/App.xaml.cs

@ -1,5 +1,4 @@
using Avalonia; using Avalonia;
using Avalonia.Logging.Serilog;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
@ -22,6 +21,5 @@ namespace RenderDemo
.UsePlatformDetect() .UsePlatformDetect()
.UseReactiveUI() .UseReactiveUI()
.LogToDebug(); .LogToDebug();
} }
} }

3
samples/RenderDemo/MainWindow.xaml

@ -29,6 +29,9 @@
<TabItem Header="Animations"> <TabItem Header="Animations">
<pages:AnimationsPage/> <pages:AnimationsPage/>
</TabItem> </TabItem>
<TabItem Header="Transitions">
<pages:TransitionsPage/>
</TabItem>
<TabItem Header="Clipping"> <TabItem Header="Clipping">
<pages:ClippingPage/> <pages:ClippingPage/>
</TabItem> </TabItem>

115
samples/RenderDemo/Pages/TransitionsPage.xaml

@ -0,0 +1,115 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.TransitionsPage">
<UserControl.Styles>
<Styles>
<Styles.Resources>
<Template x:Key="Acorn">
<Path Fill="White" Stretch="Uniform"
Data="F1 M 16.6309,18.6563C 17.1309,
8.15625 29.8809,14.1563 29.8809,
14.1563C 30.8809,11.1563 34.1308,
11.4063 34.1308,11.4063C 33.5,12
34.6309,13.1563 34.6309,13.1563C
32.1309,13.1562 31.1309,14.9062
31.1309,14.9062C 41.1309,23.9062
32.6309,27.9063 32.6309,27.9062C
24.6309,24.9063 21.1309,22.1562
16.6309,18.6563 Z M 16.6309,19.9063C
21.6309,24.1563 25.1309,26.1562
31.6309,28.6562C 31.6309,28.6562
26.3809,39.1562 18.3809,36.1563C
18.3809,36.1563 18,38 16.3809,36.9063C
15,36 16.3809,34.9063 16.3809,34.9063C
16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z"/>
</Template>
<Template x:Key="Heart">
<Path Fill="Red" Stretch="Uniform" Data="
M 272.70141,238.71731
C 206.46141,238.71731 152.70146,292.4773 152.70146,358.71731
C 152.70146,493.47282 288.63461,528.80461 381.26391,662.02535
C 468.83815,529.62199 609.82641,489.17075 609.82641,358.71731
C 609.82641,292.47731 556.06651,238.7173 489.82641,238.71731
C 441.77851,238.71731 400.42481,267.08774 381.26391,307.90481
C 362.10311,267.08773 320.74941,238.7173 272.70141,238.71731 z "/>
</Template>
</Styles.Resources>
<Style Selector="Border.Test">
<Setter Property="Margin" Value="15"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Child" Value="{StaticResource Acorn}"/>
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:1" />
</Transitions>
</Setter>
<Setter Property="RenderTransform" Value="none" />
</Style>
<Style Selector="Border.Rect1:pointerover">
<Setter Property="RenderTransform" Value="rotate(120deg) scale(1.5)" />
</Style>
<Style Selector="Border.Rect2:pointerover">
<Setter Property="RenderTransform" Value="scale(0.8)" />
</Style>
<Style Selector="Border.Rect3">
<Setter Property="Child" Value="{StaticResource Heart}"/>
</Style>
<Style Selector="Border.Rect3:pointerover">
<Setter Property="RenderTransform" Value="rotate(1turn)" />
</Style>
<Style Selector="Border.Rect4:pointerover">
<Setter Property="RenderTransform" Value="translateY(-100px)" />
</Style>
<Style Selector="Border.Rect5:pointerover">
<Setter Property="RenderTransform" Value="skewX(-20deg)" />
</Style>
<Style Selector="Border.Rect5:pointerover">
<Setter Property="RenderTransform" Value="skewX(-20deg)" />
</Style>
<Style Selector="Border.Rect6">
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.5" Delay="0:0:1"/>
</Transitions>
</Setter>
<Setter Property="RenderTransform" Value="scale(0.8)" />
</Style>
<Style Selector="Border.Rect6:pointerover">
<Setter Property="RenderTransform" Value="none" />
</Style>
</Styles>
</UserControl.Styles>
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" ClipToBounds="False">
<StackPanel.Clock>
<Clock />
</StackPanel.Clock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock>
<Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
</StackPanel>
<WrapPanel ClipToBounds="False">
<Border Classes="Test Rect1" Background="DarkRed"/>
<Border Classes="Test Rect2" Background="Magenta"/>
<Border Classes="Test Rect3"/>
<Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Orange"/>
</WrapPanel>
</StackPanel>
</Grid>
</UserControl>

37
samples/RenderDemo/Pages/TransitionsPage.xaml.cs

@ -0,0 +1,37 @@
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using RenderDemo.ViewModels;
namespace RenderDemo.Pages
{
public class TransitionsPage : UserControl
{
public TransitionsPage()
{
InitializeComponent();
this.DataContext = new AnimationsPageViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void ToggleClock(object sender, RoutedEventArgs args)
{
var button = sender as Button;
var clock = button.Clock;
if (clock.PlayState == PlayState.Run)
{
clock.PlayState = PlayState.Pause;
}
else if (clock.PlayState == PlayState.Pause)
{
clock.PlayState = PlayState.Run;
}
}
}
}

1
samples/RenderDemo/RenderDemo.csproj

@ -13,7 +13,6 @@
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\EmbedXaml.props" /> <Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" /> <Import Project="..\..\build\ReactiveUI.props" />
<Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\build\NetFX.props" /> <Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\build\NetFX.props" />

2
samples/VirtualizationDemo/Program.cs

@ -1,9 +1,7 @@
using System; using System;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Serilog;
namespace VirtualizationDemo namespace VirtualizationDemo
{ {

1
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -10,7 +10,6 @@
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\EmbedXaml.props" /> <Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" /> <Import Project="..\..\build\ReactiveUI.props" />
<Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\build\NetFX.props" /> <Import Condition="'$(TargetFramework)'=='net461'" Project="..\..\build\NetFX.props" />

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -26,7 +26,6 @@
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Rx.props" /> <Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\ReferenceCoreLibraries.props" /> <Import Project="..\..\..\build\ReferenceCoreLibraries.props" />
</Project> </Project>

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

@ -149,7 +149,6 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Base.props" /> <Import Project="..\..\..\build\Base.props" />
<Import Project="..\..\..\build\Rx.props" /> <Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\System.Memory.props" /> <Import Project="..\..\..\build\System.Memory.props" />

36
src/Avalonia.Animation/TransitionInstance.cs

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

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

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

12
src/Avalonia.Base/AvaloniaObject.cs

@ -421,8 +421,7 @@ namespace Avalonia
throw new ArgumentException($"The property {property.Name} is readonly."); throw new ArgumentException($"The property {property.Name} is readonly.");
} }
Logger.TryGet(LogEventLevel.Verbose)?.Log( Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log(
LogArea.Property,
this, this,
"Bound {Property} to {Binding} with priority LocalValue", "Bound {Property} to {Binding} with priority LocalValue",
property, property,
@ -501,8 +500,7 @@ namespace Avalonia
if (change.IsEffectiveValueChange) if (change.IsEffectiveValueChange)
{ {
Logger.TryGet(LogEventLevel.Verbose)?.Log( Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log(
LogArea.Property,
this, this,
"{Property} changed from {$Old} to {$Value} with priority {Priority}", "{Property} changed from {$Old} to {$Value} with priority {Priority}",
property, property,
@ -586,8 +584,7 @@ namespace Avalonia
/// <param name="e">The binding error.</param> /// <param name="e">The binding error.</param>
protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e) protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e)
{ {
Logger.TryGet(LogEventLevel.Warning)?.Log( Logger.TryGet(LogEventLevel.Warning, LogArea.Binding)?.Log(
LogArea.Binding,
this, this,
"Error in binding to {Target}.{Property}: {Message}", "Error in binding to {Target}.{Property}: {Message}",
this, this,
@ -857,8 +854,7 @@ namespace Avalonia
/// <param name="priority">The priority.</param> /// <param name="priority">The priority.</param>
private void LogPropertySet<T>(AvaloniaProperty<T> property, T value, BindingPriority priority) private void LogPropertySet<T>(AvaloniaProperty<T> property, T value, BindingPriority priority)
{ {
Logger.TryGet(LogEventLevel.Verbose)?.Log( Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log(
LogArea.Property,
this, this,
"Set {Property} to {$Value} with priority {Priority}", "Set {Property} to {$Value} with priority {Priority}",
property, property,

3
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -168,8 +168,7 @@ namespace Avalonia.Data.Core
} }
else else
{ {
Logger.TryGet(LogEventLevel.Error)?.Log( Logger.TryGet(LogEventLevel.Error, LogArea.Binding)?.Log(
LogArea.Binding,
this, this,
"Could not convert FallbackValue {FallbackValue} to {Type}", "Could not convert FallbackValue {FallbackValue} to {Type}",
_fallbackValue, _fallbackValue,

179
src/Avalonia.Base/Logging/DebugLogSink.cs

@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Avalonia.Utilities;
namespace Avalonia.Logging
{
public class DebugLogSink : ILogSink
{
private readonly LogEventLevel _level;
private readonly IList<string> _areas;
public DebugLogSink(
LogEventLevel minimumLevel,
IList<string> areas = null)
{
_level = minimumLevel;
_areas = areas?.Count > 0 ? areas : null;
}
public bool IsEnabled(LogEventLevel level, string area)
{
return level >= _level && (_areas?.Contains(area) ?? true);
}
public void Log(LogEventLevel level, string area, object source, string messageTemplate)
{
if (IsEnabled(level, area))
{
Debug.WriteLine(Format<object, object, object>(area, messageTemplate, source));
}
}
public void Log<T0>(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0)
{
if (IsEnabled(level, area))
{
Debug.WriteLine(Format<T0, object, object>(area, messageTemplate, source, propertyValue0));
}
}
public void Log<T0, T1>(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1)
{
if (IsEnabled(level, area))
{
Debug.WriteLine(Format<T0, T1, object>(area, messageTemplate, source, propertyValue0, propertyValue1));
}
}
public void Log<T0, T1, T2>(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2)
{
if (IsEnabled(level, area))
{
Debug.WriteLine(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2));
}
}
public void Log(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
{
if (IsEnabled(level, area))
{
Debug.WriteLine(Format(area, messageTemplate, source, propertyValues));
}
}
private static string Format<T0, T1, T2>(
string area,
string template,
object source,
T0 v0 = default,
T1 v1 = default,
T2 v2 = default)
{
var result = new StringBuilder(template.Length);
var r = new CharacterReader(template.AsSpan());
var i = 0;
result.Append('[');
result.Append(area);
result.Append("] ");
while (!r.End)
{
var c = r.Take();
if (c != '{')
{
result.Append(c);
}
else
{
if (r.Peek != '{')
{
result.Append('\'');
result.Append(i++ switch
{
0 => v0,
1 => v1,
2 => v2,
_ => null
});
result.Append('\'');
r.TakeUntil('}');
r.Take();
}
else
{
result.Append('{');
r.Take();
}
}
}
if (source is object)
{
result.Append(" (");
result.Append(source.GetType().Name);
result.Append(" #");
result.Append(source.GetHashCode());
result.Append(')');
}
return result.ToString();
}
private static string Format(
string area,
string template,
object source,
object[] v)
{
var result = new StringBuilder(template.Length);
var r = new CharacterReader(template.AsSpan());
var i = 0;
result.Append('[');
result.Append(area);
result.Append(']');
while (!r.End)
{
var c = r.Take();
if (c != '{')
{
result.Append(c);
}
else
{
if (r.Peek != '{')
{
result.Append('\'');
result.Append(i < v.Length ? v[i++] : null);
result.Append('\'');
r.TakeUntil('}');
r.Take();
}
else
{
result.Append('{');
r.Take();
}
}
}
if (source is object)
{
result.Append('(');
result.Append(source.GetType().Name);
result.Append(" #");
result.Append(source.GetHashCode());
result.Append(')');
}
return result.ToString();
}
}
}

5
src/Avalonia.Base/Logging/ILogSink.cs

@ -6,11 +6,12 @@ namespace Avalonia.Logging
public interface ILogSink public interface ILogSink
{ {
/// <summary> /// <summary>
/// Checks if given log level is enabled. /// Checks if given log level and area is enabled.
/// </summary> /// </summary>
/// <param name="level">The log event level.</param> /// <param name="level">The log event level.</param>
/// <param name="area">The log area.</param>
/// <returns><see langword="true"/> if given log level is enabled.</returns> /// <returns><see langword="true"/> if given log level is enabled.</returns>
bool IsEnabled(LogEventLevel level); bool IsEnabled(LogEventLevel level, string area);
/// <summary> /// <summary>
/// Logs an event. /// Logs an event.

17
src/Avalonia.Base/Logging/Logger.cs

@ -14,36 +14,39 @@ namespace Avalonia.Logging
/// Checks if given log level is enabled. /// Checks if given log level is enabled.
/// </summary> /// </summary>
/// <param name="level">The log event level.</param> /// <param name="level">The log event level.</param>
/// <param name="area">The log area.</param>
/// <returns><see langword="true"/> if given log level is enabled.</returns> /// <returns><see langword="true"/> if given log level is enabled.</returns>
public static bool IsEnabled(LogEventLevel level) public static bool IsEnabled(LogEventLevel level, string area)
{ {
return Sink?.IsEnabled(level) == true; return Sink?.IsEnabled(level, area) == true;
} }
/// <summary> /// <summary>
/// Returns parametrized logging sink if given log level is enabled. /// Returns parametrized logging sink if given log level is enabled.
/// </summary> /// </summary>
/// <param name="level">The log event level.</param> /// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates from.</param>
/// <returns>Log sink or <see langword="null"/> if log level is not enabled.</returns> /// <returns>Log sink or <see langword="null"/> if log level is not enabled.</returns>
public static ParametrizedLogger? TryGet(LogEventLevel level) public static ParametrizedLogger? TryGet(LogEventLevel level, string area)
{ {
if (!IsEnabled(level)) if (!IsEnabled(level, area))
{ {
return null; return null;
} }
return new ParametrizedLogger(Sink, level); return new ParametrizedLogger(Sink, level, area);
} }
/// <summary> /// <summary>
/// Returns parametrized logging sink if given log level is enabled. /// Returns parametrized logging sink if given log level is enabled.
/// </summary> /// </summary>
/// <param name="level">The log event level.</param> /// <param name="level">The log event level.</param>
/// <param name="area">The area that the event originates from.</param>
/// <param name="outLogger">Log sink that is valid only if method returns <see langword="true"/>.</param> /// <param name="outLogger">Log sink that is valid only if method returns <see langword="true"/>.</param>
/// <returns><see langword="true"/> if logger was obtained successfully.</returns> /// <returns><see langword="true"/> if logger was obtained successfully.</returns>
public static bool TryGet(LogEventLevel level, out ParametrizedLogger outLogger) public static bool TryGet(LogEventLevel level, string area, out ParametrizedLogger outLogger)
{ {
ParametrizedLogger? logger = TryGet(level); ParametrizedLogger? logger = TryGet(level, area);
outLogger = logger.GetValueOrDefault(); outLogger = logger.GetValueOrDefault();

32
src/Avalonia.Base/Logging/ParametrizedLogger.cs

@ -9,11 +9,13 @@ namespace Avalonia.Logging
{ {
private readonly ILogSink _sink; private readonly ILogSink _sink;
private readonly LogEventLevel _level; private readonly LogEventLevel _level;
private readonly string _area;
public ParametrizedLogger(ILogSink sink, LogEventLevel level) public ParametrizedLogger(ILogSink sink, LogEventLevel level, string area)
{ {
_sink = sink; _sink = sink;
_level = level; _level = level;
_area = area;
} }
/// <summary> /// <summary>
@ -24,58 +26,51 @@ namespace Avalonia.Logging
/// <summary> /// <summary>
/// Logs an event. /// Logs an event.
/// </summary> /// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param> /// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param> /// <param name="messageTemplate">The message template.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log( public void Log(
string area,
object source, object source,
string messageTemplate) string messageTemplate)
{ {
_sink.Log(_level, area, source, messageTemplate); _sink.Log(_level, _area, source, messageTemplate);
} }
/// <summary> /// <summary>
/// Logs an event. /// Logs an event.
/// </summary> /// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param> /// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param> /// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param> /// <param name="propertyValue0">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0>( public void Log<T0>(
string area,
object source, object source,
string messageTemplate, string messageTemplate,
T0 propertyValue0) T0 propertyValue0)
{ {
_sink.Log(_level, area, source, messageTemplate, propertyValue0); _sink.Log(_level, _area, source, messageTemplate, propertyValue0);
} }
/// <summary> /// <summary>
/// Logs an event. /// Logs an event.
/// </summary> /// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param> /// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param> /// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param> /// <param name="propertyValue0">Message property value.</param>
/// <param name="propertyValue1">Message property value.</param> /// <param name="propertyValue1">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1>( public void Log<T0, T1>(
string area,
object source, object source,
string messageTemplate, string messageTemplate,
T0 propertyValue0, T0 propertyValue0,
T1 propertyValue1) T1 propertyValue1)
{ {
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1); _sink.Log(_level, _area, source, messageTemplate, propertyValue0, propertyValue1);
} }
/// <summary> /// <summary>
/// Logs an event. /// Logs an event.
/// </summary> /// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param> /// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param> /// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param> /// <param name="propertyValue0">Message property value.</param>
@ -83,20 +78,18 @@ namespace Avalonia.Logging
/// <param name="propertyValue2">Message property value.</param> /// <param name="propertyValue2">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1, T2>( public void Log<T0, T1, T2>(
string area,
object source, object source,
string messageTemplate, string messageTemplate,
T0 propertyValue0, T0 propertyValue0,
T1 propertyValue1, T1 propertyValue1,
T2 propertyValue2) T2 propertyValue2)
{ {
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2); _sink.Log(_level, _area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2);
} }
/// <summary> /// <summary>
/// Logs an event. /// Logs an event.
/// </summary> /// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param> /// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param> /// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param> /// <param name="propertyValue0">Message property value.</param>
@ -105,7 +98,6 @@ namespace Avalonia.Logging
/// <param name="propertyValue3">Message property value.</param> /// <param name="propertyValue3">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1, T2, T3>( public void Log<T0, T1, T2, T3>(
string area,
object source, object source,
string messageTemplate, string messageTemplate,
T0 propertyValue0, T0 propertyValue0,
@ -113,13 +105,12 @@ namespace Avalonia.Logging
T2 propertyValue2, T2 propertyValue2,
T3 propertyValue3) T3 propertyValue3)
{ {
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3); _sink.Log(_level, _area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3);
} }
/// <summary> /// <summary>
/// Logs an event. /// Logs an event.
/// </summary> /// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param> /// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param> /// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param> /// <param name="propertyValue0">Message property value.</param>
@ -129,7 +120,6 @@ namespace Avalonia.Logging
/// <param name="propertyValue4">Message property value.</param> /// <param name="propertyValue4">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1, T2, T3, T4>( public void Log<T0, T1, T2, T3, T4>(
string area,
object source, object source,
string messageTemplate, string messageTemplate,
T0 propertyValue0, T0 propertyValue0,
@ -138,13 +128,12 @@ namespace Avalonia.Logging
T3 propertyValue3, T3 propertyValue3,
T4 propertyValue4) T4 propertyValue4)
{ {
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4); _sink.Log(_level, _area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4);
} }
/// <summary> /// <summary>
/// Logs an event. /// Logs an event.
/// </summary> /// </summary>
/// <param name="area">The area that the event originates.</param>
/// <param name="source">The object from which the event originates.</param> /// <param name="source">The object from which the event originates.</param>
/// <param name="messageTemplate">The message template.</param> /// <param name="messageTemplate">The message template.</param>
/// <param name="propertyValue0">Message property value.</param> /// <param name="propertyValue0">Message property value.</param>
@ -155,7 +144,6 @@ namespace Avalonia.Logging
/// <param name="propertyValue5">Message property value.</param> /// <param name="propertyValue5">Message property value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log<T0, T1, T2, T3, T4, T5>( public void Log<T0, T1, T2, T3, T4, T5>(
string area,
object source, object source,
string messageTemplate, string messageTemplate,
T0 propertyValue0, T0 propertyValue0,
@ -165,7 +153,7 @@ namespace Avalonia.Logging
T4 propertyValue4, T4 propertyValue4,
T5 propertyValue5) T5 propertyValue5)
{ {
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4, propertyValue5); _sink.Log(_level, _area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4, propertyValue5);
} }
} }
} }

3
src/Avalonia.Base/Reactive/TypedBindingAdapter.cs

@ -32,8 +32,7 @@ namespace Avalonia.Reactive
} }
catch (InvalidCastException e) catch (InvalidCastException e)
{ {
Logger.TryGet(LogEventLevel.Error)?.Log( Logger.TryGet(LogEventLevel.Error, LogArea.Binding)?.Log(
LogArea.Binding,
_target, _target,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
_property.Name, _property.Name,

30
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -267,6 +267,36 @@ namespace Avalonia.Utilities
} }
} }
/// <summary>
/// Converts an angle in degrees to radians.
/// </summary>
/// <param name="angle">The angle in degrees.</param>
/// <returns>The angle in radians.</returns>
public static double Deg2Rad(double angle)
{
return angle * (Math.PI / 180d);
}
/// <summary>
/// Converts an angle in gradians to radians.
/// </summary>
/// <param name="angle">The angle in gradians.</param>
/// <returns>The angle in radians.</returns>
public static double Grad2Rad(double angle)
{
return angle * (Math.PI / 200d);
}
/// <summary>
/// Converts an angle in turns to radians.
/// </summary>
/// <param name="angle">The angle in turns.</param>
/// <returns>The angle in radians.</returns>
public static double Turn2Rad(double angle)
{
return angle * 2 * Math.PI;
}
private static void ThrowCannotBeGreaterThanException(double min, double max) private static void ThrowCannotBeGreaterThanException(double min, double max)
{ {
throw new ArgumentException($"{min} cannot be greater than {max}."); throw new ArgumentException($"{min} cannot be greater than {max}.");

5
src/Avalonia.Controls/Border.cs

@ -1,13 +1,14 @@
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// A control which decorates a child with a border and background. /// A control which decorates a child with a border and background.
/// </summary> /// </summary>
public partial class Border : Decorator public partial class Border : Decorator, IVisualWithRoundRectClip
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Background"/> property. /// Defines the <see cref="Background"/> property.
@ -129,5 +130,7 @@ namespace Avalonia.Controls
{ {
return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness); return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness);
} }
public CornerRadius ClipToBoundsRadius => CornerRadius;
} }
} }

64
src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Controls.Primitives;
using Avalonia.Data.Converters;
using Avalonia.Utilities;
namespace Avalonia.Controls.Converters
{
public class MenuScrollingVisibilityConverter : IMultiValueConverter
{
public static readonly MenuScrollingVisibilityConverter Instance = new MenuScrollingVisibilityConverter();
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null ||
values == null ||
values.Count != 4 ||
!(values[0] is ScrollBarVisibility visiblity) ||
!(values[1] is double offset) ||
!(values[2] is double extent) ||
!(values[3] is double viewport))
{
return AvaloniaProperty.UnsetValue;
}
if (visiblity == ScrollBarVisibility.Auto)
{
if (extent == viewport)
{
return false;
}
double target;
if (parameter is double d)
{
target = d;
}
else if (parameter is string s)
{
target = double.Parse(s, NumberFormatInfo.InvariantInfo);
}
else
{
return AvaloniaProperty.UnsetValue;
}
// Calculate the percent so that we can see if we are near the edge of the range
double percent = MathUtilities.Clamp(offset * 100.0 / (extent - viewport), 0, 100);
if (MathUtilities.AreClose(percent, target))
{
// We are at the end of the range, so no need for this button to be shown
return false;
}
return true;
}
return false;
}
}
}

4
src/Avalonia.Controls/DropDown.cs

@ -9,7 +9,7 @@ namespace Avalonia.Controls
{ {
public DropDown() public DropDown()
{ {
Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDown is deprecated: Use ComboBox"); Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(this, "DropDown is deprecated: Use ComboBox");
} }
Type IStyleable.StyleKey => typeof(ComboBox); Type IStyleable.StyleKey => typeof(ComboBox);
@ -20,7 +20,7 @@ namespace Avalonia.Controls
{ {
public DropDownItem() public DropDownItem()
{ {
Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem"); Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(this, "DropDownItem is deprecated: Use ComboBoxItem");
} }
Type IStyleable.StyleKey => typeof(ComboBoxItem); Type IStyleable.StyleKey => typeof(ComboBoxItem);

6
src/Avalonia.Controls/LayoutTransformControl.cs

@ -14,8 +14,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class LayoutTransformControl : Decorator public class LayoutTransformControl : Decorator
{ {
public static readonly StyledProperty<Transform> LayoutTransformProperty = public static readonly StyledProperty<ITransform> LayoutTransformProperty =
AvaloniaProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform)); AvaloniaProperty.Register<LayoutTransformControl, ITransform>(nameof(LayoutTransform));
public static readonly StyledProperty<bool> UseRenderTransformProperty = public static readonly StyledProperty<bool> UseRenderTransformProperty =
AvaloniaProperty.Register<LayoutTransformControl, bool>(nameof(LayoutTransform)); AvaloniaProperty.Register<LayoutTransformControl, bool>(nameof(LayoutTransform));
@ -37,7 +37,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets a graphics transformation that should apply to this element when layout is performed. /// Gets or sets a graphics transformation that should apply to this element when layout is performed.
/// </summary> /// </summary>
public Transform LayoutTransform public ITransform LayoutTransform
{ {
get { return GetValue(LayoutTransformProperty); } get { return GetValue(LayoutTransformProperty); }
set { SetValue(LayoutTransformProperty, value); } set { SetValue(LayoutTransformProperty, value); }

26
src/Avalonia.Controls/LoggingExtensions.cs

@ -0,0 +1,26 @@
using Avalonia.Controls;
using Avalonia.Logging;
namespace Avalonia
{
public static class LoggingExtensions
{
/// <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>
/// <param name="areas">The areas to log. Valid values are listed in <see cref="LogArea"/>.</param>
/// <returns>The app builder instance.</returns>
public static T LogToDebug<T>(
this T builder,
LogEventLevel level = LogEventLevel.Warning,
params string[] areas)
where T : AppBuilderBase<T>, new()
{
Logger.Sink = new DebugLogSink(level, areas);
return builder;
}
}
}

5
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -1,5 +1,4 @@
using System; using System;
using Avalonia.Controls;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -46,9 +45,9 @@ namespace Avalonia.Platform
IPlatformHandle Handle { get; } IPlatformHandle Handle { get; }
/// <summary> /// <summary>
/// Gets the maximum size of a window on the system. /// Gets a maximum client size hint for an auto-sizing window, in device-independent pixels.
/// </summary> /// </summary>
Size MaxClientSize { get; } Size MaxAutoSizeHint { get; }
/// <summary> /// <summary>
/// Sets whether this window appears on top of all other windows /// Sets whether this window appears on top of all other windows

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

@ -113,7 +113,7 @@ namespace Avalonia.Controls.Presenters
{ {
var scrollable = (ILogicalScrollable)Owner; var scrollable = (ILogicalScrollable)Owner;
var visualRoot = Owner.GetVisualRoot(); var visualRoot = Owner.GetVisualRoot();
var maxAvailableSize = (visualRoot as WindowBase)?.PlatformImpl?.MaxClientSize var maxAvailableSize = (visualRoot as WindowBase)?.PlatformImpl?.MaxAutoSizeHint
?? (visualRoot as TopLevel)?.ClientSize; ?? (visualRoot as TopLevel)?.ClientSize;
// If infinity is passed as the available size and we're virtualized then we need to // If infinity is passed as the available size and we're virtualized then we need to

15
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -121,7 +121,20 @@ namespace Avalonia.Controls.Primitives
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var measured = base.MeasureOverride(availableSize); var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity;
var constraint = availableSize;
if (double.IsInfinity(constraint.Width))
{
constraint = constraint.WithWidth(maxAutoSize.Width);
}
if (double.IsInfinity(constraint.Height))
{
constraint = constraint.WithHeight(maxAutoSize.Height);
}
var measured = base.MeasureOverride(constraint);
var width = measured.Width; var width = measured.Width;
var height = measured.Height; var height = measured.Height;
var widthCache = Width; var widthCache = Width;

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

@ -252,7 +252,7 @@ namespace Avalonia.Controls.Primitives
if (template != null) if (template != null)
{ {
Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Control, this, "Creating control template"); Logger.TryGet(LogEventLevel.Verbose, LogArea.Control)?.Log(this, "Creating control template");
var (child, nameScope) = template.Build(this); var (child, nameScope) = template.Build(this);
ApplyTemplatedParent(child); ApplyTemplatedParent(child);

100
src/Avalonia.Controls/ScrollViewer.cs

@ -181,6 +181,9 @@ namespace Avalonia.Controls
private Size _extent; private Size _extent;
private Vector _offset; private Vector _offset;
private Size _viewport; private Size _viewport;
private Size _oldExtent;
private Vector _oldOffset;
private Size _oldViewport;
private Size _largeChange; private Size _largeChange;
private Size _smallChange = new Size(DefaultSmallChange, DefaultSmallChange); private Size _smallChange = new Size(DefaultSmallChange, DefaultSmallChange);
@ -198,6 +201,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ScrollViewer() public ScrollViewer()
{ {
LayoutUpdated += OnLayoutUpdated;
} }
/// <summary> /// <summary>
@ -221,11 +225,9 @@ namespace Avalonia.Controls
private set private set
{ {
var old = _extent;
if (SetAndRaise(ExtentProperty, ref _extent, value)) if (SetAndRaise(ExtentProperty, ref _extent, value))
{ {
CalculatedPropertiesChanged(extentDelta: value - old); CalculatedPropertiesChanged();
} }
} }
} }
@ -242,13 +244,11 @@ namespace Avalonia.Controls
set set
{ {
var old = _offset;
value = ValidateOffset(this, value); value = ValidateOffset(this, value);
if (SetAndRaise(OffsetProperty, ref _offset, value)) if (SetAndRaise(OffsetProperty, ref _offset, value))
{ {
CalculatedPropertiesChanged(offsetDelta: value - old); CalculatedPropertiesChanged();
} }
} }
} }
@ -265,11 +265,9 @@ namespace Avalonia.Controls
private set private set
{ {
var old = _viewport;
if (SetAndRaise(ViewportProperty, ref _viewport, value)) if (SetAndRaise(ViewportProperty, ref _viewport, value))
{ {
CalculatedPropertiesChanged(viewportDelta: value - old); CalculatedPropertiesChanged();
} }
} }
} }
@ -387,6 +385,38 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
IControl IScrollAnchorProvider.CurrentAnchor => null; // TODO: Implement IControl IScrollAnchorProvider.CurrentAnchor => null; // TODO: Implement
/// <summary>
/// Scrolls the content up one line.
/// </summary>
public void LineUp()
{
Offset -= new Vector(0, _smallChange.Height);
}
/// <summary>
/// Scrolls the content down one line.
/// </summary>
public void LineDown()
{
Offset += new Vector(0, _smallChange.Height);
}
/// <summary>
/// Scrolls the content left one line.
/// </summary>
public void LineLeft()
{
Offset -= new Vector(_smallChange.Width, 0);
}
/// <summary>
/// Scrolls the content right one line.
/// </summary>
public void LineRight()
{
Offset += new Vector(_smallChange.Width, 0);
}
/// <summary> /// <summary>
/// Scrolls to the top-left corner of the content. /// Scrolls to the top-left corner of the content.
/// </summary> /// </summary>
@ -549,10 +579,7 @@ namespace Avalonia.Controls
} }
} }
private void CalculatedPropertiesChanged( private void CalculatedPropertiesChanged()
Size extentDelta = default,
Vector offsetDelta = default,
Size viewportDelta = default)
{ {
// Pass old values of 0 here because we don't have the old values at this point, // Pass old values of 0 here because we don't have the old values at this point,
// and it shouldn't matter as only the template uses these properies. // and it shouldn't matter as only the template uses these properies.
@ -573,20 +600,6 @@ namespace Avalonia.Controls
SetAndRaise(SmallChangeProperty, ref _smallChange, new Size(DefaultSmallChange, DefaultSmallChange)); SetAndRaise(SmallChangeProperty, ref _smallChange, new Size(DefaultSmallChange, DefaultSmallChange));
SetAndRaise(LargeChangeProperty, ref _largeChange, Viewport); SetAndRaise(LargeChangeProperty, ref _largeChange, Viewport);
} }
if (extentDelta != default || offsetDelta != default || viewportDelta != default)
{
using var route = BuildEventRoute(ScrollChangedEvent);
if (route.HasHandlers)
{
var e = new ScrollChangedEventArgs(
new Vector(extentDelta.Width, extentDelta.Height),
offsetDelta,
new Vector(viewportDelta.Width, viewportDelta.Height));
route.RaiseEvent(this, e);
}
}
} }
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
@ -602,5 +615,38 @@ namespace Avalonia.Controls
e.Handled = true; e.Handled = true;
} }
} }
/// <summary>
/// Called when a change in scrolling state is detected, such as a change in scroll
/// position, extent, or viewport size.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// If you override this method, call `base.OnScrollChanged(ScrollChangedEventArgs)` to
/// ensure that this event is raised.
/// </remarks>
protected virtual void OnScrollChanged(ScrollChangedEventArgs e)
{
RaiseEvent(e);
}
private void OnLayoutUpdated(object sender, EventArgs e) => RaiseScrollChanged();
private void RaiseScrollChanged()
{
var extentDelta = new Vector(Extent.Width - _oldExtent.Width, Extent.Height - _oldExtent.Height);
var offsetDelta = Offset - _oldOffset;
var viewportDelta = new Vector(Viewport.Width - _oldViewport.Width, Viewport.Height - _oldViewport.Height);
if (!extentDelta.NearlyEquals(default) || !offsetDelta.NearlyEquals(default) || !viewportDelta.NearlyEquals(default))
{
var e = new ScrollChangedEventArgs(extentDelta, offsetDelta, viewportDelta);
OnScrollChanged(e);
_oldExtent = Extent;
_oldOffset = Offset;
_oldViewport = Viewport;
}
}
} }
} }

1
src/Avalonia.Controls/TabItem.cs

@ -26,6 +26,7 @@ namespace Avalonia.Controls
static TabItem() static TabItem()
{ {
SelectableMixin.Attach<TabItem>(IsSelectedProperty); SelectableMixin.Attach<TabItem>(IsSelectedProperty);
PressedMixin.Attach<TabItem>();
FocusableProperty.OverrideDefaultValue(typeof(TabItem), true); FocusableProperty.OverrideDefaultValue(typeof(TabItem), true);
DataContextProperty.Changed.AddClassHandler<TabItem>((x, e) => x.UpdateHeader(e)); DataContextProperty.Changed.AddClassHandler<TabItem>((x, e) => x.UpdateHeader(e));
} }

32
src/Avalonia.Controls/TextBox.cs

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

33
src/Avalonia.Controls/ToggleSwitch.cs

@ -1,4 +1,5 @@
using Avalonia.Controls.Primitives; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
@ -57,6 +58,18 @@ namespace Avalonia.Controls
set { SetValue(OffContentProperty, value); } set { SetValue(OffContentProperty, value); }
} }
public IContentPresenter OffContentPresenter
{
get;
private set;
}
public IContentPresenter OnContentPresenter
{
get;
private set;
}
/// <summary> /// <summary>
/// Gets or Sets the <see cref="IDataTemplate"/> used to display the <see cref="OffContent"/>. /// Gets or Sets the <see cref="IDataTemplate"/> used to display the <see cref="OffContent"/>.
/// </summary> /// </summary>
@ -100,6 +113,24 @@ namespace Avalonia.Controls
LogicalChildren.Add(newChild); LogicalChildren.Add(newChild);
} }
} }
protected override bool RegisterContentPresenter(IContentPresenter presenter)
{
var result = base.RegisterContentPresenter(presenter);
if (presenter.Name == "Part_OnContentPresenter")
{
OnContentPresenter = presenter;
result = true;
}
else if (presenter.Name == "PART_OffContentPresenter")
{
OffContentPresenter = presenter;
result = true;
}
return result;
}
} }
} }

6
src/Avalonia.Controls/TopLevel.cs

@ -276,9 +276,6 @@ namespace Avalonia.Controls
set { SetValue(AccessText.ShowAccessKeyProperty, value); } set { SetValue(AccessText.ShowAccessKeyProperty, value); }
} }
/// <inheritdoc/>
Size ILayoutRoot.MaxClientSize => Size.Infinity;
/// <inheritdoc/> /// <inheritdoc/>
double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1; double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1;
@ -452,8 +449,7 @@ namespace Avalonia.Controls
if (result == null) if (result == null)
{ {
Logger.TryGet(LogEventLevel.Warning)?.Log( Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(
LogArea.Control,
this, this,
"Could not create {Service} : maybe Application.RegisterServices() wasn't called?", "Could not create {Service} : maybe Application.RegisterServices() wasn't called?",
typeof(T)); typeof(T));

95
src/Avalonia.Controls/Window.cs

@ -69,7 +69,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot
{ {
private List<Window> _children = new List<Window>(); private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();
/// <summary> /// <summary>
/// Defines the <see cref="SizeToContent"/> property. /// Defines the <see cref="SizeToContent"/> property.
@ -188,7 +188,7 @@ namespace Avalonia.Controls
impl.Closing = HandleClosing; impl.Closing = HandleClosing;
impl.GotInputWhenDisabled = OnGotInputWhenDisabled; impl.GotInputWhenDisabled = OnGotInputWhenDisabled;
impl.WindowStateChanged = HandleWindowStateChanged; impl.WindowStateChanged = HandleWindowStateChanged;
_maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size); _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size);
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x)); this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar); PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar);
@ -318,9 +318,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => PlatformImpl?.BeginResizeDrag(edge, e); public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => PlatformImpl?.BeginResizeDrag(edge, e);
/// <inheritdoc/>
Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
/// <inheritdoc/> /// <inheritdoc/>
Type IStyleable.StyleKey => typeof(Window); Type IStyleable.StyleKey => typeof(Window);
@ -376,7 +373,7 @@ namespace Avalonia.Controls
private void CloseInternal() private void CloseInternal()
{ {
foreach (var child in _children.ToList()) foreach (var (child, _) in _children.ToList())
{ {
// if we HandleClosing() before then there will be no children. // if we HandleClosing() before then there will be no children.
child.CloseInternal(); child.CloseInternal();
@ -399,7 +396,7 @@ namespace Avalonia.Controls
{ {
bool canClose = true; bool canClose = true;
foreach (var child in _children.ToList()) foreach (var (child, _) in _children.ToList())
{ {
if (!child.HandleClosing()) if (!child.HandleClosing())
{ {
@ -472,6 +469,28 @@ namespace Avalonia.Controls
/// The window has already been closed. /// The window has already been closed.
/// </exception> /// </exception>
public override void Show() public override void Show()
{
ShowCore(null);
}
/// <summary>
/// Shows the window as a child of <paramref name="parent"/>.
/// </summary>
/// <param name="parent">Window that will be a parent of the shown window.</param>
/// <exception cref="InvalidOperationException">
/// The window has already been closed.
/// </exception>
public void Show(Window parent)
{
if (parent is null)
{
throw new ArgumentNullException(nameof(parent), "Showing a child window requires valid parent.");
}
ShowCore(parent);
}
private void ShowCore(Window parent)
{ {
if (PlatformImpl == null) if (PlatformImpl == null)
{ {
@ -483,7 +502,7 @@ namespace Avalonia.Controls
return; return;
} }
this.RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
EnsureInitialized(); EnsureInitialized();
IsVisible = true; IsVisible = true;
@ -504,6 +523,14 @@ namespace Avalonia.Controls
using (BeginAutoSizing()) using (BeginAutoSizing())
{ {
if (parent != null)
{
PlatformImpl?.SetParent(parent.PlatformImpl);
}
Owner = parent;
parent?.AddChild(this, false);
PlatformImpl?.Show(); PlatformImpl?.Show();
Renderer?.Start(); Renderer?.Start();
} }
@ -571,9 +598,9 @@ namespace Avalonia.Controls
using (BeginAutoSizing()) using (BeginAutoSizing())
{ {
PlatformImpl.SetParent(owner.PlatformImpl); PlatformImpl?.SetParent(owner.PlatformImpl);
Owner = owner; Owner = owner;
owner.AddChild(this); owner.AddChild(this, true);
PlatformImpl?.Show(); PlatformImpl?.Show();
Renderer?.Start(); Renderer?.Start();
@ -598,28 +625,57 @@ namespace Avalonia.Controls
private void UpdateEnabled() private void UpdateEnabled()
{ {
PlatformImpl.SetEnabled(_children.Count == 0); bool isEnabled = true;
foreach (var (_, isDialog) in _children)
{
if (isDialog)
{
isEnabled = false;
break;
}
}
PlatformImpl.SetEnabled(isEnabled);
} }
private void AddChild(Window window) private void AddChild(Window window, bool isDialog)
{ {
_children.Add(window); _children.Add((window, isDialog));
UpdateEnabled(); UpdateEnabled();
} }
private void RemoveChild(Window window) private void RemoveChild(Window window)
{ {
_children.Remove(window); for (int i = _children.Count - 1; i >= 0; i--)
{
var (child, _) = _children[i];
if (ReferenceEquals(child, window))
{
_children.RemoveAt(i);
}
}
UpdateEnabled(); UpdateEnabled();
} }
private void OnGotInputWhenDisabled() private void OnGotInputWhenDisabled()
{ {
var firstChild = _children.FirstOrDefault(); Window firstDialogChild = null;
foreach (var (child, isDialog) in _children)
{
if (isDialog)
{
firstDialogChild = child;
break;
}
}
if (firstChild != null) if (firstDialogChild != null)
{ {
firstChild.OnGotInputWhenDisabled(); firstDialogChild.OnGotInputWhenDisabled();
} }
else else
{ {
@ -663,15 +719,16 @@ namespace Avalonia.Controls
var sizeToContent = SizeToContent; var sizeToContent = SizeToContent;
var clientSize = ClientSize; var clientSize = ClientSize;
var constraint = clientSize; var constraint = clientSize;
var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity;
if (sizeToContent.HasFlagCustom(SizeToContent.Width)) if (sizeToContent.HasFlagCustom(SizeToContent.Width))
{ {
constraint = constraint.WithWidth(double.PositiveInfinity); constraint = constraint.WithWidth(maxAutoSize.Width);
} }
if (sizeToContent.HasFlagCustom(SizeToContent.Height)) if (sizeToContent.HasFlagCustom(SizeToContent.Height))
{ {
constraint = constraint.WithHeight(double.PositiveInfinity); constraint = constraint.WithHeight(maxAutoSize.Height);
} }
var result = base.MeasureOverride(constraint); var result = base.MeasureOverride(constraint);

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

@ -44,7 +44,7 @@ namespace Avalonia.DesignerSupport.Remote
public IPlatformHandle Handle { get; } public IPlatformHandle Handle { get; }
public WindowState WindowState { get; set; } public WindowState WindowState { get; set; }
public Action<WindowState> WindowStateChanged { get; set; } public Action<WindowState> WindowStateChanged { get; set; }
public Size MaxClientSize { get; } = new Size(4096, 4096); public Size MaxAutoSizeHint { get; } = new Size(4096, 4096);
public event Action LostFocus public event Action LostFocus
{ {
add {} add {}

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

@ -19,7 +19,7 @@ namespace Avalonia.DesignerSupport.Remote
public Action Deactivated { get; set; } public Action Deactivated { get; set; }
public Action Activated { get; set; } public Action Activated { get; set; }
public IPlatformHandle Handle { get; } public IPlatformHandle Handle { get; }
public Size MaxClientSize { get; } public Size MaxAutoSizeHint { get; }
public Size ClientSize { get; } public Size ClientSize { get; }
public double Scaling { get; } = 1.0; public double Scaling { get; } = 1.0;
public IEnumerable<object> Surfaces { get; } public IEnumerable<object> Surfaces { get; }

8
src/Avalonia.Layout/ILayoutManager.cs

@ -1,3 +1,6 @@
using System;
#nullable enable
namespace Avalonia.Layout namespace Avalonia.Layout
{ {
@ -6,6 +9,11 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
public interface ILayoutManager public interface ILayoutManager
{ {
/// <summary>
/// Raised when the layout manager completes a layout pass.
/// </summary>
event EventHandler LayoutUpdated;
/// <summary> /// <summary>
/// Notifies the layout manager that a control requires a measure. /// Notifies the layout manager that a control requires a measure.
/// </summary> /// </summary>

5
src/Avalonia.Layout/ILayoutRoot.cs

@ -10,11 +10,6 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
Size ClientSize { get; } Size ClientSize { get; }
/// <summary>
/// The maximum client size available.
/// </summary>
Size MaxClientSize { get; }
/// <summary> /// <summary>
/// The scaling factor to use in layout. /// The scaling factor to use in layout.
/// </summary> /// </summary>

2
src/Avalonia.Layout/ILayoutable.cs

@ -1,5 +1,7 @@
using Avalonia.VisualTree; using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Layout namespace Avalonia.Layout
{ {
/// <summary> /// <summary>

20
src/Avalonia.Layout/LayoutManager.cs

@ -3,6 +3,8 @@ using System.Diagnostics;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Threading; using Avalonia.Threading;
#nullable enable
namespace Avalonia.Layout namespace Avalonia.Layout
{ {
/// <summary> /// <summary>
@ -21,10 +23,12 @@ namespace Avalonia.Layout
_executeLayoutPass = ExecuteLayoutPass; _executeLayoutPass = ExecuteLayoutPass;
} }
public event EventHandler? LayoutUpdated;
/// <inheritdoc/> /// <inheritdoc/>
public void InvalidateMeasure(ILayoutable control) public void InvalidateMeasure(ILayoutable control)
{ {
Contract.Requires<ArgumentNullException>(control != null); control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
if (!control.IsAttachedToVisualTree) if (!control.IsAttachedToVisualTree)
@ -45,7 +49,7 @@ namespace Avalonia.Layout
/// <inheritdoc/> /// <inheritdoc/>
public void InvalidateArrange(ILayoutable control) public void InvalidateArrange(ILayoutable control)
{ {
Contract.Requires<ArgumentNullException>(control != null); control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
if (!control.IsAttachedToVisualTree) if (!control.IsAttachedToVisualTree)
@ -73,15 +77,14 @@ namespace Avalonia.Layout
{ {
_running = true; _running = true;
Stopwatch stopwatch = null; Stopwatch? stopwatch = null;
const LogEventLevel timingLogLevel = LogEventLevel.Information; const LogEventLevel timingLogLevel = LogEventLevel.Information;
bool captureTiming = Logger.IsEnabled(timingLogLevel); bool captureTiming = Logger.IsEnabled(timingLogLevel, LogArea.Layout);
if (captureTiming) if (captureTiming)
{ {
Logger.TryGet(timingLogLevel)?.Log( Logger.TryGet(timingLogLevel, LogArea.Layout)?.Log(
LogArea.Layout,
this, this,
"Started layout pass. To measure: {Measure} To arrange: {Arrange}", "Started layout pass. To measure: {Measure} To arrange: {Arrange}",
_toMeasure.Count, _toMeasure.Count,
@ -117,13 +120,14 @@ namespace Avalonia.Layout
if (captureTiming) if (captureTiming)
{ {
stopwatch.Stop(); stopwatch!.Stop();
Logger.TryGet(timingLogLevel)?.Log(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed); Logger.TryGet(timingLogLevel, LogArea.Layout)?.Log(this, "Layout pass finished in {Time}", stopwatch.Elapsed);
} }
} }
_queued = false; _queued = false;
LayoutUpdated?.Invoke(this, EventArgs.Empty);
} }
/// <inheritdoc/> /// <inheritdoc/>

65
src/Avalonia.Layout/Layoutable.cs

@ -1,8 +1,9 @@
using System; using System;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Layout namespace Avalonia.Layout
{ {
/// <summary> /// <summary>
@ -131,6 +132,7 @@ namespace Avalonia.Layout
private bool _measuring; private bool _measuring;
private Size? _previousMeasure; private Size? _previousMeasure;
private Rect? _previousArrange; private Rect? _previousArrange;
private EventHandler? _layoutUpdated;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Layoutable"/> class. /// Initializes static members of the <see cref="Layoutable"/> class.
@ -153,7 +155,28 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Occurs when a layout pass completes for the control. /// Occurs when a layout pass completes for the control.
/// </summary> /// </summary>
public event EventHandler LayoutUpdated; public event EventHandler? LayoutUpdated
{
add
{
if (_layoutUpdated is null && VisualRoot is ILayoutRoot r)
{
r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
}
_layoutUpdated += value;
}
remove
{
_layoutUpdated -= value;
if (_layoutUpdated is null && VisualRoot is ILayoutRoot r)
{
r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
}
}
}
/// <summary> /// <summary>
/// Gets or sets the width of the element. /// Gets or sets the width of the element.
@ -326,7 +349,7 @@ namespace Avalonia.Layout
DesiredSize = desiredSize; DesiredSize = desiredSize;
_previousMeasure = availableSize; _previousMeasure = availableSize;
Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Measure requested {DesiredSize}", DesiredSize); Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Measure requested {DesiredSize}", DesiredSize);
if (DesiredSize != previousDesiredSize) if (DesiredSize != previousDesiredSize)
{ {
@ -353,17 +376,14 @@ namespace Avalonia.Layout
if (!IsArrangeValid || _previousArrange != rect) if (!IsArrangeValid || _previousArrange != rect)
{ {
Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Arrange to {Rect} ", rect); Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Arrange to {Rect} ", rect);
IsArrangeValid = true; IsArrangeValid = true;
ArrangeCore(rect); ArrangeCore(rect);
_previousArrange = rect; _previousArrange = rect;
LayoutUpdated?.Invoke(this, EventArgs.Empty);
} }
} }
/// <summary> /// <summary>
/// Called by InvalidateMeasure /// Called by InvalidateMeasure
/// </summary> /// </summary>
@ -378,7 +398,7 @@ namespace Avalonia.Layout
{ {
if (IsMeasureValid) if (IsMeasureValid)
{ {
Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Invalidated measure"); Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Invalidated measure");
IsMeasureValid = false; IsMeasureValid = false;
IsArrangeValid = false; IsArrangeValid = false;
@ -399,7 +419,7 @@ namespace Avalonia.Layout
{ {
if (IsArrangeValid) if (IsArrangeValid)
{ {
Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Invalidated arrange"); Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Invalidated arrange");
IsArrangeValid = false; IsArrangeValid = false;
(VisualRoot as ILayoutRoot)?.LayoutManager?.InvalidateArrange(this); (VisualRoot as ILayoutRoot)?.LayoutManager?.InvalidateArrange(this);
@ -693,6 +713,26 @@ namespace Avalonia.Layout
InvalidateMeasure(); InvalidateMeasure();
} }
protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTreeCore(e);
if (_layoutUpdated is object && e.Root is ILayoutRoot r)
{
r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
}
}
protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTreeCore(e);
if (_layoutUpdated is object && e.Root is ILayoutRoot r)
{
r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
}
}
/// <inheritdoc/> /// <inheritdoc/>
protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent) protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{ {
@ -701,6 +741,13 @@ namespace Avalonia.Layout
base.OnVisualParentChanged(oldParent, newParent); base.OnVisualParentChanged(oldParent, newParent);
} }
/// <summary>
/// Called when the layout manager raises a LayoutUpdated event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event args.</param>
private void LayoutManagedLayoutUpdated(object sender, EventArgs e) => _layoutUpdated?.Invoke(this, e);
/// <summary> /// <summary>
/// Tests whether any of a <see cref="Rect"/>'s properties include negative values, /// Tests whether any of a <see cref="Rect"/>'s properties include negative values,
/// a NaN or Infinity. /// a NaN or Infinity.

10
src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj

@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
</ItemGroup>
<Import Project="..\..\build\Serilog.props" />
</Project>

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

@ -1,103 +0,0 @@
using System;
using Avalonia.Controls;
using Serilog;
using Serilog.Configuration;
using Serilog.Filters;
using SerilogLevel = Serilog.Events.LogEventLevel;
namespace Avalonia.Logging.Serilog
{
/// <summary>
/// Extension methods for Serilog logging.
/// </summary>
public static class SerilogExtensions
{
private const string DefaultTemplate = "[{Area}] {Message} ({SourceType} #{SourceHash})";
/// <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)
.Enrich.FromLogContext()
.WriteTo.Debug(outputTemplate: DefaultTemplate)
.CreateLogger());
return builder;
}
/// <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="area">The area to log. Valid values are listed in <see cref="LogArea"/>.</param>
/// <param name="level">The minimum level to log.</param>
/// <returns>The app builder instance.</returns>
public static T LogToDebug<T>(
this T builder,
string area,
LogEventLevel level = LogEventLevel.Warning)
where T : AppBuilderBase<T>, new()
{
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Is((SerilogLevel)level)
.Filter.ByIncludingOnly(Matching.WithProperty("Area", area))
.Enrich.FromLogContext()
.WriteTo.Debug(outputTemplate: DefaultTemplate)
.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)
.Enrich.FromLogContext()
.WriteTo.Trace(outputTemplate: DefaultTemplate)
.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="area">The area to log. Valid values are listed in <see cref="LogArea"/>.</param>
/// <param name="level">The minimum level to log.</param>
/// <returns>The app builder instance.</returns>
public static T LogToTrace<T>(
this T builder,
string area,
LogEventLevel level = LogEventLevel.Warning)
where T : AppBuilderBase<T>, new()
{
SerilogLogger.Initialize(new LoggerConfiguration()
.MinimumLevel.Is((SerilogLevel)level)
.Filter.ByIncludingOnly(Matching.WithProperty("Area", area))
.Enrich.FromLogContext()
.WriteTo.Trace(outputTemplate: DefaultTemplate)
.CreateLogger());
return builder;
}
}
}

151
src/Avalonia.Logging.Serilog/SerilogLogger.cs

@ -1,151 +0,0 @@
using System;
using Serilog;
using Serilog.Context;
using AvaloniaLogEventLevel = Avalonia.Logging.LogEventLevel;
using SerilogLogEventLevel = Serilog.Events.LogEventLevel;
namespace Avalonia.Logging.Serilog
{
/// <summary>
/// Sends log output to serilog.
/// </summary>
public class SerilogLogger : ILogSink
{
private readonly ILogger _output;
/// <summary>
/// Initializes a new instance of the <see cref="SerilogLogger"/> class.
/// </summary>
/// <param name="output">The serilog logger to use.</param>
public SerilogLogger(ILogger output)
{
_output = output;
}
/// <summary>
/// Initializes the Avalonia logging with a new instance of a <see cref="SerilogLogger"/>.
/// </summary>
/// <param name="output">The serilog logger to use.</param>
public static void Initialize(ILogger output)
{
Logger.Sink = new SerilogLogger(output);
}
public bool IsEnabled(LogEventLevel level)
{
return _output.IsEnabled((SerilogLogEventLevel)level);
}
public void Log(
LogEventLevel level,
string area,
object source,
string messageTemplate)
{
Contract.Requires<ArgumentNullException>(area != null);
Contract.Requires<ArgumentNullException>(messageTemplate != null);
using (PushLogContextProperties(area, source))
{
_output.Write((SerilogLogEventLevel)level, messageTemplate);
}
}
public void Log<T0>(
LogEventLevel level,
string area, object source,
string messageTemplate,
T0 propertyValue0)
{
Contract.Requires<ArgumentNullException>(area != null);
Contract.Requires<ArgumentNullException>(messageTemplate != null);
using (PushLogContextProperties(area, source))
{
_output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0);
}
}
public void Log<T0, T1>(
LogEventLevel level,
string area,
object source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1)
{
Contract.Requires<ArgumentNullException>(area != null);
Contract.Requires<ArgumentNullException>(messageTemplate != null);
using (PushLogContextProperties(area, source))
{
_output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0, propertyValue1);
}
}
public void Log<T0, T1, T2>(
LogEventLevel level,
string area,
object source,
string messageTemplate,
T0 propertyValue0,
T1 propertyValue1,
T2 propertyValue2)
{
Contract.Requires<ArgumentNullException>(area != null);
Contract.Requires<ArgumentNullException>(messageTemplate != null);
using (PushLogContextProperties(area, source))
{
_output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0, propertyValue1, propertyValue2);
}
}
/// <inheritdoc/>
public void Log(
AvaloniaLogEventLevel level,
string area,
object source,
string messageTemplate,
params object[] propertyValues)
{
Contract.Requires<ArgumentNullException>(area != null);
Contract.Requires<ArgumentNullException>(messageTemplate != null);
using (PushLogContextProperties(area, source))
{
_output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValues);
}
}
private static LogContextDisposable PushLogContextProperties(string area, object source)
{
return new LogContextDisposable(
LogContext.PushProperty("Area", area),
LogContext.PushProperty("SourceType", source?.GetType()),
LogContext.PushProperty("SourceHash", source?.GetHashCode())
);
}
private readonly struct LogContextDisposable : IDisposable
{
private readonly IDisposable _areaDisposable;
private readonly IDisposable _sourceTypeDisposable;
private readonly IDisposable _sourceHashDisposable;
public LogContextDisposable(IDisposable areaDisposable, IDisposable sourceTypeDisposable, IDisposable sourceHashDisposable)
{
_areaDisposable = areaDisposable;
_sourceTypeDisposable = sourceTypeDisposable;
_sourceHashDisposable = sourceHashDisposable;
}
public void Dispose()
{
_areaDisposable.Dispose();
_sourceTypeDisposable.Dispose();
_sourceHashDisposable.Dispose();
}
}
}
}

2
src/Avalonia.Native/WindowImplBase.cs

@ -336,7 +336,7 @@ namespace Avalonia.Native
_native.BeginMoveDrag(); _native.BeginMoveDrag();
} }
public Size MaxClientSize => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity)) public Size MaxAutoSizeHint => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault(); .OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
public void SetTopmost(bool value) public void SetTopmost(bool value)

2
src/Avalonia.OpenGL/EglGlPlatformFeature.cs

@ -34,7 +34,7 @@ namespace Avalonia.OpenGL
} }
catch(Exception e) catch(Exception e)
{ {
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", null, "Unable to initialize EGL-based rendering: {0}", e); Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e);
return null; return null;
} }
} }

8
src/Avalonia.OpenGL/OpenGlControlBase.cs

@ -94,7 +94,7 @@ namespace Avalonia.OpenGL
} }
catch (Exception e) catch (Exception e)
{ {
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase", Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e); "Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e);
_glFailed = true; _glFailed = true;
return false; return false;
@ -109,7 +109,7 @@ namespace Avalonia.OpenGL
{ {
_context.Dispose(); _context.Dispose();
_context = null; _context = null;
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase", Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL: unable to create OpenGlTextureBitmap: {exception}", e); "Unable to initialize OpenGL: unable to create OpenGlTextureBitmap: {exception}", e);
_glFailed = true; _glFailed = true;
return false; return false;
@ -138,7 +138,7 @@ namespace Avalonia.OpenGL
{ {
int code; int code;
while ((code = gl.GetError()) != 0) while ((code = gl.GetError()) != 0)
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase", Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL FBO: {code}", code); "Unable to initialize OpenGL FBO: {code}", code);
_glFailed = true; _glFailed = true;
@ -147,7 +147,7 @@ namespace Avalonia.OpenGL
} }
catch(Exception e) catch(Exception e)
{ {
Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", "OpenGlControlBase", Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL FBO: {exception}", e); "Unable to initialize OpenGL FBO: {exception}", e);
_glFailed = true; _glFailed = true;
} }

3
src/Avalonia.Styling/StyledElement.cs

@ -689,8 +689,7 @@ namespace Avalonia
#if DEBUG #if DEBUG
if (((INotifyCollectionChangedDebug)Classes).GetCollectionChangedSubscribers()?.Length > 0) if (((INotifyCollectionChangedDebug)Classes).GetCollectionChangedSubscribers()?.Length > 0)
{ {
Logger.TryGet(LogEventLevel.Warning)?.Log( Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(
LogArea.Control,
this, this,
"{Type} detached from logical tree but still has class listeners", "{Type} detached from logical tree but still has class listeners",
GetType()); GetType());

2
src/Avalonia.Themes.Default/ContextMenu.xaml

@ -10,7 +10,7 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"> Padding="{TemplateBinding Padding}">
<ScrollViewer> <ScrollViewer Classes="menuscroller">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"

2
src/Avalonia.Themes.Default/MenuItem.xaml

@ -113,7 +113,7 @@
<Border Background="{TemplateBinding Background}" <Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer> <ScrollViewer Classes="menuscroller">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"

150
src/Avalonia.Themes.Default/ScrollViewer.xaml

@ -1,47 +1,105 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ScrollViewer"> <Styles xmlns="https://github.com/avaloniaui"
<Setter Property="Background" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Value="Transparent" /> xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls">
<Setter Property="Template"> <Style Selector="ScrollViewer">
<ControlTemplate> <Setter Property="Background"
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto"> Value="Transparent" />
<ScrollContentPresenter Name="PART_ContentPresenter" <Setter Property="Template">
Background="{TemplateBinding Background}" <ControlTemplate>
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}" <Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}" <ScrollContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}" Background="{TemplateBinding Background}"
Extent="{TemplateBinding Extent, Mode=TwoWay}" CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
Margin="{TemplateBinding Padding}" CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
Offset="{TemplateBinding Offset, Mode=TwoWay}" Content="{TemplateBinding Content}"
Viewport="{TemplateBinding Viewport, Mode=TwoWay}"> Extent="{TemplateBinding Extent, Mode=TwoWay}"
<ScrollContentPresenter.GestureRecognizers> Margin="{TemplateBinding Padding}"
<ScrollGestureRecognizer Offset="{TemplateBinding Offset, Mode=TwoWay}"
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}" Viewport="{TemplateBinding Viewport, Mode=TwoWay}">
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}" <ScrollContentPresenter.GestureRecognizers>
<ScrollGestureRecognizer
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
/> />
</ScrollContentPresenter.GestureRecognizers> </ScrollContentPresenter.GestureRecognizers>
</ScrollContentPresenter> </ScrollContentPresenter>
<ScrollBar Name="horizontalScrollBar" <ScrollBar Name="horizontalScrollBar"
Orientation="Horizontal" Orientation="Horizontal"
LargeChange="{Binding LargeChange.Width, RelativeSource={RelativeSource TemplatedParent}}" LargeChange="{Binding LargeChange.Width, RelativeSource={RelativeSource TemplatedParent}}"
SmallChange="{Binding SmallChange.Width, RelativeSource={RelativeSource TemplatedParent}}" SmallChange="{Binding SmallChange.Width, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{TemplateBinding HorizontalScrollBarMaximum}" Maximum="{TemplateBinding HorizontalScrollBarMaximum}"
Value="{TemplateBinding HorizontalScrollBarValue, Mode=TwoWay}" Value="{TemplateBinding HorizontalScrollBarValue, Mode=TwoWay}"
ViewportSize="{TemplateBinding HorizontalScrollBarViewportSize}" ViewportSize="{TemplateBinding HorizontalScrollBarViewportSize}"
Visibility="{TemplateBinding HorizontalScrollBarVisibility}" Visibility="{TemplateBinding HorizontalScrollBarVisibility}"
Grid.Row="1" Grid.Row="1"
Focusable="False"/> Focusable="False"/>
<ScrollBar Name="verticalScrollBar" <ScrollBar Name="verticalScrollBar"
Orientation="Vertical" Orientation="Vertical"
LargeChange="{Binding LargeChange.Height, RelativeSource={RelativeSource TemplatedParent}}" LargeChange="{Binding LargeChange.Height, RelativeSource={RelativeSource TemplatedParent}}"
SmallChange="{Binding SmallChange.Height, RelativeSource={RelativeSource TemplatedParent}}" SmallChange="{Binding SmallChange.Height, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{TemplateBinding VerticalScrollBarMaximum}" Maximum="{TemplateBinding VerticalScrollBarMaximum}"
Value="{TemplateBinding VerticalScrollBarValue, Mode=TwoWay}" Value="{TemplateBinding VerticalScrollBarValue, Mode=TwoWay}"
ViewportSize="{TemplateBinding VerticalScrollBarViewportSize}" ViewportSize="{TemplateBinding VerticalScrollBarViewportSize}"
Visibility="{TemplateBinding VerticalScrollBarVisibility}" Visibility="{TemplateBinding VerticalScrollBarVisibility}"
Grid.Column="1" Grid.Column="1"
Focusable="False"/> Focusable="False"/>
<Panel Grid.Row="1" Grid.Column="1" Background="{DynamicResource ThemeControlMidBrush}"/> <Panel Grid.Row="1" Grid.Column="1" Background="{DynamicResource ThemeControlMidBrush}"/>
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="ScrollViewer.menuscroller">
<Setter Property="Template">
<ControlTemplate>
<DockPanel>
<RepeatButton DockPanel.Dock="Top"
BorderThickness="0"
Background="Transparent"
Command="{Binding LineUp, RelativeSource={RelativeSource TemplatedParent}}">
<RepeatButton.IsVisible>
<MultiBinding Converter="{x:Static converters:MenuScrollingVisibilityConverter.Instance}"
ConverterParameter="0">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="VerticalScrollBarVisibility"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Offset.Y"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Extent.Height"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Viewport.Height"/>
</MultiBinding>
</RepeatButton.IsVisible>
<Path Data="M 0 4 L 8 4 L 4 0 Z"/>
</RepeatButton>
<RepeatButton DockPanel.Dock="Bottom"
BorderThickness="0"
Background="Transparent"
Command="{Binding LineDown, RelativeSource={RelativeSource TemplatedParent}}">
<RepeatButton.IsVisible>
<MultiBinding Converter="{x:Static converters:MenuScrollingVisibilityConverter.Instance}"
ConverterParameter="100">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="VerticalScrollBarVisibility"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Offset.Y"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Extent.Height"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Viewport.Height"/>
</MultiBinding>
</RepeatButton.IsVisible>
<Path Data="M 0 0 L 4 4 L 8 0 Z"/>
</RepeatButton>
<ScrollContentPresenter Name="PART_ContentPresenter"
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
Content="{TemplateBinding Content}"
Extent="{TemplateBinding Extent, Mode=TwoWay}"
Margin="{TemplateBinding Padding}"
Offset="{TemplateBinding Offset, Mode=TwoWay}"
Viewport="{TemplateBinding Viewport, Mode=TwoWay}"/>
</DockPanel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ScrollViewer.menuscroller /template/ RepeatButton > Path">
<Setter Property="Fill" Value="{DynamicResource ThemeForegroundLowBrush}" />
</Style>
<Style Selector="ScrollViewer.menuscroller /template/ RepeatButton:pointerover > Path">
<Setter Property="Fill" Value="{DynamicResource ThemeAccentBrush}" />
</Style>
</Styles>

69
src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml

@ -402,5 +402,74 @@
<SolidColorBrush x:Key="ToolTipBackgroundThemeBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="ToolTipBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" /> <SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" />
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" /> <SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<!-- Resources for Pivot.xaml -->
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
<x:Double x:Key="PivotHeaderItemFontSize">24</x:Double>
<x:Double x:Key="PivotHeaderItemLockedTranslation">40</x:Double>
<x:Double x:Key="PivotTitleFontSize">14</x:Double>
<x:Int32 x:Key="PivotHeaderItemCharacterSpacing">-25</x:Int32>
<Thickness x:Key="PivotHeaderItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotLandscapeThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="PivotNavButtonBorderThemeThickness">0</Thickness>
<Thickness x:Key="PivotNavButtonMargin">0,6,0,0</Thickness>
<Thickness x:Key="PivotPortraitThemePadding">12,14,0,13</Thickness>
<FontWeight x:Key="PivotHeaderItemThemeFontWeight">SemiLight</FontWeight>
<FontWeight x:Key="PivotTitleThemeFontWeight">Bold</FontWeight>
<StaticResource x:Key="PivotBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBackground" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="PivotNextButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="PivotNextButtonBackgroundPressed" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrushPointerOver" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrushPressed" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonForeground" ResourceKey="SystemControlForegroundAltMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonForegroundPressed" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonBackground" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="PivotPreviousButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="PivotPreviousButtonBackgroundPressed" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrushPointerOver" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrushPressed" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonForeground" ResourceKey="SystemControlForegroundAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonForegroundPressed" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<!-- Resources for PivotItem -->
<StaticResource x:Key="PivotItemBackground" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for PivotHeaderItem -->
<StaticResource x:Key="PivotHeaderItemBackgroundUnselected" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundUnselectedPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundUnselectedPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelected" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelectedPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelectedPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselected" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselectedPointerOver" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselectedPressed" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelected" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelectedPointerOver" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelectedPressed" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="PivotHeaderItemFocusPipeFill" ResourceKey="SystemControlHighlightAltAccentBrush" />
<StaticResource x:Key="PivotHeaderItemSelectedPipeFill" ResourceKey="SystemControlHighlightAltAccentBrush" />
<SolidColorBrush x:Key="PivotForegroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="PivotHeaderBackgroundSelectedBrush" Color="Transparent" />
<SolidColorBrush x:Key="PivotHeaderBackgroundUnselectedBrush" Color="Transparent" />
<SolidColorBrush x:Key="PivotHeaderForegroundSelectedBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="PivotHeaderForegroundUnselectedBrush" Color="#66FFFFFF" />
<SolidColorBrush x:Key="PivotNavButtonBackgroundThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="PivotNavButtonBorderThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="PivotNavButtonForegroundThemeBrush" Color="#99000000" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverBackgroundThemeBrush" Color="#F0D7D7D7" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverBorderThemeBrush" Color="#9EC1C1C1" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverForegroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="PivotNavButtonPressedBackgroundThemeBrush" Color="#BD292929" />
<SolidColorBrush x:Key="PivotNavButtonPressedBorderThemeBrush" Color="#BD292929" />
<SolidColorBrush x:Key="PivotNavButtonPressedForegroundThemeBrush" Color="#FFFFFFFF" />
</Style.Resources> </Style.Resources>
</Style> </Style>

69
src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml

@ -404,5 +404,74 @@
<SolidColorBrush x:Key="ToolTipBackgroundThemeBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="ToolTipBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" /> <SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" />
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" /> <SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<!-- Resources for Pivot.xaml -->
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
<x:Double x:Key="PivotHeaderItemFontSize">24</x:Double>
<x:Double x:Key="PivotHeaderItemLockedTranslation">40</x:Double>
<x:Double x:Key="PivotTitleFontSize">14</x:Double>
<x:Int32 x:Key="PivotHeaderItemCharacterSpacing">-25</x:Int32>
<Thickness x:Key="PivotHeaderItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotLandscapeThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="PivotNavButtonBorderThemeThickness">0</Thickness>
<Thickness x:Key="PivotNavButtonMargin">0,6,0,0</Thickness>
<Thickness x:Key="PivotPortraitThemePadding">12,14,0,13</Thickness>
<FontWeight x:Key="PivotHeaderItemThemeFontWeight">SemiLight</FontWeight>
<FontWeight x:Key="PivotTitleThemeFontWeight">Bold</FontWeight>
<StaticResource x:Key="PivotBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBackground" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="PivotNextButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="PivotNextButtonBackgroundPressed" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrushPointerOver" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrushPressed" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonForeground" ResourceKey="SystemControlForegroundAltMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonForegroundPressed" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonBackground" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="PivotPreviousButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="PivotPreviousButtonBackgroundPressed" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrushPointerOver" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrushPressed" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonForeground" ResourceKey="SystemControlForegroundAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonForegroundPressed" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<!-- Resources for PivotItem -->
<StaticResource x:Key="PivotItemBackground" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for PivotHeaderItem -->
<StaticResource x:Key="PivotHeaderItemBackgroundUnselected" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundUnselectedPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundUnselectedPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelected" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelectedPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelectedPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselected" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselectedPointerOver" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselectedPressed" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelected" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelectedPointerOver" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelectedPressed" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="PivotHeaderItemFocusPipeFill" ResourceKey="SystemControlHighlightAltAccentBrush" />
<StaticResource x:Key="PivotHeaderItemSelectedPipeFill" ResourceKey="SystemControlHighlightAltAccentBrush" />
<SolidColorBrush x:Key="PivotForegroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="PivotHeaderBackgroundSelectedBrush" Color="Transparent" />
<SolidColorBrush x:Key="PivotHeaderBackgroundUnselectedBrush" Color="Transparent" />
<SolidColorBrush x:Key="PivotHeaderForegroundSelectedBrush" Color="#FF000000" />
<SolidColorBrush x:Key="PivotHeaderForegroundUnselectedBrush" Color="#66000000" />
<SolidColorBrush x:Key="PivotNavButtonBackgroundThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="PivotNavButtonBorderThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="PivotNavButtonForegroundThemeBrush" Color="#99000000" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverBackgroundThemeBrush" Color="#F0D7D7D7" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverBorderThemeBrush" Color="#9EC1C1C1" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverForegroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="PivotNavButtonPressedBackgroundThemeBrush" Color="#BD292929" />
<SolidColorBrush x:Key="PivotNavButtonPressedBorderThemeBrush" Color="#BD292929" />
<SolidColorBrush x:Key="PivotNavButtonPressedForegroundThemeBrush" Color="#FFFFFFFF" />
</Style.Resources> </Style.Resources>
</Style> </Style>

69
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -657,5 +657,74 @@
<SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" /> <SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" />
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" /> <SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness> <Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness>
<!-- Resources for Pivot.xaml -->
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
<x:Double x:Key="PivotHeaderItemFontSize">24</x:Double>
<x:Double x:Key="PivotHeaderItemLockedTranslation">40</x:Double>
<x:Double x:Key="PivotTitleFontSize">14</x:Double>
<x:Int32 x:Key="PivotHeaderItemCharacterSpacing">-25</x:Int32>
<Thickness x:Key="PivotHeaderItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotLandscapeThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="PivotNavButtonBorderThemeThickness">0</Thickness>
<Thickness x:Key="PivotNavButtonMargin">0,6,0,0</Thickness>
<Thickness x:Key="PivotPortraitThemePadding">12,14,0,13</Thickness>
<FontWeight x:Key="PivotHeaderItemThemeFontWeight">SemiLight</FontWeight>
<FontWeight x:Key="PivotTitleThemeFontWeight">Bold</FontWeight>
<StaticResource x:Key="PivotBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBackground" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="PivotNextButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="PivotNextButtonBackgroundPressed" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrushPointerOver" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrushPressed" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonForeground" ResourceKey="SystemControlForegroundAltMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonForegroundPressed" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonBackground" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="PivotPreviousButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="PivotPreviousButtonBackgroundPressed" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrushPointerOver" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrushPressed" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonForeground" ResourceKey="SystemControlForegroundAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonForegroundPressed" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<!-- Resources for PivotItem -->
<StaticResource x:Key="PivotItemBackground" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for PivotHeaderItem -->
<StaticResource x:Key="PivotHeaderItemBackgroundUnselected" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundUnselectedPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundUnselectedPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelected" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelectedPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelectedPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselected" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselectedPointerOver" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselectedPressed" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelected" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelectedPointerOver" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelectedPressed" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="PivotHeaderItemFocusPipeFill" ResourceKey="SystemControlHighlightAltAccentBrush" />
<StaticResource x:Key="PivotHeaderItemSelectedPipeFill" ResourceKey="SystemControlHighlightAltAccentBrush" />
<SolidColorBrush x:Key="PivotForegroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="PivotHeaderBackgroundSelectedBrush" Color="Transparent" />
<SolidColorBrush x:Key="PivotHeaderBackgroundUnselectedBrush" Color="Transparent" />
<SolidColorBrush x:Key="PivotHeaderForegroundSelectedBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="PivotHeaderForegroundUnselectedBrush" Color="#66FFFFFF" />
<SolidColorBrush x:Key="PivotNavButtonBackgroundThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="PivotNavButtonBorderThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="PivotNavButtonForegroundThemeBrush" Color="#99000000" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverBackgroundThemeBrush" Color="#F0D7D7D7" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverBorderThemeBrush" Color="#9EC1C1C1" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverForegroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="PivotNavButtonPressedBackgroundThemeBrush" Color="#BD292929" />
<SolidColorBrush x:Key="PivotNavButtonPressedBorderThemeBrush" Color="#BD292929" />
<SolidColorBrush x:Key="PivotNavButtonPressedForegroundThemeBrush" Color="#FFFFFFFF" />
</Style.Resources> </Style.Resources>
</Style> </Style>

73
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@ -366,8 +366,8 @@
<x:Double x:Key="AutoCompleteBoxIconFontSize">12</x:Double> <x:Double x:Key="AutoCompleteBoxIconFontSize">12</x:Double>
<!-- Resources for CheckBox.xaml --> <!-- Resources for CheckBox.xaml -->
<x:Double x:Key="CheckBoxBorderThemeThickness">1</x:Double> <Thickness x:Key="CheckBoxBorderThemeThickness">1</Thickness>
<x:Double x:Key="CheckBoxCheckedStrokeThickness">0</x:Double> <Thickness x:Key="CheckBoxCheckedStrokeThickness">0</Thickness>
<StaticResource x:Key="CheckBoxForegroundUnchecked" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="CheckBoxForegroundUnchecked" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CheckBoxForegroundUncheckedPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="CheckBoxForegroundUncheckedPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CheckBoxForegroundUncheckedPressed" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="CheckBoxForegroundUncheckedPressed" ResourceKey="SystemControlForegroundBaseHighBrush" />
@ -655,5 +655,74 @@
<SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" /> <SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" />
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" /> <SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness> <Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness>
<!-- Resources for Pivot.xaml -->
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
<x:Double x:Key="PivotHeaderItemFontSize">24</x:Double>
<x:Double x:Key="PivotHeaderItemLockedTranslation">40</x:Double>
<x:Double x:Key="PivotTitleFontSize">14</x:Double>
<x:Int32 x:Key="PivotHeaderItemCharacterSpacing">-25</x:Int32>
<Thickness x:Key="PivotHeaderItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotItemMargin">12,0,12,0</Thickness>
<Thickness x:Key="PivotLandscapeThemePadding">12,14,0,13</Thickness>
<Thickness x:Key="PivotNavButtonBorderThemeThickness">0</Thickness>
<Thickness x:Key="PivotNavButtonMargin">0,6,0,0</Thickness>
<Thickness x:Key="PivotPortraitThemePadding">12,14,0,13</Thickness>
<FontWeight x:Key="PivotHeaderItemThemeFontWeight">SemiLight</FontWeight>
<FontWeight x:Key="PivotTitleThemeFontWeight">Bold</FontWeight>
<StaticResource x:Key="PivotBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBackground" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="PivotNextButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="PivotNextButtonBackgroundPressed" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrushPointerOver" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonBorderBrushPressed" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotNextButtonForeground" ResourceKey="SystemControlForegroundAltMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotNextButtonForegroundPressed" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonBackground" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="PivotPreviousButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="PivotPreviousButtonBackgroundPressed" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrushPointerOver" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonBorderBrushPressed" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="PivotPreviousButtonForeground" ResourceKey="SystemControlForegroundAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<StaticResource x:Key="PivotPreviousButtonForegroundPressed" ResourceKey="SystemControlHighlightAltAltMediumHighBrush" />
<!-- Resources for PivotItem -->
<StaticResource x:Key="PivotItemBackground" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for PivotHeaderItem -->
<StaticResource x:Key="PivotHeaderItemBackgroundUnselected" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundUnselectedPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundUnselectedPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelected" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelectedPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundSelectedPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselected" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselectedPointerOver" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundUnselectedPressed" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelected" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelectedPointerOver" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundSelectedPressed" ResourceKey="SystemControlHighlightAltBaseMediumHighBrush" />
<StaticResource x:Key="PivotHeaderItemForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="PivotHeaderItemFocusPipeFill" ResourceKey="SystemControlHighlightAltAccentBrush" />
<StaticResource x:Key="PivotHeaderItemSelectedPipeFill" ResourceKey="SystemControlHighlightAltAccentBrush" />
<SolidColorBrush x:Key="PivotForegroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="PivotHeaderBackgroundSelectedBrush" Color="Transparent" />
<SolidColorBrush x:Key="PivotHeaderBackgroundUnselectedBrush" Color="Transparent" />
<SolidColorBrush x:Key="PivotHeaderForegroundSelectedBrush" Color="#FF000000" />
<SolidColorBrush x:Key="PivotHeaderForegroundUnselectedBrush" Color="#66000000" />
<SolidColorBrush x:Key="PivotNavButtonBackgroundThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="PivotNavButtonBorderThemeBrush" Color="#59D5D5D5" />
<SolidColorBrush x:Key="PivotNavButtonForegroundThemeBrush" Color="#99000000" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverBackgroundThemeBrush" Color="#F0D7D7D7" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverBorderThemeBrush" Color="#9EC1C1C1" />
<SolidColorBrush x:Key="PivotNavButtonPointerOverForegroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="PivotNavButtonPressedBackgroundThemeBrush" Color="#BD292929" />
<SolidColorBrush x:Key="PivotNavButtonPressedBorderThemeBrush" Color="#BD292929" />
<SolidColorBrush x:Key="PivotNavButtonPressedForegroundThemeBrush" Color="#FFFFFFFF" />
</Style.Resources> </Style.Resources>
</Style> </Style>

2
src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj

@ -19,4 +19,4 @@
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
</Project> </Project>

117
src/Avalonia.Themes.Fluent/TabControl.xaml

@ -1,62 +1,61 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui"
<Style Selector="TabControl"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Setter Property="Template"> <Design.PreviewWith>
<ControlTemplate> <Border Width="400">
<Border <TabControl>
Margin="{TemplateBinding Margin}" <TabItem Header="Arch">
BorderBrush="{TemplateBinding BorderBrush}" <Border Background="AntiqueWhite"
BorderThickness="{TemplateBinding BorderThickness}" Height="100">
Background="{TemplateBinding Background}" <TextBlock Text="Content" Foreground="Black" FontSize="20"/>
HorizontalAlignment="{TemplateBinding HorizontalAlignment}" </Border>
VerticalAlignment="{TemplateBinding VerticalAlignment}"> </TabItem>
<DockPanel> <TabItem Header="Leaf">
<ItemsPresenter <Border Background="Green"
Name="PART_ItemsPresenter" Height="100" />
</TabItem>
<TabItem Header="Disabled"
IsEnabled="False" />
</TabControl>
</Border>
</Design.PreviewWith>
<Style Selector="TabControl">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="{DynamicResource PivotItemMargin}" />
<Setter Property="Background" Value="{DynamicResource PivotBackground}" />
<Setter Property="Template">
<ControlTemplate>
<Border Margin="{TemplateBinding Margin}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<DockPanel>
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"> ItemTemplate="{TemplateBinding ItemTemplate}"
</ItemsPresenter> DockPanel.Dock="{TemplateBinding TabStripPlacement}"/>
<ContentPresenter <ContentPresenter Name="PART_SelectedContentHost"
Name="PART_SelectedContentHost" Margin="{TemplateBinding Padding}"
Margin="{TemplateBinding Padding}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" Content="{TemplateBinding SelectedContent}"
Content="{TemplateBinding SelectedContent}" ContentTemplate="{TemplateBinding SelectedContentTemplate}" />
ContentTemplate="{TemplateBinding SelectedContentTemplate}"> </DockPanel>
</ContentPresenter> </Border>
</DockPanel> </ControlTemplate>
</Border> </Setter>
</ControlTemplate> </Style>
</Setter>
</Style> <Style Selector="TabControl[TabStripPlacement=Left] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
<Style Selector="TabControl[TabStripPlacement=Top]"> <Setter Property="Orientation" Value="Vertical" />
<Setter Property="Padding" Value="0 4 0 0"/> </Style>
</Style> <Style Selector="TabControl[TabStripPlacement=Right] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
<Style Selector="TabControl[TabStripPlacement=Top] /template/ ItemsPresenter#PART_ItemsPresenter"> <Setter Property="Orientation" Value="Vertical" />
<Setter Property="DockPanel.Dock" Value="Top"/> </Style>
</Style> <Style Selector="TabControl[TabStripPlacement=Top] /template/ ItemsPresenter#PART_ItemsPresenter">
<Style Selector="TabControl[TabStripPlacement=Bottom] /template/ ItemsPresenter#PART_ItemsPresenter"> <Setter Property="Margin" Value="0 0 0 2" />
<Setter Property="DockPanel.Dock" Value="Bottom"/> </Style>
</Style>
<Style Selector="TabControl[TabStripPlacement=Bottom]">
<Setter Property="Padding" Value="0 0 0 4"/>
</Style>
<Style Selector="TabControl[TabStripPlacement=Left] /template/ ItemsPresenter#PART_ItemsPresenter">
<Setter Property="DockPanel.Dock" Value="Left"/>
</Style>
<Style Selector="TabControl[TabStripPlacement=Left] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
<Setter Property="Orientation" Value="Vertical"/>
</Style>
<Style Selector="TabControl[TabStripPlacement=Left]">
<Setter Property="Padding" Value="4 0 0 0"/>
</Style>
<Style Selector="TabControl[TabStripPlacement=Right] /template/ ItemsPresenter#PART_ItemsPresenter">
<Setter Property="DockPanel.Dock" Value="Right"/>
</Style>
<Style Selector="TabControl[TabStripPlacement=Right] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
<Setter Property="Orientation" Value="Vertical"/>
</Style>
<Style Selector="TabControl[TabStripPlacement=Right]">
<Setter Property="Padding" Value="0 0 4 0"/>
</Style>
</Styles> </Styles>

173
src/Avalonia.Themes.Fluent/TabItem.xaml

@ -1,45 +1,130 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui"
<Style Selector="TabItem"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Setter Property="Background" Value="Transparent"/> <Design.PreviewWith>
<Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/> <Border Padding="20">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLightBrush}"/> <StackPanel Spacing="20">
<Setter Property="HorizontalContentAlignment" Value="Left"/> <TabItem Header="Leaf" />
<Setter Property="Padding" Value="8"/> <TabItem Header="Arch" IsSelected="True" />
<Setter Property="Template"> </StackPanel>
<ControlTemplate> </Border>
<ContentPresenter </Design.PreviewWith>
Name="PART_ContentPresenter" <Styles.Resources>
Background="{TemplateBinding Background}" <x:Double x:Key="TabItemMinHeight">48</x:Double>
BorderBrush="{TemplateBinding BorderBrush}" <x:Double x:Key="TabItemVerticalPipeHeight">24</x:Double>
BorderThickness="{TemplateBinding BorderThickness}" <x:Double x:Key="TabItemPipeThickness">2</x:Double>
ContentTemplate="{TemplateBinding HeaderTemplate}" </Styles.Resources>
Content="{TemplateBinding Header}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" <Style Selector="TabItem">
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" <Setter Property="FontSize" Value="{DynamicResource PivotHeaderItemFontSize}" />
Margin="{TemplateBinding Margin}" <Setter Property="FontWeight" Value="{DynamicResource PivotHeaderItemThemeFontWeight}" />
Padding="{TemplateBinding Padding}"/> <Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundUnselected}" />
</ControlTemplate> <Setter Property="Foreground" Value="{DynamicResource PivotHeaderItemForegroundUnselected}" />
</Setter> <Setter Property="Padding" Value="{DynamicResource PivotHeaderItemMargin}" />
</Style> <Setter Property="Margin" Value="0" />
<Style Selector="TabItem:disabled"> <Setter Property="MinHeight" Value="{DynamicResource TabItemMinHeight}" />
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/> <Setter Property="VerticalContentAlignment" Value="Center" />
</Style> <Setter Property="Template">
<Style Selector="TabItem:pointerover"> <ControlTemplate>
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/> <Border Name="PART_LayoutRoot"
</Style> BorderBrush="{TemplateBinding BorderBrush}"
<Style Selector="TabItem:selected"> BorderThickness="{TemplateBinding BorderThickness}"
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/> Padding="{TemplateBinding Padding}">
</Style> <Panel>
<Style Selector="TabItem:selected:focus"> <ContentPresenter Name="PART_ContentPresenter"
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/> ContentTemplate="{TemplateBinding HeaderTemplate}"
</Style> Content="{TemplateBinding Header}"
<Style Selector="TabItem:selected:pointerover"> HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/> VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
</Style> TextBlock.FontFamily="{TemplateBinding FontFamily}"
<Style Selector="TabItem:selected:focus:pointerover"> TextBlock.FontSize="{TemplateBinding FontSize}"
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/> TextBlock.FontWeight="{TemplateBinding FontWeight}" />
</Style> <Border Name="PART_SelectedPipe"
<Style Selector="TabItem[TabStripPlacement=Right]"> CornerRadius="{DynamicResource ControlCornerRadius}"
<Setter Property="HorizontalContentAlignment" Value="Right"/> Background="{DynamicResource PivotHeaderItemSelectedPipeFill}" />
</Style> </Panel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<!-- Nornal state -->
<Style Selector="TabItem /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{Binding $parent[TabItem].Background}" />
</Style>
<Style Selector="TabItem /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="False" />
</Style>
<!-- Selected state -->
<!-- We don't use selector to PART_LayoutRoot, so developer can override selected item background with TabStripItem.Background -->
<Style Selector="TabItem:selected">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundSelected}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundSelected}" />
</Style>
<Style Selector="TabItem:selected /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="True" />
</Style>
<!-- PointerOver state -->
<Style Selector="TabItem:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundUnselectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundUnselectedPointerOver}" />
</Style>
<!-- Selected PointerOver state -->
<Style Selector="TabItem:selected:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundSelectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundSelectedPointerOver}" />
</Style>
<!-- Pressed state -->
<Style Selector="TabItem:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundUnselectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundUnselectedPressed}" />
</Style>
<!-- Selected Pressed state -->
<Style Selector="TabItem:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundSelectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundSelectedPressed}" />
</Style>
<!-- Disabled state -->
<Style Selector="TabItem:disabled /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundDisabled}" />
</Style>
<!-- TabStripPlacement States Group -->
<Style Selector="TabItem[TabStripPlacement=Left] /template/ Border#PART_SelectedPipe">
<Setter Property="Width" Value="{StaticResource TabItemPipeThickness}" />
<Setter Property="Height" Value="{StaticResource TabItemVerticalPipeHeight}" />
<Setter Property="Margin" Value="0,0,2,0" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="TabItem[TabStripPlacement=Left] /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Margin" Value="8,0,0,0" />
</Style>
<Style Selector="TabItem[TabStripPlacement=Top] /template/ Border#PART_SelectedPipe, TabItem[TabStripPlacement=Bottom] /template/ Border#PART_SelectedPipe">
<Setter Property="Height" Value="{StaticResource TabItemPipeThickness}" />
<Setter Property="Margin" Value="0,0,0,2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Bottom" />
</Style>
<Style Selector="TabItem[TabStripPlacement=Right] /template/ Border#PART_SelectedPipe">
<Setter Property="Width" Value="{StaticResource TabItemPipeThickness}" />
<Setter Property="Height" Value="{StaticResource TabItemVerticalPipeHeight}" />
<Setter Property="Margin" Value="2,0,0,0" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="TabItem[TabStripPlacement=Right] /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Margin" Value="0,0,8,0" />
</Style>
<Style Selector="TabItem[TabStripPlacement=Right]">
<Setter Property="HorizontalContentAlignment" Value="Right" />
</Style>
</Styles> </Styles>

14
src/Avalonia.Themes.Fluent/TabStrip.xaml

@ -1,11 +1,20 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui">
<Design.PreviewWith>
<Border Padding="20">
<TabStrip>
<TabStripItem>Item 1</TabStripItem>
<TabStripItem>Item 2</TabStripItem>
<TabStripItem IsEnabled="False">Disabled</TabStripItem>
</TabStrip>
</Border>
</Design.PreviewWith>
<Style Selector="TabStrip"> <Style Selector="TabStrip">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"/> ItemTemplate="{TemplateBinding ItemTemplate}" />
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Setter Property="ItemsPanel"> <Setter Property="ItemsPanel">
@ -14,7 +23,4 @@
</ItemsPanelTemplate> </ItemsPanelTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="TabStrip > TabStripItem">
<Setter Property="Margin" Value="16"/>
</Style>
</Styles> </Styles>

110
src/Avalonia.Themes.Fluent/TabStripItem.xaml

@ -1,23 +1,103 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="20">
<TabStripItem>Leaf</TabStripItem>
<TabStripItem IsSelected="True">Arch</TabStripItem>
</StackPanel>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<x:Double x:Key="TabStripItemMinHeight">48</x:Double>
<x:Double x:Key="TabStripItemPipeThickness">2</x:Double>
</Styles.Resources>
<Style Selector="TabStripItem"> <Style Selector="TabStripItem">
<Setter Property="Background" Value="Transparent"/> <Setter Property="FontSize" Value="{DynamicResource PivotHeaderItemFontSize}" />
<Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/> <Setter Property="FontWeight" Value="{DynamicResource PivotHeaderItemThemeFontWeight}" />
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLowBrush}"/> <Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundUnselected}" />
<Setter Property="Foreground" Value="{DynamicResource PivotHeaderItemForegroundUnselected}" />
<Setter Property="Padding" Value="{DynamicResource PivotHeaderItemMargin}" />
<Setter Property="Margin" Value="0" />
<Setter Property="MinHeight" Value="{DynamicResource TabStripItemMinHeight}" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter" <Border Name="PART_LayoutRoot"
Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
ContentTemplate="{TemplateBinding ContentTemplate}" <Panel>
Content="{TemplateBinding Content}" <ContentPresenter Name="PART_ContentPresenter"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" ContentTemplate="{TemplateBinding ContentTemplate}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/> HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
TextBlock.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" />
<Border Name="PART_SelectedPipe"
CornerRadius="{DynamicResource ControlCornerRadius}"
Background="{DynamicResource PivotHeaderItemSelectedPipeFill}" />
</Panel>
</Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<!-- Nornal state -->
<Style Selector="TabStripItem /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{Binding $parent[TabStripItem].Background}" />
</Style>
<Style Selector="TabStripItem /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="TabStripItem /template/ Border#PART_SelectedPipe, TabItem[TabStripPlacement=Bottom] /template/ Border#PART_SelectedPipe">
<Setter Property="Height" Value="{StaticResource TabStripItemPipeThickness}" />
<Setter Property="Margin" Value="0,0,0,2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Bottom" />
</Style>
<!-- Selected state -->
<!-- We don't use selector to PART_LayoutRoot, so developer can override selected item background with TabStripItem.Background -->
<Style Selector="TabStripItem:selected"> <Style Selector="TabStripItem:selected">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/> <Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundSelected}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundSelected}" />
</Style>
<Style Selector="TabStripItem:selected /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="True" />
</Style>
<!-- PointerOver state -->
<Style Selector="TabStripItem:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundUnselectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundUnselectedPointerOver}" />
</Style>
<!-- Selected PointerOver state -->
<Style Selector="TabStripItem:selected:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundSelectedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundSelectedPointerOver}" />
</Style>
<!-- Pressed state -->
<Style Selector="TabStripItem:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundUnselectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundUnselectedPressed}" />
</Style>
<!-- Selected Pressed state -->
<Style Selector="TabStripItem:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundSelectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundSelectedPressed}" />
</Style>
<!-- Disabled state -->
<Style Selector="TabStripItem:disabled /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource PivotHeaderItemBackgroundDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource PivotHeaderItemForegroundDisabled}" />
</Style> </Style>
</Styles> </Styles>

16
src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs

@ -1,6 +1,8 @@
using System; using System;
using System.Reactive.Disposables;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Transformation;
namespace Avalonia.Animation.Animators namespace Avalonia.Animation.Animators
{ {
@ -19,6 +21,12 @@ namespace Avalonia.Animation.Animators
// Check if the Target Property is Transform derived. // Check if the Target Property is Transform derived.
if (typeof(Transform).IsAssignableFrom(Property.OwnerType)) if (typeof(Transform).IsAssignableFrom(Property.OwnerType))
{ {
if (ctrl.RenderTransform is TransformOperations)
{
// HACK: This animator cannot reasonably animate CSS transforms at the moment.
return Disposable.Empty;
}
if (ctrl.RenderTransform == null) if (ctrl.RenderTransform == null)
{ {
var normalTransform = new TransformGroup(); var normalTransform = new TransformGroup();
@ -51,7 +59,7 @@ namespace Avalonia.Animation.Animators
// It's a transform object so let's target that. // It's a transform object so let's target that.
if (renderTransformType == Property.OwnerType) if (renderTransformType == Property.OwnerType)
{ {
return _doubleAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete); return _doubleAnimator.Apply(animation, (Transform) ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete);
} }
// It's a TransformGroup and try finding the target there. // It's a TransformGroup and try finding the target there.
else if (renderTransformType == typeof(TransformGroup)) else if (renderTransformType == typeof(TransformGroup))
@ -65,15 +73,13 @@ namespace Avalonia.Animation.Animators
} }
} }
Logger.TryGet(LogEventLevel.Warning)?.Log( Logger.TryGet(LogEventLevel.Warning, LogArea.Animations)?.Log(
LogArea.Animations,
control, control,
$"Cannot find the appropriate transform: \"{Property.OwnerType}\" in {control}."); $"Cannot find the appropriate transform: \"{Property.OwnerType}\" in {control}.");
} }
else else
{ {
Logger.TryGet(LogEventLevel.Error)?.Log( Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log(
LogArea.Animations,
control, control,
$"Cannot apply animation: Target property owner {Property.OwnerType} is not a Transform object."); $"Cannot apply animation: Target property owner {Property.OwnerType} is not a Transform object.");
} }

35
src/Avalonia.Visuals/Animation/Animators/TransformOperationsAnimator.cs

@ -0,0 +1,35 @@
using System;
using Avalonia.Media;
using Avalonia.Media.Transformation;
namespace Avalonia.Animation.Animators
{
public class TransformOperationsAnimator : Animator<TransformOperations>
{
public TransformOperationsAnimator()
{
Validate = ValidateTransform;
}
public override TransformOperations Interpolate(double progress, TransformOperations oldValue, TransformOperations newValue)
{
var oldTransform = EnsureOperations(oldValue);
var newTransform = EnsureOperations(newValue);
return TransformOperations.Interpolate(oldTransform, newTransform, progress);
}
internal static TransformOperations EnsureOperations(ITransform value)
{
return value as TransformOperations ?? TransformOperations.Identity;
}
private void ValidateTransform(AnimatorKeyFrame kf)
{
if (!(kf.Value is TransformOperations))
{
throw new InvalidOperationException($"All keyframes must be of type {typeof(TransformOperations)}.");
}
}
}
}

28
src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs

@ -0,0 +1,28 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Media;
namespace Avalonia.Animation
{
public class TransformOperationsTransition : Transition<ITransform>
{
private static readonly TransformOperationsAnimator _operationsAnimator = new TransformOperationsAnimator();
public override IObservable<ITransform> DoTransition(IObservable<double> progress,
ITransform oldValue,
ITransform newValue)
{
var oldTransform = TransformOperationsAnimator.EnsureOperations(oldValue);
var newTransform = TransformOperationsAnimator.EnsureOperations(newValue);
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return _operationsAnimator.Interpolate(f, oldTransform, newTransform);
});
}
}
}

75
src/Avalonia.Visuals/Matrix.cs

@ -54,7 +54,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// HasInverse Property - returns true if this matrix is invertible, false otherwise. /// HasInverse Property - returns true if this matrix is invertible, false otherwise.
/// </summary> /// </summary>
public bool HasInverse => GetDeterminant() != 0; public bool HasInverse => !MathUtilities.IsZero(GetDeterminant());
/// <summary> /// <summary>
/// The first element of the first row /// The first element of the first row
@ -286,7 +286,7 @@ namespace Avalonia
{ {
double d = GetDeterminant(); double d = GetDeterminant();
if (d == 0) if (MathUtilities.IsZero(d))
{ {
throw new InvalidOperationException("Transform is not invertible."); throw new InvalidOperationException("Transform is not invertible.");
} }
@ -319,5 +319,76 @@ namespace Avalonia
); );
} }
} }
/// <summary>
/// Decomposes given matrix into transform operations.
/// </summary>
/// <param name="matrix">Matrix to decompose.</param>
/// <param name="decomposed">Decomposed matrix.</param>
/// <returns>The status of the operation.</returns>
public static bool TryDecomposeTransform(Matrix matrix, out Decomposed decomposed)
{
decomposed = default;
var determinant = matrix.GetDeterminant();
if (MathUtilities.IsZero(determinant))
{
return false;
}
var m11 = matrix.M11;
var m21 = matrix.M21;
var m12 = matrix.M12;
var m22 = matrix.M22;
// Translation.
decomposed.Translate = new Vector(matrix.M31, matrix.M32);
// Scale sign.
var scaleX = 1d;
var scaleY = 1d;
if (determinant < 0)
{
if (m11 < m22)
{
scaleX *= -1d;
}
else
{
scaleY *= -1d;
}
}
// X Scale.
scaleX *= Math.Sqrt(m11 * m11 + m12 * m12);
m11 /= scaleX;
m12 /= scaleX;
// XY Shear.
double scaledShear = m11 * m21 + m12 * m22;
m21 -= m11 * scaledShear;
m22 -= m12 * scaledShear;
// Y Scale.
scaleY *= Math.Sqrt(m21 * m21 + m22 * m22);
decomposed.Scale = new Vector(scaleX, scaleY);
decomposed.Skew = new Vector(scaledShear / scaleY, 0d);
decomposed.Angle = Math.Atan2(m12, m11);
return true;
}
public struct Decomposed
{
public Vector Translate;
public Vector Scale;
public Vector Skew;
public double Angle;
}
} }
} }

128
src/Avalonia.Visuals/Media/Color.cs

@ -89,33 +89,64 @@ namespace Avalonia.Media
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
public static Color Parse(string s) public static Color Parse(string s)
{ {
if (s == null) throw new ArgumentNullException(nameof(s)); if (TryParse(s, out Color color))
if (s.Length == 0) throw new FormatException(); {
return color;
}
if (s[0] == '#') throw new FormatException($"Invalid color string: '{s}'.");
}
/// <summary>
/// Parses a color string.
/// </summary>
/// <param name="s">The color string.</param>
/// <returns>The <see cref="Color"/>.</returns>
public static Color Parse(ReadOnlySpan<char> s)
{
if (TryParse(s, out Color color))
{ {
var or = 0u; return color;
}
if (s.Length == 7) throw new FormatException($"Invalid color string: '{s.ToString()}'.");
{ }
or = 0xff000000;
}
else if (s.Length != 9)
{
throw new FormatException($"Invalid color string: '{s}'.");
}
return FromUInt32(uint.Parse(s.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture) | or); /// <summary>
/// Parses a color string.
/// </summary>
/// <param name="s">The color string.</param>
/// <param name="color">The parsed color</param>
/// <returns>The status of the operation.</returns>
public static bool TryParse(string s, out Color color)
{
if (s == null)
{
throw new ArgumentNullException(nameof(s));
}
if (s.Length == 0)
{
throw new FormatException();
}
if (s[0] == '#' && TryParseInternal(s.AsSpan(), out color))
{
return true;
} }
var knownColor = KnownColors.GetKnownColor(s); var knownColor = KnownColors.GetKnownColor(s);
if (knownColor != KnownColor.None) if (knownColor != KnownColor.None)
{ {
return knownColor.ToColor(); color = knownColor.ToColor();
return true;
} }
throw new FormatException($"Invalid color string: '{s}'."); color = default;
return false;
} }
/// <summary> /// <summary>
@ -126,40 +157,79 @@ namespace Avalonia.Media
/// <returns>The status of the operation.</returns> /// <returns>The status of the operation.</returns>
public static bool TryParse(ReadOnlySpan<char> s, out Color color) public static bool TryParse(ReadOnlySpan<char> s, out Color color)
{ {
color = default;
if (s == null)
return false;
if (s.Length == 0) if (s.Length == 0)
{
color = default;
return false; return false;
}
if (s[0] == '#') if (s[0] == '#')
{ {
var or = 0u; return TryParseInternal(s, out color);
}
var knownColor = KnownColors.GetKnownColor(s.ToString());
if (knownColor != KnownColor.None)
{
color = knownColor.ToColor();
return true;
}
color = default;
return false;
}
private static bool TryParseInternal(ReadOnlySpan<char> s, out Color color)
{
static bool TryParseCore(ReadOnlySpan<char> input, ref Color color)
{
var alphaComponent = 0u;
if (s.Length == 7) if (input.Length == 6)
{ {
or = 0xff000000; alphaComponent = 0xff000000;
} }
else if (s.Length != 9) else if (input.Length != 8)
{ {
return false; return false;
} }
if(!uint.TryParse(s.Slice(1).ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed)) // TODO: (netstandard 2.1) Can use allocation free parsing.
if (!uint.TryParse(input.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out var parsed))
{
return false; return false;
color = FromUInt32(parsed| or); }
color = FromUInt32(parsed | alphaComponent);
return true; return true;
} }
var knownColor = KnownColors.GetKnownColor(s.ToString()); color = default;
if (knownColor != KnownColor.None) ReadOnlySpan<char> input = s.Slice(1);
// Handle shorthand cases like #FFF (RGB) or #FFFF (ARGB).
if (input.Length == 3 || input.Length == 4)
{ {
color = knownColor.ToColor(); var extendedLength = 2 * input.Length;
return true; Span<char> extended = stackalloc char[extendedLength];
for (int i = 0; i < input.Length; i++)
{
extended[2 * i + 0] = input[i];
extended[2 * i + 1] = input[i];
}
return TryParseCore(extended, ref color);
} }
return false; return TryParseCore(input, ref color);
} }
/// <summary> /// <summary>

6
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -283,6 +283,12 @@ namespace Avalonia.Media
} }
public PushedState PushClip(RoundedRect clip)
{
PlatformImpl.PushClip(clip);
return new PushedState(this, PushedState.PushedStateType.Clip);
}
/// <summary> /// <summary>
/// Pushes a clip rectangle. /// Pushes a clip rectangle.
/// </summary> /// </summary>

12
src/Avalonia.Visuals/Media/IMutableTransform.cs

@ -0,0 +1,12 @@
using System;
namespace Avalonia.Media
{
public interface IMutableTransform : ITransform
{
/// <summary>
/// Raised when the transform changes.
/// </summary>
event EventHandler Changed;
}
}

10
src/Avalonia.Visuals/Media/ITransform.cs

@ -0,0 +1,10 @@
using System.ComponentModel;
namespace Avalonia.Media
{
[TypeConverter(typeof(TransformConverter))]
public interface ITransform
{
Matrix Value { get; }
}
}

5
src/Avalonia.Visuals/Media/Transform.cs

@ -8,11 +8,12 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Represents a transform on an <see cref="IVisual"/>. /// Represents a transform on an <see cref="IVisual"/>.
/// </summary> /// </summary>
public abstract class Transform : Animatable public abstract class Transform : Animatable, IMutableTransform
{ {
static Transform() static Transform()
{ {
Animation.Animation.RegisterAnimator<TransformAnimator>(prop => typeof(Transform).IsAssignableFrom(prop.OwnerType)); Animation.Animation.RegisterAnimator<TransformAnimator>(prop =>
typeof(ITransform).IsAssignableFrom(prop.OwnerType));
} }
/// <summary> /// <summary>

23
src/Avalonia.Visuals/Media/TransformConverter.cs

@ -0,0 +1,23 @@
using System;
using System.ComponentModel;
using System.Globalization;
using Avalonia.Media.Transformation;
namespace Avalonia.Media
{
/// <summary>
/// Creates an <see cref="ITransform"/> from a string representation.
/// </summary>
public class TransformConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return TransformOperations.Parse((string)value);
}
}
}

40
src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs

@ -0,0 +1,40 @@
namespace Avalonia.Media.Transformation
{
internal static class InterpolationUtilities
{
public static double InterpolateScalars(double from, double to, double progress)
{
return from * (1d - progress) + to * progress;
}
public static Vector InterpolateVectors(Vector from, Vector to, double progress)
{
var x = InterpolateScalars(from.X, to.X, progress);
var y = InterpolateScalars(from.Y, to.Y, progress);
return new Vector(x, y);
}
public static Matrix ComposeTransform(Matrix.Decomposed decomposed)
{
// According to https://www.w3.org/TR/css-transforms-1/#recomposing-to-a-2d-matrix
return Matrix.CreateTranslation(decomposed.Translate) *
Matrix.CreateRotation(decomposed.Angle) *
Matrix.CreateSkew(decomposed.Skew.X, decomposed.Skew.Y) *
Matrix.CreateScale(decomposed.Scale);
}
public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progres)
{
Matrix.Decomposed result = default;
result.Translate = InterpolateVectors(from.Translate, to.Translate, progres);
result.Scale = InterpolateVectors(from.Scale, to.Scale, progres);
result.Skew = InterpolateVectors(from.Skew, to.Skew, progres);
result.Angle = InterpolateScalars(from.Angle, to.Angle, progres);
return result;
}
}
}

230
src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs

@ -0,0 +1,230 @@
using System.Runtime.InteropServices;
namespace Avalonia.Media.Transformation
{
/// <summary>
/// Represents a single primitive transform (like translation, rotation, scale, etc.).
/// </summary>
public struct TransformOperation
{
public OperationType Type;
public Matrix Matrix;
public DataLayout Data;
public enum OperationType
{
Translate,
Rotate,
Scale,
Skew,
Matrix,
Identity
}
/// <summary>
/// Returns whether operation produces the identity matrix.
/// </summary>
public bool IsIdentity => Matrix.IsIdentity;
/// <summary>
/// Bakes this operation to a transform matrix.
/// </summary>
public void Bake()
{
Matrix = Matrix.Identity;
switch (Type)
{
case OperationType.Translate:
{
Matrix = Matrix.CreateTranslation(Data.Translate.X, Data.Translate.Y);
break;
}
case OperationType.Rotate:
{
Matrix = Matrix.CreateRotation(Data.Rotate.Angle);
break;
}
case OperationType.Scale:
{
Matrix = Matrix.CreateScale(Data.Scale.X, Data.Scale.Y);
break;
}
case OperationType.Skew:
{
Matrix = Matrix.CreateSkew(Data.Skew.X, Data.Skew.Y);
break;
}
}
}
/// <summary>
/// Returns new identity transform operation.
/// </summary>
public static TransformOperation Identity =>
new TransformOperation { Matrix = Matrix.Identity, Type = OperationType.Identity };
/// <summary>
/// Attempts to interpolate between two transform operations.
/// </summary>
/// <param name="from">Source operation.</param>
/// <param name="to">Target operation.</param>
/// <param name="progress">Interpolation progress.</param>
/// <param name="result">Interpolation result that will be filled in when operation was successful.</param>
/// <remarks>
/// Based upon https://www.w3.org/TR/css-transforms-1/#interpolation-of-transform-functions.
/// </remarks>
public static bool TryInterpolate(TransformOperation? from, TransformOperation? to, double progress,
ref TransformOperation result)
{
bool fromIdentity = IsOperationIdentity(ref from);
bool toIdentity = IsOperationIdentity(ref to);
if (fromIdentity && toIdentity)
{
return true;
}
// ReSharper disable PossibleInvalidOperationException
TransformOperation fromValue = fromIdentity ? Identity : from.Value;
TransformOperation toValue = toIdentity ? Identity : to.Value;
// ReSharper restore PossibleInvalidOperationException
var interpolationType = toIdentity ? fromValue.Type : toValue.Type;
result.Type = interpolationType;
switch (interpolationType)
{
case OperationType.Translate:
{
double fromX = fromIdentity ? 0 : fromValue.Data.Translate.X;
double fromY = fromIdentity ? 0 : fromValue.Data.Translate.Y;
double toX = toIdentity ? 0 : toValue.Data.Translate.X;
double toY = toIdentity ? 0 : toValue.Data.Translate.Y;
result.Data.Translate.X = InterpolationUtilities.InterpolateScalars(fromX, toX, progress);
result.Data.Translate.Y = InterpolationUtilities.InterpolateScalars(fromY, toY, progress);
result.Bake();
break;
}
case OperationType.Rotate:
{
double fromAngle = fromIdentity ? 0 : fromValue.Data.Rotate.Angle;
double toAngle = toIdentity ? 0 : toValue.Data.Rotate.Angle;
result.Data.Rotate.Angle = InterpolationUtilities.InterpolateScalars(fromAngle, toAngle, progress);
result.Bake();
break;
}
case OperationType.Scale:
{
double fromX = fromIdentity ? 1 : fromValue.Data.Scale.X;
double fromY = fromIdentity ? 1 : fromValue.Data.Scale.Y;
double toX = toIdentity ? 1 : toValue.Data.Scale.X;
double toY = toIdentity ? 1 : toValue.Data.Scale.Y;
result.Data.Scale.X = InterpolationUtilities.InterpolateScalars(fromX, toX, progress);
result.Data.Scale.Y = InterpolationUtilities.InterpolateScalars(fromY, toY, progress);
result.Bake();
break;
}
case OperationType.Skew:
{
double fromX = fromIdentity ? 0 : fromValue.Data.Skew.X;
double fromY = fromIdentity ? 0 : fromValue.Data.Skew.Y;
double toX = toIdentity ? 0 : toValue.Data.Skew.X;
double toY = toIdentity ? 0 : toValue.Data.Skew.Y;
result.Data.Skew.X = InterpolationUtilities.InterpolateScalars(fromX, toX, progress);
result.Data.Skew.Y = InterpolationUtilities.InterpolateScalars(fromY, toY, progress);
result.Bake();
break;
}
case OperationType.Matrix:
{
var fromMatrix = fromIdentity ? Matrix.Identity : fromValue.Matrix;
var toMatrix = toIdentity ? Matrix.Identity : toValue.Matrix;
if (!Matrix.TryDecomposeTransform(fromMatrix, out Matrix.Decomposed fromDecomposed) ||
!Matrix.TryDecomposeTransform(toMatrix, out Matrix.Decomposed toDecomposed))
{
return false;
}
var interpolated =
InterpolationUtilities.InterpolateDecomposedTransforms(
ref fromDecomposed, ref toDecomposed,
progress);
result.Matrix = InterpolationUtilities.ComposeTransform(interpolated);
break;
}
case OperationType.Identity:
{
// Do nothing.
break;
}
}
return true;
}
private static bool IsOperationIdentity(ref TransformOperation? operation)
{
return !operation.HasValue || operation.Value.IsIdentity;
}
[StructLayout(LayoutKind.Explicit)]
public struct DataLayout
{
[FieldOffset(0)] public SkewLayout Skew;
[FieldOffset(0)] public ScaleLayout Scale;
[FieldOffset(0)] public TranslateLayout Translate;
[FieldOffset(0)] public RotateLayout Rotate;
public struct SkewLayout
{
public double X;
public double Y;
}
public struct ScaleLayout
{
public double X;
public double Y;
}
public struct TranslateLayout
{
public double X;
public double Y;
}
public struct RotateLayout
{
public double Angle;
}
}
}
}

258
src/Avalonia.Visuals/Media/Transformation/TransformOperations.cs

@ -0,0 +1,258 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Media.Transformation
{
/// <summary>
/// Contains a list of <see cref="TransformOperation"/> that represent primitive transforms that will be
/// applied in declared order.
/// </summary>
public sealed class TransformOperations : ITransform
{
public static TransformOperations Identity { get; } = new TransformOperations(new List<TransformOperation>());
private readonly List<TransformOperation> _operations;
private TransformOperations(List<TransformOperation> operations)
{
_operations = operations ?? throw new ArgumentNullException(nameof(operations));
IsIdentity = CheckIsIdentity();
Value = ApplyTransforms();
}
/// <summary>
/// Returns whether all operations combined together produce the identity matrix.
/// </summary>
public bool IsIdentity { get; }
public IReadOnlyList<TransformOperation> Operations => _operations;
public Matrix Value { get; }
public static TransformOperations Parse(string s)
{
return TransformParser.Parse(s);
}
public static Builder CreateBuilder(int capacity)
{
return new Builder(capacity);
}
public static TransformOperations Interpolate(TransformOperations from, TransformOperations to, double progress)
{
TransformOperations result = Identity;
if (!TryInterpolate(from, to, progress, ref result))
{
// If the matrices cannot be interpolated, fallback to discrete animation logic.
// See https://drafts.csswg.org/css-transforms/#matrix-interpolation
result = progress < 0.5 ? from : to;
}
return result;
}
private Matrix ApplyTransforms(int startOffset = 0)
{
Matrix matrix = Matrix.Identity;
for (var i = startOffset; i < _operations.Count; i++)
{
TransformOperation operation = _operations[i];
matrix *= operation.Matrix;
}
return matrix;
}
private bool CheckIsIdentity()
{
foreach (TransformOperation operation in _operations)
{
if (!operation.IsIdentity)
{
return false;
}
}
return true;
}
private static bool TryInterpolate(TransformOperations from, TransformOperations to, double progress, ref TransformOperations result)
{
bool fromIdentity = from.IsIdentity;
bool toIdentity = to.IsIdentity;
if (fromIdentity && toIdentity)
{
return true;
}
int matchingPrefixLength = ComputeMatchingPrefixLength(from, to);
int fromSize = fromIdentity ? 0 : from._operations.Count;
int toSize = toIdentity ? 0 : to._operations.Count;
int numOperations = Math.Max(fromSize, toSize);
var builder = new Builder(matchingPrefixLength);
for (int i = 0; i < matchingPrefixLength; i++)
{
TransformOperation interpolated = new TransformOperation
{
Type = TransformOperation.OperationType.Identity
};
if (!TransformOperation.TryInterpolate(
i >= fromSize ? default(TransformOperation?) : from._operations[i],
i >= toSize ? default(TransformOperation?) : to._operations[i],
progress,
ref interpolated))
{
return false;
}
builder.Append(interpolated);
}
if (matchingPrefixLength < numOperations)
{
if (!ComputeDecomposedTransform(from, matchingPrefixLength, out Matrix.Decomposed fromDecomposed) ||
!ComputeDecomposedTransform(to, matchingPrefixLength, out Matrix.Decomposed toDecomposed))
{
return false;
}
var transform = InterpolationUtilities.InterpolateDecomposedTransforms(ref fromDecomposed, ref toDecomposed, progress);
builder.AppendMatrix(InterpolationUtilities.ComposeTransform(transform));
}
result = builder.Build();
return true;
}
private static bool ComputeDecomposedTransform(TransformOperations operations, int startOffset, out Matrix.Decomposed decomposed)
{
Matrix transform = operations.ApplyTransforms(startOffset);
if (!Matrix.TryDecomposeTransform(transform, out decomposed))
{
return false;
}
return true;
}
private static int ComputeMatchingPrefixLength(TransformOperations from, TransformOperations to)
{
int numOperations = Math.Min(from._operations.Count, to._operations.Count);
for (int i = 0; i < numOperations; i++)
{
if (from._operations[i].Type != to._operations[i].Type)
{
return i;
}
}
// If the operations match to the length of the shorter list, then pad its
// length with the matching identity operations.
// https://drafts.csswg.org/css-transforms/#transform-function-lists
return Math.Max(from._operations.Count, to._operations.Count);
}
public readonly struct Builder
{
private readonly List<TransformOperation> _operations;
public Builder(int capacity)
{
_operations = new List<TransformOperation>(capacity);
}
public void AppendTranslate(double x, double y)
{
var toAdd = new TransformOperation();
toAdd.Type = TransformOperation.OperationType.Translate;
toAdd.Data.Translate.X = x;
toAdd.Data.Translate.Y = y;
toAdd.Bake();
_operations.Add(toAdd);
}
public void AppendRotate(double angle)
{
var toAdd = new TransformOperation();
toAdd.Type = TransformOperation.OperationType.Rotate;
toAdd.Data.Rotate.Angle = angle;
toAdd.Bake();
_operations.Add(toAdd);
}
public void AppendScale(double x, double y)
{
var toAdd = new TransformOperation();
toAdd.Type = TransformOperation.OperationType.Scale;
toAdd.Data.Scale.X = x;
toAdd.Data.Scale.Y = y;
toAdd.Bake();
_operations.Add(toAdd);
}
public void AppendSkew(double x, double y)
{
var toAdd = new TransformOperation();
toAdd.Type = TransformOperation.OperationType.Skew;
toAdd.Data.Skew.X = x;
toAdd.Data.Skew.Y = y;
toAdd.Bake();
_operations.Add(toAdd);
}
public void AppendMatrix(Matrix matrix)
{
var toAdd = new TransformOperation();
toAdd.Type = TransformOperation.OperationType.Matrix;
toAdd.Matrix = matrix;
_operations.Add(toAdd);
}
public void AppendIdentity()
{
var toAdd = new TransformOperation();
toAdd.Type = TransformOperation.OperationType.Identity;
_operations.Add(toAdd);
}
public void Append(TransformOperation toAdd)
{
_operations.Add(toAdd);
}
public TransformOperations Build()
{
return new TransformOperations(_operations);
}
}
}
}

444
src/Avalonia.Visuals/Media/Transformation/TransformParser.cs

@ -0,0 +1,444 @@
using System;
using System.Globalization;
using Avalonia.Utilities;
namespace Avalonia.Media.Transformation
{
public static class TransformParser
{
private static readonly (string, TransformFunction)[] s_functionMapping =
{
("translate", TransformFunction.Translate),
("translateX", TransformFunction.TranslateX),
("translateY", TransformFunction.TranslateY),
("scale", TransformFunction.Scale),
("scaleX", TransformFunction.ScaleX),
("scaleY", TransformFunction.ScaleY),
("skew", TransformFunction.Skew),
("skewX", TransformFunction.SkewX),
("skewY", TransformFunction.SkewY),
("rotate", TransformFunction.Rotate),
("matrix", TransformFunction.Matrix)
};
private static readonly (string, Unit)[] s_unitMapping =
{
("deg", Unit.Degree),
("grad", Unit.Gradian),
("rad", Unit.Radian),
("turn", Unit.Turn),
("px", Unit.Pixel)
};
public static TransformOperations Parse(string s)
{
void ThrowInvalidFormat()
{
throw new FormatException($"Invalid transform string: '{s}'.");
}
if (string.IsNullOrEmpty(s))
{
throw new ArgumentException(nameof(s));
}
var span = s.AsSpan().Trim();
if (span.Equals("none".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return TransformOperations.Identity;
}
var builder = TransformOperations.CreateBuilder(0);
while (true)
{
var beginIndex = span.IndexOf('(');
var endIndex = span.IndexOf(')');
if (beginIndex == -1 || endIndex == -1)
{
ThrowInvalidFormat();
}
var namePart = span.Slice(0, beginIndex).Trim();
var function = ParseTransformFunction(in namePart);
if (function == TransformFunction.Invalid)
{
ThrowInvalidFormat();
}
var valuePart = span.Slice(beginIndex + 1, endIndex - beginIndex - 1).Trim();
ParseFunction(in valuePart, function, in builder);
span = span.Slice(endIndex + 1);
if (span.IsWhiteSpace())
{
break;
}
}
return builder.Build();
}
private static void ParseFunction(
in ReadOnlySpan<char> functionPart,
TransformFunction function,
in TransformOperations.Builder builder)
{
static UnitValue ParseValue(ReadOnlySpan<char> part)
{
int unitIndex = -1;
for (int i = 0; i < part.Length; i++)
{
char c = part[i];
if (char.IsDigit(c) || c == '-' || c == '.')
{
continue;
}
unitIndex = i;
break;
}
Unit unit = Unit.None;
if (unitIndex != -1)
{
var unitPart = part.Slice(unitIndex, part.Length - unitIndex);
unit = ParseUnit(unitPart);
part = part.Slice(0, unitIndex);
}
var value = double.Parse(part.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture);
return new UnitValue(unit, value);
}
static int ParseValuePair(
in ReadOnlySpan<char> part,
ref UnitValue leftValue,
ref UnitValue rightValue)
{
var commaIndex = part.IndexOf(',');
if (commaIndex != -1)
{
var leftPart = part.Slice(0, commaIndex).Trim();
var rightPart = part.Slice(commaIndex + 1, part.Length - commaIndex - 1).Trim();
leftValue = ParseValue(leftPart);
rightValue = ParseValue(rightPart);
return 2;
}
leftValue = ParseValue(part);
return 1;
}
static int ParseCommaDelimitedValues(ReadOnlySpan<char> part, in Span<UnitValue> outValues)
{
int valueIndex = 0;
while (true)
{
if (valueIndex >= outValues.Length)
{
throw new FormatException("Too many provided values.");
}
var commaIndex = part.IndexOf(',');
if (commaIndex == -1)
{
if (!part.IsWhiteSpace())
{
outValues[valueIndex++] = ParseValue(part);
}
break;
}
var valuePart = part.Slice(0, commaIndex).Trim();
outValues[valueIndex++] = ParseValue(valuePart);
part = part.Slice(commaIndex + 1, part.Length - commaIndex - 1);
}
return valueIndex;
}
switch (function)
{
case TransformFunction.Scale:
case TransformFunction.ScaleX:
case TransformFunction.ScaleY:
{
var scaleX = UnitValue.One;
var scaleY = UnitValue.One;
int count = ParseValuePair(functionPart, ref scaleX, ref scaleY);
if (count != 1 && (function == TransformFunction.ScaleX || function == TransformFunction.ScaleY))
{
ThrowFormatInvalidValueCount(function, 1);
}
VerifyZeroOrUnit(function, in scaleX, Unit.None);
VerifyZeroOrUnit(function, in scaleY, Unit.None);
if (function == TransformFunction.ScaleY)
{
scaleY = scaleX;
scaleX = UnitValue.One;
}
else if (function == TransformFunction.Scale && count == 1)
{
scaleY = scaleX;
}
builder.AppendScale(scaleX.Value, scaleY.Value);
break;
}
case TransformFunction.Skew:
case TransformFunction.SkewX:
case TransformFunction.SkewY:
{
var skewX = UnitValue.Zero;
var skewY = UnitValue.Zero;
int count = ParseValuePair(functionPart, ref skewX, ref skewY);
if (count != 1 && (function == TransformFunction.SkewX || function == TransformFunction.SkewY))
{
ThrowFormatInvalidValueCount(function, 1);
}
VerifyZeroOrAngle(function, in skewX);
VerifyZeroOrAngle(function, in skewY);
if (function == TransformFunction.SkewY)
{
skewY = skewX;
skewX = UnitValue.Zero;
}
builder.AppendSkew(ToRadians(in skewX), ToRadians(in skewY));
break;
}
case TransformFunction.Rotate:
{
var angle = UnitValue.Zero;
UnitValue _ = default;
int count = ParseValuePair(functionPart, ref angle, ref _);
if (count != 1)
{
ThrowFormatInvalidValueCount(function, 1);
}
VerifyZeroOrAngle(function, in angle);
builder.AppendRotate(ToRadians(in angle));
break;
}
case TransformFunction.Translate:
case TransformFunction.TranslateX:
case TransformFunction.TranslateY:
{
var translateX = UnitValue.Zero;
var translateY = UnitValue.Zero;
int count = ParseValuePair(functionPart, ref translateX, ref translateY);
if (count != 1 && (function == TransformFunction.TranslateX || function == TransformFunction.TranslateY))
{
ThrowFormatInvalidValueCount(function, 1);
}
VerifyZeroOrUnit(function, in translateX, Unit.Pixel);
VerifyZeroOrUnit(function, in translateY, Unit.Pixel);
if (function == TransformFunction.TranslateY)
{
translateY = translateX;
translateX = UnitValue.Zero;
}
builder.AppendTranslate(translateX.Value, translateY.Value);
break;
}
case TransformFunction.Matrix:
{
Span<UnitValue> values = stackalloc UnitValue[6];
int count = ParseCommaDelimitedValues(functionPart, in values);
if (count != 6)
{
ThrowFormatInvalidValueCount(function, 6);
}
foreach (UnitValue value in values)
{
VerifyZeroOrUnit(function, value, Unit.None);
}
var matrix = new Matrix(
values[0].Value,
values[1].Value,
values[2].Value,
values[3].Value,
values[4].Value,
values[5].Value);
builder.AppendMatrix(matrix);
break;
}
}
}
private static void VerifyZeroOrUnit(TransformFunction function, in UnitValue value, Unit unit)
{
bool isZero = value.Unit == Unit.None && value.Value == 0d;
if (!isZero && value.Unit != unit)
{
ThrowFormatInvalidValue(function, in value);
}
}
private static void VerifyZeroOrAngle(TransformFunction function, in UnitValue value)
{
if (value.Value != 0d && !IsAngleUnit(value.Unit))
{
ThrowFormatInvalidValue(function, in value);
}
}
private static bool IsAngleUnit(Unit unit)
{
switch (unit)
{
case Unit.Radian:
case Unit.Gradian:
case Unit.Degree:
case Unit.Turn:
{
return true;
}
}
return false;
}
private static void ThrowFormatInvalidValue(TransformFunction function, in UnitValue value)
{
var unitString = value.Unit == Unit.None ? string.Empty : value.Unit.ToString();
throw new FormatException($"Invalid value {value.Value} {unitString} for {function}");
}
private static void ThrowFormatInvalidValueCount(TransformFunction function, int count)
{
throw new FormatException($"Invalid format. {function} expects {count} value(s).");
}
private static Unit ParseUnit(in ReadOnlySpan<char> part)
{
foreach (var (name, unit) in s_unitMapping)
{
if (part.Equals(name.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return unit;
}
}
throw new FormatException($"Invalid unit: {part.ToString()}");
}
private static TransformFunction ParseTransformFunction(in ReadOnlySpan<char> part)
{
foreach (var (name, transformFunction) in s_functionMapping)
{
if (part.Equals(name.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return transformFunction;
}
}
return TransformFunction.Invalid;
}
private static double ToRadians(in UnitValue value)
{
return value.Unit switch
{
Unit.Radian => value.Value,
Unit.Gradian => MathUtilities.Grad2Rad(value.Value),
Unit.Degree => MathUtilities.Deg2Rad(value.Value),
Unit.Turn => MathUtilities.Turn2Rad(value.Value),
_ => value.Value
};
}
private enum Unit
{
None,
Pixel,
Radian,
Gradian,
Degree,
Turn
}
private readonly struct UnitValue
{
public readonly Unit Unit;
public readonly double Value;
public UnitValue(Unit unit, double value)
{
Unit = unit;
Value = value;
}
public static UnitValue Zero => new UnitValue(Unit.None, 0);
public static UnitValue One => new UnitValue(Unit.None, 1);
}
private enum TransformFunction
{
Invalid,
Translate,
TranslateX,
TranslateY,
Scale,
ScaleX,
ScaleY,
Skew,
SkewX,
SkewY,
Rotate,
Matrix
}
}
}

6
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -107,6 +107,12 @@ namespace Avalonia.Platform
/// <param name="clip">The clip rectangle.</param> /// <param name="clip">The clip rectangle.</param>
void PushClip(Rect clip); void PushClip(Rect clip);
/// <summary>
/// Pushes a clip rounded rectangle.
/// </summary>
/// <param name="clip">The clip rounded rectangle</param>
void PushClip(RoundedRect clip);
/// <summary> /// <summary>
/// Pops the latest pushed clip rectangle. /// Pops the latest pushed clip rectangle.
/// </summary> /// </summary>

1
src/Avalonia.Visuals/Properties/AssemblyInfo.cs

@ -6,6 +6,7 @@ using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media.Imaging")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media.Imaging")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media.Transformation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")] [assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]

2
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -287,7 +287,7 @@ namespace Avalonia.Rendering
} }
catch (RenderTargetCorruptedException ex) catch (RenderTargetCorruptedException ex)
{ {
Logger.TryGet(LogEventLevel.Information)?.Log("Renderer", this, "Render target was corrupted. Exception: {0}", ex); Logger.TryGet(LogEventLevel.Information, LogArea.Animations)?.Log(this, "Render target was corrupted. Exception: {0}", ex);
RenderTarget?.Dispose(); RenderTarget?.Dispose();
RenderTarget = null; RenderTarget = null;
} }

8
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -78,7 +78,7 @@ namespace Avalonia.Rendering
} }
catch (RenderTargetCorruptedException ex) catch (RenderTargetCorruptedException ex)
{ {
Logger.TryGet(LogEventLevel.Information)?.Log("Renderer", this, "Render target was corrupted. Exception: {0}", ex); Logger.TryGet(LogEventLevel.Information, LogArea.Animations)?.Log(this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget.Dispose(); _renderTarget.Dispose();
_renderTarget = null; _renderTarget = null;
} }
@ -289,7 +289,11 @@ namespace Avalonia.Rendering
using (context.PushPostTransform(m)) using (context.PushPostTransform(m))
using (context.PushOpacity(opacity)) using (context.PushOpacity(opacity))
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState)) using (clipToBounds
? visual is IVisualWithRoundRectClip roundClipVisual
? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius))
: context.PushClip(bounds)
: default(DrawingContext.PushedState))
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState)) using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState))
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState)) using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer()) using (context.PushTransformContainer())

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

Loading…
Cancel
Save