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

1
build.cake

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

2
build/SharedVersion.props

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.7.0</Version>
<Version>0.7.1</Version>
<Copyright>Copyright 2018 &#169; The AvaloniaUI Project</Copyright>
<PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
<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;
};
AVNCOM(IAvnString, 17) : IUnknown
{
virtual HRESULT Pointer(void**retOut) = 0;
virtual HRESULT Length(int*ret) = 0;
};
AVNCOM(IAvnWindowBase, 02) : IUnknown
{
virtual HRESULT Show() = 0;
@ -210,7 +216,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
virtual HRESULT ShowDialog (IUnknown**ppv) = 0;
virtual HRESULT SetCanResize(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 SetWindowState(AvnWindowState state) = 0;
virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
@ -315,8 +321,8 @@ AVNCOM(IAvnScreens, 0e) : IUnknown
AVNCOM(IAvnClipboard, 0f) : IUnknown
{
virtual HRESULT GetText (void** retOut) = 0;
virtual HRESULT SetText (char* text) = 0;
virtual HRESULT GetText (IAvnString**ppv) = 0;
virtual HRESULT SetText (void* utf8Text) = 0;
virtual HRESULT Clear() = 0;
};

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

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.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 */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.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>"; };
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>"; };
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>"; };
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>"; };
@ -65,6 +68,8 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */,
37A4E71A2178846A00EACBCD /* headers */,
AB573DC3217605E400D389A2 /* gl.mm */,
5BF943652167AD1D009CAE35 /* cursor.h */,
@ -161,6 +166,7 @@
files = (
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.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"
launchStyle = "0"
useCustomWorkingDirectory = "YES"
customWorkingDirectory = "$PROJECT_DIR/../../../../samples/ControlCatalog"
customWorkingDirectory = "$PROJECT_DIR/../../../../samples/ControlCatalog.NetCore"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
@ -58,7 +58,7 @@
</MacroExpansion>
<CommandLineArguments>
<CommandLineArgument
argument = "bin/Debug/netcoreapp2.0/ControlCatalog.dll"
argument = "bin/Debug/netcoreapp2.0/ControlCatalog.NetCore.dll"
isEnabled = "YES">
</CommandLineArgument>
</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.
#include "common.h"
#include "AvnString.h"
class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT GetText (void** retOut) override
virtual HRESULT GetText (IAvnString**ppv) override
{
@autoreleasepool
{
NSString *str = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString];
*retOut = (void *)str.UTF8String;
if(ppv == nullptr)
{
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
{
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard clearContents];
[pasteBoard setString:@(text) forType:NSPasteboardTypeString];
[pasteBoard setString:[NSString stringWithUTF8String:(const char*)utf8String] forType:NSPasteboardTypeString];
}
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
{
_lastTitle = [NSString stringWithUTF8String:title];
_lastTitle = [NSString stringWithUTF8String:(const char*)utf8title];
[Window setTitle:_lastTitle];
[Window setTitleVisibility:NSWindowTitleVisible];
@ -963,7 +963,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (BOOL)performKeyEquivalent:(NSEvent *)event
{
return _lastKeyHandled;
bool result = _lastKeyHandled;
_lastKeyHandled = false;
return result;
}
- (void)keyDown:(NSEvent *)event

7
packages/Avalonia/Avalonia.csproj

@ -1,4 +1,4 @@
<Project Sdk="AggregatePackage.NuGet.Sdk">
<Project Sdk="AggregatePackage.NuGet.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net461;netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
@ -27,6 +27,11 @@
<Visible>false</Visible>
<BuildAction>None</BuildAction>
</_PackageFiles>
<_PackageFiles Include="Avalonia.props">
<PackagePath>build/Avalonia.props</PackagePath>
<Visible>false</Visible>
<BuildAction>None</BuildAction>
</_PackageFiles>
</ItemGroup>
</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 ReleasePlatform { get; private set; }
public string ReleaseConfiguration { get; private set; }
public string ReleaseBranchPrefix { get; private set; }
public string MSBuildSolution { get; private set; }
public bool IsLocalBuild { get; private set; }
public bool IsRunningOnUnix { get; private set; }
@ -18,6 +19,7 @@ public class Parameters
public bool IsPullRequest { get; private set; }
public bool IsMainRepo { get; private set; }
public bool IsMasterBranch { get; private set; }
public bool IsReleaseBranch { get; private set; }
public bool IsTagged { get; private set; }
public bool IsReleasable { get; private set; }
public bool IsMyGetRelease { get; private set; }
@ -46,8 +48,9 @@ public class Parameters
SkipTests = context.HasArgument("skip-tests");
// CONFIGURATION
MainRepo = "AvaloniaUI/Avalonia";
MainRepo = "https://github.com/AvaloniaUI/Avalonia";
MasterBranch = "master";
ReleaseBranchPrefix = "refs/heads/release/";
ReleaseConfiguration = "Release";
MSBuildSolution = "./dirs.proj";
@ -59,12 +62,14 @@ public class Parameters
IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts";
IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest;
IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, buildSystem.AppVeyor.Environment.Repository.Name);
IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, buildSystem.AppVeyor.Environment.Repository.Branch);
IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, context.EnvironmentVariable("BUILD_REPOSITORY_URI"));
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
&& !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name);
IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
IsMyGetRelease = !IsTagged && IsReleasable;
IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch;
// VERSION
Version = context.Argument("force-nuget-version", GetVersion());
@ -92,9 +97,13 @@ public class Parameters
}
else if (IsRunningOnAzure)
{
if(!IsNuGetRelease)
{
// Use AssemblyVersion with Build as version
Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta";
PublishTestResults = true;
}
PublishTestResults = true;
}
// DIRECTORIES

