Browse Source

Merge branch 'master' into feature/SharedSizeScope

pull/1945/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
39d7907022
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      azure-pipelines.yml
  2. 1
      build.cake
  3. 2
      build/SharedVersion.props
  4. 12
      native/Avalonia.Native/inc/avalonia-native.h
  5. 6
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  6. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme
  7. 14
      native/Avalonia.Native/src/OSX/AvnString.h
  8. 55
      native/Avalonia.Native/src/OSX/AvnString.mm
  9. 19
      native/Avalonia.Native/src/OSX/clipboard.mm
  10. 10
      native/Avalonia.Native/src/OSX/window.mm
  11. 7
      packages/Avalonia/Avalonia.csproj
  12. 6
      packages/Avalonia/Avalonia.props
  13. 17
      parameters.cake
  14. 19
      samples/ControlCatalog/App.xaml
  15. 2
      samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml
  16. 48
      src/Avalonia.Animation/AnimationInstance`1.cs
  17. 7
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  18. 11
      src/Avalonia.Controls/Button.cs
  19. 2
      src/Avalonia.Controls/DropDown.cs
  20. 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  21. 14
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  22. 8
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  23. 15
      src/Avalonia.Controls/ScrollViewer.cs
  24. 3
      src/Avalonia.Controls/TextBox.cs
  25. 2
      src/Avalonia.Controls/UserControl.cs
  26. 1
      src/Avalonia.Native/.gitignore
  27. 2
      src/Avalonia.Native/Avalonia.Native.csproj
  28. 1
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  29. 16
      src/Avalonia.Native/ClipboardImpl.cs
  30. 6
      src/Avalonia.Native/WindowImpl.cs
  31. 56
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  32. 1
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  33. 24
      src/Avalonia.Themes.Default/ScrollBar.xaml
  34. 6
      src/Avalonia.Themes.Default/ScrollViewer.xaml
  35. 15
      src/Avalonia.Themes.Default/UserControl.xaml
  36. 3
      src/Avalonia.Themes.Default/Window.xaml
  37. 16
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  38. 23
      src/Avalonia.Visuals/Visual.cs
  39. 2
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  40. 29
      tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs
  41. 208
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  42. 4
      tests/Avalonia.Controls.UnitTests/UserControlTests.cs
  43. 98
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs
  44. 44
      tests/Avalonia.Visuals.UnitTests/VisualTests.cs

3
azure-pipelines.yml

@ -29,6 +29,7 @@ jobs:
inputs: inputs:
testResultsFormat: 'VSTest' testResultsFormat: 'VSTest'
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx' testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
condition: not(canceled())
- job: macOS - job: macOS
pool: pool:
@ -77,6 +78,7 @@ jobs:
inputs: inputs:
testResultsFormat: 'VSTest' testResultsFormat: 'VSTest'
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx' testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
condition: not(canceled())
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
inputs: inputs:
@ -111,6 +113,7 @@ jobs:
inputs: inputs:
testResultsFormat: 'VSTest' testResultsFormat: 'VSTest'
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx' testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
condition: not(canceled())
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
inputs: inputs:

1
build.cake

@ -50,6 +50,7 @@ Setup<Parameters>(context =>
Information("IsPullRequest: " + parameters.IsPullRequest); Information("IsPullRequest: " + parameters.IsPullRequest);
Information("IsMainRepo: " + parameters.IsMainRepo); Information("IsMainRepo: " + parameters.IsMainRepo);
Information("IsMasterBranch: " + parameters.IsMasterBranch); Information("IsMasterBranch: " + parameters.IsMasterBranch);
Information("IsReleaseBranch: " + parameters.IsReleaseBranch);
Information("IsTagged: " + parameters.IsTagged); Information("IsTagged: " + parameters.IsTagged);
Information("IsReleasable: " + parameters.IsReleasable); Information("IsReleasable: " + parameters.IsReleasable);
Information("IsMyGetRelease: " + parameters.IsMyGetRelease); Information("IsMyGetRelease: " + parameters.IsMyGetRelease);

2
build/SharedVersion.props

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Product>Avalonia</Product> <Product>Avalonia</Product>
<Version>0.7.0</Version> <Version>0.7.1</Version>
<Copyright>Copyright 2018 &#169; The AvaloniaUI Project</Copyright> <Copyright>Copyright 2018 &#169; The AvaloniaUI Project</Copyright>
<PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl> <PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>

12
native/Avalonia.Native/inc/avalonia-native.h

@ -173,6 +173,12 @@ public:
virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0; virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0;
}; };
AVNCOM(IAvnString, 17) : IUnknown
{
virtual HRESULT Pointer(void**retOut) = 0;
virtual HRESULT Length(int*ret) = 0;
};
AVNCOM(IAvnWindowBase, 02) : IUnknown AVNCOM(IAvnWindowBase, 02) : IUnknown
{ {
virtual HRESULT Show() = 0; virtual HRESULT Show() = 0;
@ -210,7 +216,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
virtual HRESULT ShowDialog (IUnknown**ppv) = 0; virtual HRESULT ShowDialog (IUnknown**ppv) = 0;
virtual HRESULT SetCanResize(bool value) = 0; virtual HRESULT SetCanResize(bool value) = 0;
virtual HRESULT SetHasDecorations(bool value) = 0; virtual HRESULT SetHasDecorations(bool value) = 0;
virtual HRESULT SetTitle (const char* title) = 0; virtual HRESULT SetTitle (void* utf8Title) = 0;
virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
virtual HRESULT SetWindowState(AvnWindowState state) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0;
virtual HRESULT GetWindowState(AvnWindowState*ret) = 0; virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
@ -315,8 +321,8 @@ AVNCOM(IAvnScreens, 0e) : IUnknown
AVNCOM(IAvnClipboard, 0f) : IUnknown AVNCOM(IAvnClipboard, 0f) : IUnknown
{ {
virtual HRESULT GetText (void** retOut) = 0; virtual HRESULT GetText (IAvnString**ppv) = 0;
virtual HRESULT SetText (char* text) = 0; virtual HRESULT SetText (void* utf8Text) = 0;
virtual HRESULT Clear() = 0; virtual HRESULT Clear() = 0;
}; };

6
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; }; 37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; }; 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; }; 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; }; 5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; }; 5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
@ -26,6 +27,8 @@
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; }; 37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; }; 37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; };
37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = "<group>"; }; 37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = "<group>"; };
37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; };
37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; };
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; }; 37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; }; 5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; }; 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
@ -65,6 +68,8 @@
AB7A61E62147C814003C5833 = { AB7A61E62147C814003C5833 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */,
37A4E71A2178846A00EACBCD /* headers */, 37A4E71A2178846A00EACBCD /* headers */,
AB573DC3217605E400D389A2 /* gl.mm */, AB573DC3217605E400D389A2 /* gl.mm */,
5BF943652167AD1D009CAE35 /* cursor.h */, 5BF943652167AD1D009CAE35 /* cursor.h */,
@ -161,6 +166,7 @@
files = ( files = (
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */, 5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */, 5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */, AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */, 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */, 37A517B32159597E00FBA241 /* Screens.mm in Sources */,

4
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme

@ -38,7 +38,7 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "YES" useCustomWorkingDirectory = "YES"
customWorkingDirectory = "$PROJECT_DIR/../../../../samples/ControlCatalog" customWorkingDirectory = "$PROJECT_DIR/../../../../samples/ControlCatalog.NetCore"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
@ -58,7 +58,7 @@
</MacroExpansion> </MacroExpansion>
<CommandLineArguments> <CommandLineArguments>
<CommandLineArgument <CommandLineArgument
argument = "bin/Debug/netcoreapp2.0/ControlCatalog.dll" argument = "bin/Debug/netcoreapp2.0/ControlCatalog.NetCore.dll"
isEnabled = "YES"> isEnabled = "YES">
</CommandLineArgument> </CommandLineArgument>
</CommandLineArguments> </CommandLineArguments>

14
native/Avalonia.Native/src/OSX/AvnString.h

@ -0,0 +1,14 @@
//
// AvnString.h
// Avalonia.Native.OSX
//
// Created by Dan Walmsley on 07/11/2018.
// Copyright © 2018 Avalonia. All rights reserved.
//
#ifndef AvnString_h
#define AvnString_h
extern IAvnString* CreateAvnString(NSString* string);
#endif /* AvnString_h */

55
native/Avalonia.Native/src/OSX/AvnString.mm

@ -0,0 +1,55 @@
//
// AvnString.m
// Avalonia.Native.OSX
//
// Created by Dan Walmsley on 07/11/2018.
// Copyright © 2018 Avalonia. All rights reserved.
//
#include "common.h"
class AvnStringImpl : public virtual ComSingleObject<IAvnString, &IID_IAvnString>
{
private:
NSString* _string;
public:
FORWARD_IUNKNOWN()
AvnStringImpl(NSString* string)
{
_string = string;
}
virtual HRESULT Pointer(void**retOut) override
{
@autoreleasepool
{
if(retOut == nullptr)
{
return E_POINTER;
}
*retOut = (void*)_string.UTF8String;
return S_OK;
}
}
virtual HRESULT Length(int*retOut) override
{
if(retOut == nullptr)
{
return E_POINTER;
}
*retOut = (int)_string.length;
return S_OK;
}
};
IAvnString* CreateAvnString(NSString* string)
{
return new AvnStringImpl(string);
}

19
native/Avalonia.Native/src/OSX/clipboard.mm

@ -2,29 +2,34 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
#include "common.h" #include "common.h"
#include "AvnString.h"
class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard> class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard>
{ {
public: public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
virtual HRESULT GetText (void** retOut) override virtual HRESULT GetText (IAvnString**ppv) override
{ {
@autoreleasepool @autoreleasepool
{ {
NSString *str = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]; if(ppv == nullptr)
*retOut = (void *)str.UTF8String; {
return E_POINTER;
}
*ppv = CreateAvnString([[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]);
return S_OK;
} }
return S_OK;
} }
virtual HRESULT SetText (char* text) override virtual HRESULT SetText (void* utf8String) override
{ {
@autoreleasepool @autoreleasepool
{ {
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard clearContents]; [pasteBoard clearContents];
[pasteBoard setString:@(text) forType:NSPasteboardTypeString]; [pasteBoard setString:[NSString stringWithUTF8String:(const char*)utf8String] forType:NSPasteboardTypeString];
} }
return S_OK; return S_OK;

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

@ -530,11 +530,11 @@ private:
} }
} }
virtual HRESULT SetTitle (const char* title) override virtual HRESULT SetTitle (void* utf8title) override
{ {
@autoreleasepool @autoreleasepool
{ {
_lastTitle = [NSString stringWithUTF8String:title]; _lastTitle = [NSString stringWithUTF8String:(const char*)utf8title];
[Window setTitle:_lastTitle]; [Window setTitle:_lastTitle];
[Window setTitleVisibility:NSWindowTitleVisible]; [Window setTitleVisibility:NSWindowTitleVisible];
@ -963,7 +963,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (BOOL)performKeyEquivalent:(NSEvent *)event - (BOOL)performKeyEquivalent:(NSEvent *)event
{ {
return _lastKeyHandled; bool result = _lastKeyHandled;
_lastKeyHandled = false;
return result;
} }
- (void)keyDown:(NSEvent *)event - (void)keyDown:(NSEvent *)event

7
packages/Avalonia/Avalonia.csproj

@ -1,4 +1,4 @@
<Project Sdk="AggregatePackage.NuGet.Sdk"> <Project Sdk="AggregatePackage.NuGet.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net461;netcoreapp2.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net461;netcoreapp2.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
@ -27,6 +27,11 @@
<Visible>false</Visible> <Visible>false</Visible>
<BuildAction>None</BuildAction> <BuildAction>None</BuildAction>
</_PackageFiles> </_PackageFiles>
<_PackageFiles Include="Avalonia.props">
<PackagePath>build/Avalonia.props</PackagePath>
<Visible>false</Visible>
<BuildAction>None</BuildAction>
</_PackageFiles>
</ItemGroup> </ItemGroup>
</Target> </Target>

6
packages/Avalonia/Avalonia.props

@ -0,0 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)\..\tools\netcoreapp2.0\designer\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath>
<AvaloniaPreviewerNetFullToolPath>$(MSBuildThisFileDirectory)\..\tools\net461\designer\Avalonia.Designer.HostApp.exe</AvaloniaPreviewerNetFullToolPath>
</PropertyGroup>
</Project>

17
parameters.cake

@ -9,6 +9,7 @@ public class Parameters
public string MasterBranch { get; private set; } public string MasterBranch { get; private set; }
public string ReleasePlatform { get; private set; } public string ReleasePlatform { get; private set; }
public string ReleaseConfiguration { get; private set; } public string ReleaseConfiguration { get; private set; }
public string ReleaseBranchPrefix { get; private set; }
public string MSBuildSolution { get; private set; } public string MSBuildSolution { get; private set; }
public bool IsLocalBuild { get; private set; } public bool IsLocalBuild { get; private set; }
public bool IsRunningOnUnix { get; private set; } public bool IsRunningOnUnix { get; private set; }
@ -18,6 +19,7 @@ public class Parameters
public bool IsPullRequest { get; private set; } public bool IsPullRequest { get; private set; }
public bool IsMainRepo { get; private set; } public bool IsMainRepo { get; private set; }
public bool IsMasterBranch { get; private set; } public bool IsMasterBranch { get; private set; }
public bool IsReleaseBranch { get; private set; }
public bool IsTagged { get; private set; } public bool IsTagged { get; private set; }
public bool IsReleasable { get; private set; } public bool IsReleasable { get; private set; }
public bool IsMyGetRelease { get; private set; } public bool IsMyGetRelease { get; private set; }
@ -46,8 +48,9 @@ public class Parameters
SkipTests = context.HasArgument("skip-tests"); SkipTests = context.HasArgument("skip-tests");
// CONFIGURATION // CONFIGURATION
MainRepo = "AvaloniaUI/Avalonia"; MainRepo = "https://github.com/AvaloniaUI/Avalonia";
MasterBranch = "master"; MasterBranch = "master";
ReleaseBranchPrefix = "refs/heads/release/";
ReleaseConfiguration = "Release"; ReleaseConfiguration = "Release";
MSBuildSolution = "./dirs.proj"; MSBuildSolution = "./dirs.proj";
@ -59,12 +62,14 @@ public class Parameters
IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts"; IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts";
IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest; IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest;
IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, buildSystem.AppVeyor.Environment.Repository.Name); IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, context.EnvironmentVariable("BUILD_REPOSITORY_URI"));
IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, buildSystem.AppVeyor.Environment.Repository.Branch); IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, context.EnvironmentVariable("BUILD_SOURCEBRANCHNAME"));
IsReleaseBranch = (context.EnvironmentVariable("BUILD_SOURCEBRANCH")??"").StartsWith(ReleaseBranchPrefix, StringComparison.OrdinalIgnoreCase);
IsTagged = buildSystem.AppVeyor.Environment.Repository.Tag.IsTag IsTagged = buildSystem.AppVeyor.Environment.Repository.Tag.IsTag
&& !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name); && !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name);
IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
IsMyGetRelease = !IsTagged && IsReleasable; IsMyGetRelease = !IsTagged && IsReleasable;
IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch;
// VERSION // VERSION
Version = context.Argument("force-nuget-version", GetVersion()); Version = context.Argument("force-nuget-version", GetVersion());
@ -92,9 +97,13 @@ public class Parameters
} }
else if (IsRunningOnAzure) else if (IsRunningOnAzure)
{ {
if(!IsNuGetRelease)
{
// Use AssemblyVersion with Build as version // Use AssemblyVersion with Build as version
Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta"; Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta";
PublishTestResults = true; }
PublishTestResults = true;
} }
// DIRECTORIES // DIRECTORIES

19
samples/ControlCatalog/App.xaml

@ -2,23 +2,16 @@
<Application.Styles> <Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
<Style Selector="TextBlock.h1"> <Style Selector="TextBlock.h1">
<Setter Property="Foreground" Value="#212121"/> <Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="Medium"/> <Setter Property="FontWeight" Value="Medium"/>
</Style> </Style>
<Style Selector="TextBlock.h2"> <Style Selector="TextBlock.h2">
<Setter Property="Foreground" Value="#727272"/> <Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
<Setter Property="FontSize" Value="13"/>
</Style> </Style>
<Style Selector="TextBlock.h3">
<Style Selector="TextBlock.h3"> <Setter Property="FontSize" Value="{DynamicResource FontSizeSmall}"/>
<Setter Property="Foreground" Value="#a2a2a2"/> </Style>
<Setter Property="FontSize" Value="13"/> <StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
</Style>
<StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
</Application.Styles> </Application.Styles>
</Application> </Application>

2
samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml

@ -19,7 +19,7 @@
<LayoutTransformControl.LayoutTransform> <LayoutTransformControl.LayoutTransform>
<RotateTransform Angle="{Binding #rotation.Value}"/> <RotateTransform Angle="{Binding #rotation.Value}"/>
</LayoutTransformControl.LayoutTransform> </LayoutTransformControl.LayoutTransform>
<TextBlock>Layout Transform</TextBlock> <Button Background="White">Layout Transform</Button>
</LayoutTransformControl> </LayoutTransformControl>
</Grid> </Grid>
</DockPanel> </DockPanel>

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

@ -72,10 +72,13 @@ namespace Avalonia.Animation
_onCompleteAction = OnComplete; _onCompleteAction = OnComplete;
_interpolator = Interpolator; _interpolator = Interpolator;
_baseClock = baseClock; _baseClock = baseClock;
} }
protected override void Unsubscribed() protected override void Unsubscribed()
{ {
//Animation may have been stopped before it has finished
ApplyFinalFill();
_timerSubscription?.Dispose(); _timerSubscription?.Dispose();
_clock.PlayState = PlayState.Stop; _clock.PlayState = PlayState.Stop;
} }
@ -98,11 +101,15 @@ namespace Avalonia.Animation
} }
} }
private void DoComplete() private void ApplyFinalFill()
{ {
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
}
private void DoComplete()
{
ApplyFinalFill();
_onCompleteAction?.Invoke(); _onCompleteAction?.Invoke();
PublishCompleted(); PublishCompleted();
} }
@ -133,6 +140,7 @@ namespace Avalonia.Animation
DoPlayStates(); DoPlayStates();
var delayEndpoint = _delay; var delayEndpoint = _delay;
var iterationEndpoint = delayEndpoint + _duration; var iterationEndpoint = delayEndpoint + _duration;
var iterationTime = time;
//determine if time is currently in the first iteration. //determine if time is currently in the first iteration.
if (time >= TimeSpan.Zero & time <= iterationEndpoint) if (time >= TimeSpan.Zero & time <= iterationEndpoint)
@ -142,7 +150,7 @@ namespace Avalonia.Animation
else if (time > iterationEndpoint) else if (time > iterationEndpoint)
{ {
//Subtract first iteration to properly get the subsequent iteration time //Subtract first iteration to properly get the subsequent iteration time
time -= iterationEndpoint; iterationTime -= iterationEndpoint;
if (!_iterationDelay & delayEndpoint > TimeSpan.Zero) if (!_iterationDelay & delayEndpoint > TimeSpan.Zero)
{ {
@ -151,39 +159,45 @@ namespace Avalonia.Animation
} }
//Calculate the current iteration number //Calculate the current iteration number
_currentIteration = (int)Math.Floor((double)((double)time.Ticks / iterationEndpoint.Ticks)) + 2; _currentIteration = Math.Min(_repeatCount,(int)Math.Floor((double)((double)iterationTime.Ticks / iterationEndpoint.Ticks)) + 2);
} }
else else
{ {
return; return;
} }
time = TimeSpan.FromTicks((long)(time.Ticks % iterationEndpoint.Ticks)); // Determine if the current iteration should have its normalized time inverted.
if (!_isLooping)
{
if ((_currentIteration > _repeatCount) || (time > iterationEndpoint))
DoComplete();
}
// Determine if the current iteration should have its normalized time inverted.
bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false :
_animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true :
_animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false :
_animationDirection == PlaybackDirection.Reverse ? true : false; _animationDirection == PlaybackDirection.Reverse ? true : false;
if (delayEndpoint > TimeSpan.Zero & time < delayEndpoint) if (!_isLooping)
{
var totalTime = _iterationDelay ? _repeatCount * ( _duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks;
if (time.Ticks >= totalTime)
{
var easedTime = _easeFunc.Ease(isCurIterReverse ? 0.0 : 1.0);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
DoComplete();
return;
}
}
iterationTime = TimeSpan.FromTicks((long)(iterationTime.Ticks % iterationEndpoint.Ticks));
if (delayEndpoint > TimeSpan.Zero & iterationTime < delayEndpoint)
{ {
DoDelay(); DoDelay();
} }
else else
{ {
// Offset the delay time // Offset the delay time
time -= delayEndpoint; iterationTime -= delayEndpoint;
iterationEndpoint -= delayEndpoint; iterationEndpoint -= delayEndpoint;
// Normalize time // Normalize time
var interpVal = (double)time.Ticks / iterationEndpoint.Ticks; var interpVal = (double)iterationTime.Ticks / iterationEndpoint.Ticks;
if (isCurIterReverse) if (isCurIterReverse)
interpVal = 1 - interpVal; interpVal = 1 - interpVal;

7
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@ -104,7 +104,12 @@ namespace Avalonia.Collections
case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
Remove(e.OldStartingIndex, e.OldItems); Remove(e.OldStartingIndex, e.OldItems);
Add(e.NewStartingIndex, e.NewItems); int newIndex = e.NewStartingIndex;
if(newIndex > e.OldStartingIndex)
{
newIndex -= e.OldItems.Count;
}
Add(newIndex, e.NewItems);
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:

11
src/Avalonia.Controls/Button.cs

@ -2,10 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Linq;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -251,7 +253,10 @@ namespace Avalonia.Controls
IsPressed = false; IsPressed = false;
e.Handled = true; e.Handled = true;
if (ClickMode == ClickMode.Release && new Rect(Bounds.Size).Contains(e.GetPosition(this))) var hittest = this.GetVisualsAt(e.GetPosition(this));
if (ClickMode == ClickMode.Release &&
hittest.Any(c => c == this || (c as IStyledElement)?.TemplatedParent == this))
{ {
OnClick(); OnClick();
} }
@ -261,9 +266,9 @@ namespace Avalonia.Controls
protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status) protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status)
{ {
base.UpdateDataValidation(property, status); base.UpdateDataValidation(property, status);
if(property == CommandProperty) if (property == CommandProperty)
{ {
if(status?.ErrorType == BindingErrorType.Error) if (status?.ErrorType == BindingErrorType.Error)
{ {
IsEnabled = false; IsEnabled = false;
} }

2
src/Avalonia.Controls/DropDown.cs

@ -145,7 +145,7 @@ namespace Avalonia.Controls
{ {
if (!e.Handled) if (!e.Handled)
{ {
if (((IVisual)e.Source).GetVisualRoot() is PopupRoot) if (_popup?.PopupRoot != null && ((IVisual)e.Source).GetVisualRoot() == _popup?.PopupRoot)
{ {
if (UpdateSelectionFromEventSource(e.Source)) if (UpdateSelectionFromEventSource(e.Source))
{ {

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

@ -89,7 +89,7 @@ namespace Avalonia.Controls.Primitives
control.Clip = clip; control.Clip = clip;
} }
clip.Rect = bounds.Clip.TransformToAABB(-bounds.Transform); clip.Rect = bounds.Bounds;
} }
private void ChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) private void ChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)

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

@ -128,6 +128,20 @@ namespace Avalonia.Controls.Primitives
} }
} }
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.PageUp)
{
LargeDecrement();
e.Handled = true;
}
else if (e.Key == Key.PageDown)
{
LargeIncrement();
e.Handled = true;
}
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{ {
base.OnTemplateApplied(e); base.OnTemplateApplied(e);

8
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -211,10 +211,10 @@ namespace Avalonia.Controls.Remote.Server
} }
if(obj is KeyEventMessage key) if(obj is KeyEventMessage key)
{ {
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawKeyEventArgs( Input?.Invoke(new RawKeyEventArgs(
KeyboardDevice, KeyboardDevice,
0, 0,
@ -225,10 +225,10 @@ namespace Avalonia.Controls.Remote.Server
} }
if(obj is TextInputEventMessage text) if(obj is TextInputEventMessage text)
{ {
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawTextInputEventArgs( Input?.Invoke(new RawTextInputEventArgs(
KeyboardDevice, KeyboardDevice,
0, 0,

15
src/Avalonia.Controls/ScrollViewer.cs

@ -4,6 +4,7 @@
using System; using System;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -441,5 +442,19 @@ namespace Avalonia.Controls
RaisePropertyChanged(VerticalScrollBarValueProperty, 0, VerticalScrollBarValue); RaisePropertyChanged(VerticalScrollBarValueProperty, 0, VerticalScrollBarValue);
RaisePropertyChanged(VerticalScrollBarViewportSizeProperty, 0, VerticalScrollBarViewportSize); RaisePropertyChanged(VerticalScrollBarViewportSizeProperty, 0, VerticalScrollBarViewportSize);
} }
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.PageUp)
{
VerticalScrollBarValue = Math.Max(_offset.Y - _viewport.Height, 0);
e.Handled = true;
}
else if (e.Key == Key.PageDown)
{
VerticalScrollBarValue = Math.Min(_offset.Y + _viewport.Height, VerticalScrollBarMaximum);
e.Handled = true;
}
}
} }
} }

3
src/Avalonia.Controls/TextBox.cs

@ -14,6 +14,7 @@ using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -655,6 +656,8 @@ namespace Avalonia.Controls
if (_presenter != null && e.Device.Captured == _presenter) if (_presenter != null && e.Device.Captured == _presenter)
{ {
var point = e.GetPosition(_presenter); var point = e.GetPosition(_presenter);
point = new Point(MathUtilities.Clamp(point.X, 0, _presenter.Bounds.Width - 1), MathUtilities.Clamp(point.Y, 0, _presenter.Bounds.Height - 1));
CaretIndex = SelectionEnd = _presenter.GetCaretIndex(point); CaretIndex = SelectionEnd = _presenter.GetCaretIndex(point);
} }
} }

2
src/Avalonia.Controls/UserControl.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
Type IStyleable.StyleKey => typeof(ContentControl); Type IStyleable.StyleKey => typeof(UserControl);
/// <inheritdoc/> /// <inheritdoc/>
void INameScope.Register(string name, object element) void INameScope.Register(string name, object element)

1
src/Avalonia.Native/.gitignore

@ -0,0 +1 @@
Generated/*.cs

2
src/Avalonia.Native/Avalonia.Native.csproj

@ -7,6 +7,8 @@
<CastXmlPath Condition="Exists('/usr/bin/castxml')">/usr/bin/castxml</CastXmlPath> <CastXmlPath Condition="Exists('/usr/bin/castxml')">/usr/bin/castxml</CastXmlPath>
<CastXmlPath Condition="Exists('/usr/local/bin/castxml')">/usr/local/bin/castxml</CastXmlPath> <CastXmlPath Condition="Exists('/usr/local/bin/castxml')">/usr/local/bin/castxml</CastXmlPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- This is needed because Rider doesn't see generated files in obj for some reason -->
<SharpGenGeneratedCodeFolder>$(MSBuildThisFileDirectory)/Generated</SharpGenGeneratedCodeFolder>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release' AND '$([MSBuild]::IsOSPlatform(OSX))' == 'true'"> <ItemGroup Condition="'$(Configuration)' == 'Release' AND '$([MSBuild]::IsOSPlatform(OSX))' == 'true'">

1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -76,6 +76,7 @@ namespace Avalonia.Native
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) .Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<IWindowingPlatformGlFeature>().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature())) .Bind<IWindowingPlatformGlFeature>().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
.Bind<AvaloniaNativeOptions>().ToConstant(opts); .Bind<AvaloniaNativeOptions>().ToConstant(opts);
} }

16
src/Avalonia.Native/ClipboardImpl.cs

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Platform.Interop;
namespace Avalonia.Native namespace Avalonia.Native
{ {
@ -24,12 +25,14 @@ namespace Avalonia.Native
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task<string> GetTextAsync() public unsafe Task<string> GetTextAsync()
{ {
var outPtr = _native.GetText(); using (var text = _native.GetText())
var text = Marshal.PtrToStringAnsi(outPtr); {
var result = System.Text.Encoding.UTF8.GetString((byte*)text.Pointer(), text.Length());
return Task.FromResult(text); return Task.FromResult(result);
}
} }
public Task SetTextAsync(string text) public Task SetTextAsync(string text)
@ -38,7 +41,10 @@ namespace Avalonia.Native
if (text != null) if (text != null)
{ {
_native.SetText(text); using (var buffer = new Utf8Buffer(text))
{
_native.SetText(buffer.DangerousGetHandle());
}
} }
return Task.CompletedTask; return Task.CompletedTask;

6
src/Avalonia.Native/WindowImpl.cs

@ -5,6 +5,7 @@ using System;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Platform.Interop;
namespace Avalonia.Native namespace Avalonia.Native
{ {
@ -68,7 +69,10 @@ namespace Avalonia.Native
public void SetTitle(string title) public void SetTitle(string title)
{ {
_native.SetTitle(title); using (var buffer = new Utf8Buffer(title))
{
_native.SetTitle(buffer.DangerousGetHandle());
}
} }
public WindowState WindowState public WindowState WindowState

56
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@ -0,0 +1,56 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<Color x:Key="ThemeAccentColor">#CC119EDA</Color>
<Color x:Key="ThemeAccentColor2">#99119EDA</Color>
<Color x:Key="ThemeAccentColor3">#66119EDA</Color>
<Color x:Key="ThemeAccentColor4">#33119EDA</Color>
<Color x:Key="ThemeBackgroundColor">#FF282828</Color>
<Color x:Key="ThemeBorderLowColor">#FF505050</Color>
<Color x:Key="ThemeBorderMidColor">#FF808080</Color>
<Color x:Key="ThemeBorderHighColor">#FFA0A0A0</Color>
<Color x:Key="ThemeControlLowColor">#FF282828</Color>
<Color x:Key="ThemeControlMidColor">#FF505050</Color>
<Color x:Key="ThemeControlHighColor">#FF808080</Color>
<Color x:Key="ThemeControlHighlightLowColor">#FFA8A8A8</Color>
<Color x:Key="ThemeControlHighlightMidColor">#FF828282</Color>
<Color x:Key="ThemeControlHighlightHighColor">#FF505050</Color>
<Color x:Key="ThemeForegroundColor">#FFDEDEDE</Color>
<Color x:Key="ThemeForegroundLowColor">#FF808080</Color>
<Color x:Key="HighlightColor">#FF119EDA</Color>
<Color x:Key="ErrorColor">#FFFF0000</Color>
<Color x:Key="ErrorLowColor">#10FF0000</Color>
<SolidColorBrush x:Key="ThemeBackgroundBrush" Color="{DynamicResource ThemeBackgroundColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeBorderLowBrush" Color="{DynamicResource ThemeBorderLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeBorderMidBrush" Color="{DynamicResource ThemeBorderMidColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeBorderHighBrush" Color="{DynamicResource ThemeBorderHighColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeControlLowBrush" Color="{DynamicResource ThemeControlLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeControlMidBrush" Color="{DynamicResource ThemeControlMidColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeControlHighBrush" Color="{DynamicResource ThemeControlHighColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeControlHighlightLowBrush" Color="{DynamicResource ThemeControlHighlightLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeControlHighlightMidBrush" Color="{DynamicResource ThemeControlHighlightMidColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeControlHighlightHighBrush" Color="{DynamicResource ThemeControlHighlightHighColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="{DynamicResource ThemeForegroundLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="HighlightBrush" Color="{DynamicResource HighlightColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush" Color="{DynamicResource ThemeAccentColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush2" Color="{DynamicResource ThemeAccentColor2}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush3" Color="{DynamicResource ThemeAccentColor3}"></SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}"></SolidColorBrush>
<SolidColorBrush x:Key="ErrorBrush" Color="{DynamicResource ErrorColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ErrorLowBrush" Color="{DynamicResource ErrorLowColor}"></SolidColorBrush>
<Thickness x:Key="ThemeBorderThickness">1,1,1,1</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>
<sys:Double x:Key="FontSizeSmall">10</sys:Double>
<sys:Double x:Key="FontSizeNormal">12</sys:Double>
<sys:Double x:Key="FontSizeLarge">16</sys:Double>
</Style.Resources>
</Style>

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

@ -35,6 +35,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.Expander.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.Expander.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TreeView.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.TreeView.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TreeViewItem.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.TreeViewItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.UserControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Window.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.Window.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.EmbeddableControlRoot.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.EmbeddableControlRoot.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.CalendarButton.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.CalendarButton.xaml?assembly=Avalonia.Themes.Default"/>

24
src/Avalonia.Themes.Default/ScrollBar.xaml

@ -7,7 +7,8 @@
<RepeatButton Name="PART_LineUpButton" <RepeatButton Name="PART_LineUpButton"
Classes="repeat" Classes="repeat"
Grid.Row="0" Grid.Row="0"
Grid.Column="0"> Grid.Column="0"
Focusable="False">
<Path Data="M 0,4 C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4 z" <Path Data="M 0,4 C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4 z"
Stretch="Uniform" Stretch="Uniform"
Fill="{DynamicResource ThemeForegroundLowBrush}" /> Fill="{DynamicResource ThemeForegroundLowBrush}" />
@ -21,11 +22,13 @@
Orientation="{TemplateBinding Orientation}"> Orientation="{TemplateBinding Orientation}">
<Track.DecreaseButton> <Track.DecreaseButton>
<RepeatButton Name="PART_PageUpButton" <RepeatButton Name="PART_PageUpButton"
Classes="repeattrack" /> Classes="repeattrack"
Focusable="False"/>
</Track.DecreaseButton> </Track.DecreaseButton>
<Track.IncreaseButton> <Track.IncreaseButton>
<RepeatButton Name="PART_PageDownButton" <RepeatButton Name="PART_PageDownButton"
Classes="repeattrack" /> Classes="repeattrack"
Focusable="False"/>
</Track.IncreaseButton> </Track.IncreaseButton>
<Thumb Name="thumb"> <Thumb Name="thumb">
<Thumb.Template> <Thumb.Template>
@ -38,7 +41,8 @@
<RepeatButton Name="PART_LineDownButton" <RepeatButton Name="PART_LineDownButton"
Classes="repeat" Classes="repeat"
Grid.Row="2" Grid.Row="2"
Grid.Column="2"> Grid.Column="2"
Focusable="False">
<Path Data="M 0,2.5 C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5 z" <Path Data="M 0,2.5 C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5 z"
Stretch="Uniform" Stretch="Uniform"
Fill="{DynamicResource ThemeForegroundLowBrush}" /> Fill="{DynamicResource ThemeForegroundLowBrush}" />
@ -58,7 +62,8 @@
<RepeatButton Name="PART_LineUpButton" <RepeatButton Name="PART_LineUpButton"
Classes="repeat" Classes="repeat"
Grid.Row="0" Grid.Row="0"
Grid.Column="0"> Grid.Column="0"
Focusable="False">
<Path Data="M 3.18,7 C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7 z" <Path Data="M 3.18,7 C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7 z"
Stretch="Uniform" Stretch="Uniform"
Fill="{DynamicResource ThemeForegroundLowBrush}" /> Fill="{DynamicResource ThemeForegroundLowBrush}" />
@ -72,11 +77,13 @@
Orientation="{TemplateBinding Orientation}"> Orientation="{TemplateBinding Orientation}">
<Track.DecreaseButton> <Track.DecreaseButton>
<RepeatButton Name="PART_PageUpButton" <RepeatButton Name="PART_PageUpButton"
Classes="repeattrack" /> Classes="repeattrack"
Focusable="False"/>
</Track.DecreaseButton> </Track.DecreaseButton>
<Track.IncreaseButton> <Track.IncreaseButton>
<RepeatButton Name="PART_PageDownButton" <RepeatButton Name="PART_PageDownButton"
Classes="repeattrack" /> Classes="repeattrack"
Focusable="False"/>
</Track.IncreaseButton> </Track.IncreaseButton>
<Thumb Name="thumb"> <Thumb Name="thumb">
<Thumb.Template> <Thumb.Template>
@ -89,7 +96,8 @@
<RepeatButton Name="PART_LineDownButton" <RepeatButton Name="PART_LineDownButton"
Classes="repeat" Classes="repeat"
Grid.Row="2" Grid.Row="2"
Grid.Column="2"> Grid.Column="2"
Focusable="False">
<Path Data="M 1.81,7 C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7 z" <Path Data="M 1.81,7 C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7 z"
Stretch="Uniform" Stretch="Uniform"
Fill="{DynamicResource ThemeForegroundLowBrush}" /> Fill="{DynamicResource ThemeForegroundLowBrush}" />

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

@ -19,14 +19,16 @@
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"/>
<ScrollBar Name="verticalScrollBar" <ScrollBar Name="verticalScrollBar"
Orientation="Vertical" Orientation="Vertical"
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"/>
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

15
src/Avalonia.Themes.Default/UserControl.xaml

@ -0,0 +1,15 @@
<Style xmlns="https://github.com/avaloniaui" Selector="UserControl">
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</ControlTemplate>
</Setter>
</Style>

3
src/Avalonia.Themes.Default/Window.xaml

@ -1,5 +1,6 @@
<Style xmlns="https://github.com/avaloniaui" Selector="Window"> <Style xmlns="https://github.com/avaloniaui" Selector="Window">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/> <Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
@ -15,4 +16,4 @@
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

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

@ -125,6 +125,20 @@ namespace Avalonia.Rendering
if (m.HasValue) if (m.HasValue)
{ {
var bounds = new Rect(visual.Bounds.Size).TransformToAABB(m.Value); var bounds = new Rect(visual.Bounds.Size).TransformToAABB(m.Value);
//use transformedbounds as previous render state of the visual bounds
//so we can invalidate old and new bounds of a control in case it moved/shrinked
if (visual.TransformedBounds.HasValue)
{
var trb = visual.TransformedBounds.Value;
var trBounds = trb.Bounds.TransformToAABB(trb.Transform);
if (trBounds != bounds)
{
_renderRoot?.Invalidate(trBounds);
}
}
_renderRoot?.Invalidate(bounds); _renderRoot?.Invalidate(bounds);
} }
} }
@ -191,7 +205,7 @@ namespace Avalonia.Rendering
} }
} }
static IEnumerable<IVisual> HitTest( private static IEnumerable<IVisual> HitTest(
IVisual visual, IVisual visual,
Point p, Point p,
Func<IVisual, bool> filter) Func<IVisual, bool> filter)

23
src/Avalonia.Visuals/Visual.cs

@ -304,7 +304,7 @@ namespace Avalonia
{ {
var thisOffset = GetOffsetFrom(common, this); var thisOffset = GetOffsetFrom(common, this);
var thatOffset = GetOffsetFrom(common, visual); var thatOffset = GetOffsetFrom(common, visual);
return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset); return -thatOffset * thisOffset;
} }
return null; return null;
@ -454,13 +454,28 @@ namespace Avalonia
/// <param name="ancestor">The ancestor visual.</param> /// <param name="ancestor">The ancestor visual.</param>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <returns>The visual offset.</returns> /// <returns>The visual offset.</returns>
private static Vector GetOffsetFrom(IVisual ancestor, IVisual visual) private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual)
{ {
var result = new Vector(); var result = Matrix.Identity;
while (visual != ancestor) while (visual != ancestor)
{ {
result = new Vector(result.X + visual.Bounds.X, result.Y + visual.Bounds.Y); if (visual.RenderTransform?.Value != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(visual.Bounds.Size);
var offset = Matrix.CreateTranslation(origin);
var renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
result *= renderTransform;
}
var topLeft = visual.Bounds.TopLeft;
if (topLeft != default)
{
result *= Matrix.CreateTranslation(topLeft);
}
visual = visual.VisualParent; visual = visual.VisualParent;
if (visual == null) if (visual == null)

2
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -121,7 +121,7 @@ namespace Avalonia.Skia
int offset = 0; int offset = 0;
if (point.X >= (rects[line.Start].X + line.Width) / 2 && line.Length > 0) if (point.X >= (rects[line.Start].X + line.Width) && line.Length > 0)
{ {
offset = line.TextLength > line.Length ? offset = line.TextLength > line.Length ?
line.Length : (line.Length - 1); line.Length : (line.Length - 1);

29
tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs

@ -1,5 +1,4 @@
using System; using System.Linq;
using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Xunit; using Xunit;
@ -82,13 +81,31 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.Equal(source, result); Assert.Equal(source, result);
} }
[Fact] [Theory]
public void CreateDerivedList_Handles_MoveRange() [InlineData(0, 2, 3)]
[InlineData(0, 2, 4)]
[InlineData(0, 2, 5)]
[InlineData(0, 4, 4)]
[InlineData(1, 2, 0)]
[InlineData(1, 2, 4)]
[InlineData(1, 2, 5)]
[InlineData(1, 4, 0)]
[InlineData(2, 2, 0)]
[InlineData(2, 2, 1)]
[InlineData(2, 2, 3)]
[InlineData(2, 2, 4)]
[InlineData(2, 2, 5)]
[InlineData(4, 2, 0)]
[InlineData(4, 2, 1)]
[InlineData(4, 2, 3)]
[InlineData(5, 1, 0)]
[InlineData(5, 1, 3)]
public void CreateDerivedList_Handles_MoveRange(int oldIndex, int count, int newIndex)
{ {
var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3 }); var source = new AvaloniaList<int>(new[] { 0, 1, 2, 3, 4, 5 });
var target = source.CreateDerivedList(x => new Wrapper(x)); var target = source.CreateDerivedList(x => new Wrapper(x));
source.MoveRange(1, 2, 0); source.MoveRange(oldIndex, count, newIndex);
var result = target.Select(x => x.Value).ToList(); var result = target.Select(x => x.Value).ToList();

208
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@ -1,7 +1,12 @@
using System; using System;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Markup.Data; using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.VisualTree;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
@ -92,6 +97,205 @@ namespace Avalonia.Controls.UnitTests
Assert.False(target.IsEnabled); Assert.False(target.IsEnabled);
} }
[Fact]
public void Button_Raises_Click()
{
var mouse = Mock.Of<IMouseDevice>();
var renderer = Mock.Of<IRenderer>();
IInputElement captured = null;
Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(50, 50));
Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
var target = new TestButton()
{
Bounds = new Rect(0, 0, 100, 100),
Renderer = renderer
};
bool clicked = false;
target.Click += (s, e) => clicked = true;
RaisePointerEnter(target, mouse);
RaisePointerMove(target, mouse);
RaisePointerPressed(target, mouse, 1, MouseButton.Left);
Assert.Equal(captured, target);
RaisePointerReleased(target, mouse, MouseButton.Left);
Assert.Equal(captured, null);
Assert.True(clicked);
}
[Fact]
public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside()
{
var mouse = Mock.Of<IMouseDevice>();
var renderer = Mock.Of<IRenderer>();
IInputElement captured = null;
Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(200, 50));
Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
var target = new TestButton()
{
Bounds = new Rect(0, 0, 100, 100),
Renderer = renderer
};
bool clicked = false;
target.Click += (s, e) => clicked = true;
RaisePointerEnter(target, mouse);
RaisePointerMove(target, mouse);
RaisePointerPressed(target, mouse, 1, MouseButton.Left);
RaisePointerLeave(target, mouse);
Assert.Equal(captured, target);
RaisePointerReleased(target, mouse, MouseButton.Left);
Assert.Equal(captured, null);
Assert.False(clicked);
}
[Fact]
public void Button_With_RenderTransform_Raises_Click()
{
var mouse = Mock.Of<IMouseDevice>();
var renderer = Mock.Of<IRenderer>();
IInputElement captured = null;
Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(150, 50));
Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
new IVisual[] { r } : new IVisual[0]);
var target = new TestButton()
{
Bounds = new Rect(0, 0, 100, 100),
RenderTransform = new TranslateTransform { X = 100, Y = 0 },
Renderer = renderer
};
//actual bounds of button should be 100,0,100,100 x -> translated 100 pixels
//so mouse with x=150 coordinates should trigger click
//button shouldn't count on bounds to calculate pointer is in the over or not, but
//on avalonia event system, as renderer hit test will properly calculate whether to send
//mouse over events to button based on rendered bounds
//note: button also may have not rectangular shape and only renderer hit testing is reliable
bool clicked = false;
target.Click += (s, e) => clicked = true;
RaisePointerEnter(target, mouse);
RaisePointerMove(target, mouse);
RaisePointerPressed(target, mouse, 1, MouseButton.Left);
Assert.Equal(captured, target);
RaisePointerReleased(target, mouse, MouseButton.Left);
Assert.Equal(captured, null);
Assert.True(clicked);
}
private class TestButton : Button, IRenderRoot
{
public TestButton()
{
IsVisible = true;
}
public new Rect Bounds
{
get => base.Bounds;
set => base.Bounds = value;
}
public Size ClientSize => throw new NotImplementedException();
public IRenderer Renderer { get; set; }
public double RenderScaling => throw new NotImplementedException();
public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
public void Invalidate(Rect rect) => throw new NotImplementedException();
public Point PointToClient(Point point) => throw new NotImplementedException();
public Point PointToScreen(Point point) => throw new NotImplementedException();
}
private void RaisePointerPressed(Button button, IMouseDevice device, int clickCount, MouseButton mouseButton)
{
button.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
Source = button,
MouseButton = mouseButton,
ClickCount = clickCount,
Device = device,
});
}
private void RaisePointerReleased(Button button, IMouseDevice device, MouseButton mouseButton)
{
button.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
Source = button,
MouseButton = mouseButton,
Device = device,
});
}
private void RaisePointerEnter(Button button, IMouseDevice device)
{
button.RaiseEvent(new PointerEventArgs
{
RoutedEvent = InputElement.PointerEnterEvent,
Source = button,
Device = device,
});
}
private void RaisePointerLeave(Button button, IMouseDevice device)
{
button.RaiseEvent(new PointerEventArgs
{
RoutedEvent = InputElement.PointerLeaveEvent,
Source = button,
Device = device,
});
}
private void RaisePointerMove(Button button, IMouseDevice device)
{
button.RaiseEvent(new PointerEventArgs
{
RoutedEvent = InputElement.PointerMovedEvent,
Source = button,
Device = device,
});
}
private class TestCommand : ICommand private class TestCommand : ICommand
{ {
private bool _enabled; private bool _enabled;
@ -123,4 +327,4 @@ namespace Avalonia.Controls.UnitTests
} }
} }
} }
} }

4
tests/Avalonia.Controls.UnitTests/UserControlTests.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Styles = Styles =
{ {
new Style(x => x.OfType<ContentControl>()) new Style(x => x.OfType<UserControl>())
{ {
Setters = new[] Setters = new[]
{ {
@ -40,7 +40,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate() private FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<ContentControl>(parent => return new FuncControlTemplate<UserControl>(parent =>
{ {
return new Border return new Border
{ {

98
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs

@ -0,0 +1,98 @@
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Rendering
{
public class ImmediateRendererTests
{
[Fact]
public void AddDirty_Call_RenderRoot_Invalidate()
{
var visual = new Mock<Visual>();
var child = new Mock<Visual>() { CallBase = true };
var renderRoot = visual.As<IRenderRoot>();
visual.As<IVisual>().Setup(v => v.Bounds).Returns(new Rect(0, 0, 400, 400));
child.As<IVisual>().Setup(v => v.Bounds).Returns(new Rect(10, 10, 100, 100));
child.As<IVisual>().Setup(v => v.VisualParent).Returns(visual.Object);
var target = new ImmediateRenderer(visual.Object);
target.AddDirty(child.Object);
renderRoot.Verify(v => v.Invalidate(new Rect(10, 10, 100, 100)));
}
[Fact]
public void AddDirty_With_RenderTransform_Call_RenderRoot_Invalidate()
{
var visual = new Mock<Visual>();
var child = new Mock<Visual>() { CallBase = true };
var renderRoot = visual.As<IRenderRoot>();
visual.As<IVisual>().Setup(v => v.Bounds).Returns(new Rect(0, 0, 400, 400));
child.As<IVisual>().Setup(v => v.Bounds).Returns(new Rect(100, 100, 100, 100));
child.As<IVisual>().Setup(v => v.VisualParent).Returns(visual.Object);
child.Object.RenderTransform = new ScaleTransform() { ScaleX = 2, ScaleY = 2 };
var target = new ImmediateRenderer(visual.Object);
target.AddDirty(child.Object);
renderRoot.Verify(v => v.Invalidate(new Rect(50, 50, 200, 200)));
}
[Fact]
public void AddDirty_For_Child_Moved_Should_Invalidate_Previous_Bounds()
{
var visual = new Mock<Visual>() { CallBase = true };
var child = new Mock<Visual>() { CallBase = true };
var renderRoot = visual.As<IRenderRoot>();
var renderTarget = visual.As<IRenderTarget>();
renderRoot.Setup(r => r.CreateRenderTarget()).Returns(renderTarget.Object);
renderTarget.Setup(r => r.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>())).Returns(Mock.Of<IDrawingContextImpl>());
visual.As<IVisual>().Setup(v => v.Bounds).Returns(new Rect(0, 0, 400, 400));
visual.As<IVisual>().Setup(v => v.VisualChildren).Returns(new AvaloniaList<IVisual>() { child.As<IVisual>().Object });
Rect childBounds = new Rect(0, 0, 100, 100);
child.As<IVisual>().Setup(v => v.Bounds).Returns(() => childBounds);
child.As<IVisual>().Setup(v => v.VisualParent).Returns(visual.Object);
child.As<IVisual>().Setup(v => v.VisualChildren).Returns(new AvaloniaList<IVisual>());
var invalidationCalls = new List<Rect>();
renderRoot.Setup(v => v.Invalidate(It.IsAny<Rect>())).Callback<Rect>(v => invalidationCalls.Add(v));
var target = new ImmediateRenderer(visual.Object);
target.AddDirty(child.Object);
Assert.Equal(new Rect(0, 0, 100, 100), invalidationCalls[0]);
target.Paint(new Rect(0, 0, 100, 100));
//move child 100 pixels bottom/right
childBounds = new Rect(100, 100, 100, 100);
//renderer should invalidate old child bounds with new one
//as on old area there can be artifacts
target.AddDirty(child.Object);
//invalidate first old position
Assert.Equal(new Rect(0, 0, 100, 100), invalidationCalls[1]);
//then new position
Assert.Equal(new Rect(100, 100, 100, 100), invalidationCalls[2]);
}
}
}

44
tests/Avalonia.Visuals.UnitTests/VisualTests.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -192,5 +193,48 @@ namespace Avalonia.Visuals.UnitTests
Assert.Throws<InvalidOperationException>(() => root2.Child = child); Assert.Throws<InvalidOperationException>(() => root2.Child = child);
Assert.Empty(root2.GetVisualChildren()); Assert.Empty(root2.GetVisualChildren());
} }
[Fact]
public void TransformToVisual_Should_Work()
{
var child = new Decorator { Width = 100, Height = 100 };
var root = new TestRoot() { Child = child, Width = 400, Height = 400 };
root.Measure(Size.Infinity);
root.Arrange(new Rect(new Point(), root.DesiredSize));
var tr = child.TransformToVisual(root);
Assert.NotNull(tr);
var point = root.Bounds.TopLeft * tr;
//child is centered (400 - 100)/2
Assert.Equal(new Point(150, 150), point);
}
[Fact]
public void TransformToVisual_With_RenderTransform_Should_Work()
{
var child = new Decorator
{
Width = 100,
Height = 100,
RenderTransform = new ScaleTransform() { ScaleX = 2, ScaleY = 2 }
};
var root = new TestRoot() { Child = child, Width = 400, Height = 400 };
root.Measure(Size.Infinity);
root.Arrange(new Rect(new Point(), root.DesiredSize));
var tr = child.TransformToVisual(root);
Assert.NotNull(tr);
var point = root.Bounds.TopLeft * tr;
//child is centered (400 - 100*2 scale)/2
Assert.Equal(new Point(100, 100), point);
}
} }
} }

Loading…
Cancel
Save