19
samples/ControlCatalog/App.xaml

@ -2,23 +2,16 @@
<Application.Styles>
<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"/>
<Style Selector="TextBlock.h1">
<Setter Property="Foreground" Value="#212121"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
<Setter Property="FontWeight" Value="Medium"/>
</Style>
<Style Selector="TextBlock.h2">
<Setter Property="Foreground" Value="#727272"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
</Style>
<Style Selector="TextBlock.h3">
<Setter Property="Foreground" Value="#a2a2a2"/>
<Setter Property="FontSize" Value="13"/>
</Style>
<StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
<Style Selector="TextBlock.h3">
<Setter Property="FontSize" Value="{DynamicResource FontSizeSmall}"/>
</Style>
<StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
</Application.Styles>
</Application>

2
samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml

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

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

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

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

@ -104,7 +104,12 @@ namespace Avalonia.Collections
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
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;
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.
using System;
using System.Linq;
using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -251,7 +253,10 @@ namespace Avalonia.Controls
IsPressed = false;
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();
}
@ -261,9 +266,9 @@ namespace Avalonia.Controls
protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status)
{
base.UpdateDataValidation(property, status);
if(property == CommandProperty)
if (property == CommandProperty)
{
if(status?.ErrorType == BindingErrorType.Error)
if (status?.ErrorType == BindingErrorType.Error)
{
IsEnabled = false;
}

2
src/Avalonia.Controls/DropDown.cs

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

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

@ -89,7 +89,7 @@ namespace Avalonia.Controls.Primitives
control.Clip = clip;
}
clip.Rect = bounds.Clip.TransformToAABB(-bounds.Transform);
clip.Rect = bounds.Bounds;
}
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)
{
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)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.Post(() =>
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawKeyEventArgs(
KeyboardDevice,
0,
@ -225,10 +225,10 @@ namespace Avalonia.Controls.Remote.Server
}
if(obj is TextInputEventMessage text)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.Post(() =>
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawTextInputEventArgs(
KeyboardDevice,
0,

15
src/Avalonia.Controls/ScrollViewer.cs

@ -4,6 +4,7 @@
using System;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
namespace Avalonia.Controls
{
@ -441,5 +442,19 @@ namespace Avalonia.Controls
RaisePropertyChanged(VerticalScrollBarValueProperty, 0, VerticalScrollBarValue);
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.Metadata;
using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -655,6 +656,8 @@ namespace Avalonia.Controls
if (_presenter != null && e.Device.Captured == _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);
}
}

2
src/Avalonia.Controls/UserControl.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
Type IStyleable.StyleKey => typeof(ContentControl);
Type IStyleable.StyleKey => typeof(UserControl);
/// <inheritdoc/>
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/local/bin/castxml')">/usr/local/bin/castxml</CastXmlPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- This is needed because Rider doesn't see generated files in obj for some reason -->
<SharpGenGeneratedCodeFolder>$(MSBuildThisFileDirectory)/Generated</SharpGenGeneratedCodeFolder>
</PropertyGroup>
<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<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<IWindowingPlatformGlFeature>().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
.Bind<AvaloniaNativeOptions>().ToConstant(opts);
}

16
src/Avalonia.Native/ClipboardImpl.cs

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Avalonia.Input.Platform;
using Avalonia.Native.Interop;
using Avalonia.Platform.Interop;
namespace Avalonia.Native
{
@ -24,12 +25,14 @@ namespace Avalonia.Native
return Task.CompletedTask;
}
public Task<string> GetTextAsync()
public unsafe Task<string> GetTextAsync()
{
var outPtr = _native.GetText();
var text = Marshal.PtrToStringAnsi(outPtr);
using (var text = _native.GetText())
{
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)
@ -38,7 +41,10 @@ namespace Avalonia.Native
if (text != null)
{
_native.SetText(text);
using (var buffer = new Utf8Buffer(text))
{
_native.SetText(buffer.DangerousGetHandle());
}
}
return Task.CompletedTask;

6
src/Avalonia.Native/WindowImpl.cs

@ -5,6 +5,7 @@ using System;
using Avalonia.Controls;
using Avalonia.Native.Interop;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
namespace Avalonia.Native
{
@ -68,7 +69,10 @@ namespace Avalonia.Native
public void SetTitle(string title)
{
_native.SetTitle(title);
using (var buffer = new Utf8Buffer(title))
{
_native.SetTitle(buffer.DangerousGetHandle());
}
}
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.TreeView.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.EmbeddableControlRoot.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"
Classes="repeat"
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"
Stretch="Uniform"
Fill="{DynamicResource ThemeForegroundLowBrush}" />
@ -21,11 +22,13 @@
Orientation="{TemplateBinding Orientation}">
<Track.DecreaseButton>
<RepeatButton Name="PART_PageUpButton"
Classes="repeattrack" />
Classes="repeattrack"
Focusable="False"/>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_PageDownButton"
Classes="repeattrack" />
Classes="repeattrack"
Focusable="False"/>
</Track.IncreaseButton>
<Thumb Name="thumb">
<Thumb.Template>
@ -38,7 +41,8 @@
<RepeatButton Name="PART_LineDownButton"
Classes="repeat"
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"
Stretch="Uniform"
Fill="{DynamicResource ThemeForegroundLowBrush}" />
@ -58,7 +62,8 @@
<RepeatButton Name="PART_LineUpButton"
Classes="repeat"
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"
Stretch="Uniform"
Fill="{DynamicResource ThemeForegroundLowBrush}" />
@ -72,11 +77,13 @@
Orientation="{TemplateBinding Orientation}">
<Track.DecreaseButton>
<RepeatButton Name="PART_PageUpButton"
Classes="repeattrack" />
Classes="repeattrack"
Focusable="False"/>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_PageDownButton"
Classes="repeattrack" />
Classes="repeattrack"
Focusable="False"/>
</Track.IncreaseButton>
<Thumb Name="thumb">
<Thumb.Template>
@ -89,7 +96,8 @@
<RepeatButton Name="PART_LineDownButton"
Classes="repeat"
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"
Stretch="Uniform"
Fill="{DynamicResource ThemeForegroundLowBrush}" />

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

@ -19,14 +19,16 @@
Value="{TemplateBinding HorizontalScrollBarValue, Mode=TwoWay}"
ViewportSize="{TemplateBinding HorizontalScrollBarViewportSize}"
Visibility="{TemplateBinding HorizontalScrollBarVisibility}"
Grid.Row="1"/>
Grid.Row="1"
Focusable="False"/>
<ScrollBar Name="verticalScrollBar"
Orientation="Vertical"
Maximum="{TemplateBinding VerticalScrollBarMaximum}"
Value="{TemplateBinding VerticalScrollBarValue, Mode=TwoWay}"
ViewportSize="{TemplateBinding VerticalScrollBarViewportSize}"
Visibility="{TemplateBinding VerticalScrollBarVisibility}"
Grid.Column="1"/>
Grid.Column="1"
Focusable="False"/>
</Grid>
</ControlTemplate>
</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">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
<Setter Property="Template">
<ControlTemplate>
@ -15,4 +16,4 @@
</Border>
</ControlTemplate>
</Setter>
</Style>
</Style>

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

@ -125,6 +125,20 @@ namespace Avalonia.Rendering
if (m.HasValue)
{
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);
}
}
@ -191,7 +205,7 @@ namespace Avalonia.Rendering
}
}
static IEnumerable<IVisual> HitTest(
private static IEnumerable<IVisual> HitTest(
IVisual visual,
Point p,
Func<IVisual, bool> filter)

23
src/Avalonia.Visuals/Visual.cs

@ -304,7 +304,7 @@ namespace Avalonia
{
var thisOffset = GetOffsetFrom(common, this);
var thatOffset = GetOffsetFrom(common, visual);
return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset);
return -thatOffset * thisOffset;
}
return null;
@ -454,13 +454,28 @@ namespace Avalonia
/// <param name="ancestor">The ancestor visual.</param>
/// <param name="visual">The visual.</param>
/// <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)
{
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;
if (visual == null)

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

@ -121,7 +121,7 @@ namespace Avalonia.Skia
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 ?
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 Xunit;
@ -82,13 +81,31 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.Equal(source, result);
}
[Fact]
public void CreateDerivedList_Handles_MoveRange()
[Theory]
[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));
source.MoveRange(1, 2, 0);
source.MoveRange(oldIndex, count, newIndex);
var result = target.Select(x => x.Value).ToList();

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

@ -1,7 +1,12 @@
using System;
using System.Windows.Input;
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;
namespace Avalonia.Controls.UnitTests
@ -92,6 +97,205 @@ namespace Avalonia.Controls.UnitTests
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 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 =
{
new Style(x => x.OfType<ContentControl>())
new Style(x => x.OfType<UserControl>())
{
Setters = new[]
{
@ -40,7 +40,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ContentControl>(parent =>
return new FuncControlTemplate<UserControl>(parent =>
{
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.Linq;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
@ -192,5 +193,48 @@ namespace Avalonia.Visuals.UnitTests
Assert.Throws<InvalidOperationException>(() => root2.Child = child);
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