Browse Source

Merge branch 'master' into feature/pushPopRenderOptions

pull/12734/head^2
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
d28557dcd0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  2. 84
      native/Avalonia.Native/src/OSX/PlatformRenderTimer.mm
  3. 1
      native/Avalonia.Native/src/OSX/common.h
  4. 11
      native/Avalonia.Native/src/OSX/main.mm
  5. 2
      readme.md
  6. 1
      samples/ControlCatalog/Pages/PointerCanvas.cs
  7. 1
      samples/ControlCatalog/Pages/PointerContactsTab.cs
  8. 1
      samples/ControlCatalog/Pages/PointersPage.xaml.cs
  9. 51
      src/Avalonia.Base/Animation/Transition.cs
  10. 100
      src/Avalonia.Base/Animation/TransitionBase.cs
  11. 1
      src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
  12. 15
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
  13. 4
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  14. 6
      src/Avalonia.Base/Input/InputElement.cs
  15. 8
      src/Avalonia.Base/Input/Pointer.cs
  16. 7
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  17. 109
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  18. 26
      src/Avalonia.Controls/Documents/Inline.cs
  19. 28
      src/Avalonia.Controls/ItemsControl.cs
  20. 2
      src/Avalonia.Controls/Notifications/NotificationCard.cs
  21. 11
      src/Avalonia.Controls/Primitives/AccessText.cs
  22. 7
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  23. 5
      src/Avalonia.Controls/Shapes/Polygon.cs
  24. 5
      src/Avalonia.Controls/Shapes/Polyline.cs
  25. 2
      src/Avalonia.Controls/TextBlock.cs
  26. 12
      src/Avalonia.Controls/ToggleSwitch.cs
  27. 10
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  28. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Bold.ttf
  29. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf
  30. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Light.ttf
  31. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Medium.ttf
  32. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Regular.ttf
  33. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.ttf
  34. BIN
      src/Avalonia.Fonts.Inter/Assets/Inter-Thin.ttf
  35. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  36. 58
      src/Avalonia.Native/AvaloniaNativeRenderTimer.cs
  37. 10
      src/Avalonia.Native/avn.idl
  38. 2
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  39. 6
      src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml
  40. 34
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
  41. 12
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  42. 11
      src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs
  43. 42
      src/Windows/Avalonia.Win32/Win32Platform.cs
  44. 26
      src/Windows/Avalonia.Win32/Win32PlatformOptions.cs
  45. 2
      src/Windows/Avalonia.Win32/WindowImpl.cs
  46. 34
      tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs
  47. 152
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  48. 21
      tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
  49. 18
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs
  50. 6
      tests/Avalonia.UnitTests/TouchTestHelper.cs

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

@ -59,6 +59,7 @@
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; }; BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; }; BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; }; ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; };
ED754D262A97306B0078B4DF /* PlatformRenderTimer.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */; };
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */; }; EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -122,6 +123,7 @@
BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = "<group>"; }; BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = "<group>"; };
BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; }; BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; };
ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformRenderTimer.mm; sourceTree = "<group>"; };
EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformSettings.mm; sourceTree = "<group>"; }; EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformSettings.mm; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -162,6 +164,7 @@
AB7A61E62147C814003C5833 = { AB7A61E62147C814003C5833 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */,
855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */, 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */,
8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */, 8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */,
8D300D68292E1E5D00320C49 /* AvnTextInputMethod.mm */, 8D300D68292E1E5D00320C49 /* AvnTextInputMethod.mm */,
@ -333,6 +336,7 @@
18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */, 18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */,
18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */, 18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */,
18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */, 18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */,
ED754D262A97306B0078B4DF /* PlatformRenderTimer.mm in Sources */,
1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */, 1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
18391AC16726CBC45856233B /* AvnWindow.mm in Sources */, 18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */, 18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,

84
native/Avalonia.Native/src/OSX/PlatformRenderTimer.mm

@ -0,0 +1,84 @@
#include "common.h"
class PlatformRenderTimer : public ComSingleObject<IAvnPlatformRenderTimer, &IID_IAvnPlatformRenderTimer>
{
private:
ComPtr<IAvnActionCallback> _callback;
CVDisplayLinkRef _displayLink;
public:
FORWARD_IUNKNOWN()
virtual HRESULT RegisterTick (
IAvnActionCallback* callback) override
{
START_COM_CALL;
@autoreleasepool
{
if (_displayLink != nil)
{
return E_UNEXPECTED;
}
_callback = callback;
auto result = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
if (result != 0)
{
return E_FAIL;
}
result = CVDisplayLinkSetOutputCallback(_displayLink, OnTick, this);
if (result != 0)
{
return E_FAIL;
}
}
return S_OK;
}
virtual void Start () override
{
START_COM_CALL;
@autoreleasepool
{
if (CVDisplayLinkIsRunning(_displayLink) == false) {
CVDisplayLinkStart(_displayLink);
}
}
}
virtual void Stop () override
{
START_COM_CALL;
@autoreleasepool
{
if (CVDisplayLinkIsRunning(_displayLink) == true) {
CVDisplayLinkStop(_displayLink);
}
}
}
virtual bool RunsInBackground () override
{
START_COM_CALL;
@autoreleasepool
{
return true;
}
}
static CVReturn OnTick(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext)
{
PlatformRenderTimer *object = (PlatformRenderTimer *)displayLinkContext;
object->_callback->Run();
return kCVReturnSuccess;
}
};
extern IAvnPlatformRenderTimer* CreatePlatformRenderTimer()
{
return new PlatformRenderTimer();
}

1
native/Avalonia.Native/src/OSX/common.h

@ -32,6 +32,7 @@ extern IAvnApplicationCommands* CreateApplicationCommands();
extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition(); extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent); extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern IAvnPlatformSettings* CreatePlatformSettings(); extern IAvnPlatformSettings* CreatePlatformSettings();
extern IAvnPlatformRenderTimer* CreatePlatformRenderTimer();
extern void SetAppMenu(IAvnMenu *menu); extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu); extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu (); extern IAvnMenu* GetAppMenu ();

11
native/Avalonia.Native/src/OSX/main.mm

@ -446,6 +446,17 @@ public:
return S_OK; return S_OK;
} }
} }
virtual HRESULT CreatePlatformRenderTimer(IAvnPlatformRenderTimer** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreatePlatformRenderTimer();
return S_OK;
}
}
}; };
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

2
readme.md

@ -2,7 +2,7 @@
![Header](https://user-images.githubusercontent.com/552074/235865745-2a8e7274-4f66-4f77-8f05-feeb76e7d478.png) ![Header](https://user-images.githubusercontent.com/552074/235865745-2a8e7274-4f66-4f77-8f05-feeb76e7d478.png)
[![Telegram](https://raw.githubusercontent.com/Patrolavia/telegram-badge/master/chat.svg)](https://t.me/Avalonia) [![Telegram](https://raw.githubusercontent.com/Patrolavia/telegram-badge/master/chat.svg)](https://t.me/Avalonia)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Discord](https://img.shields.io/badge/discord-join%20chat-46BC99)]( https://aka.ms/dotnet-discord) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Discord](https://img.shields.io/badge/discord-join%20chat-46BC99)]( https://aka.ms/dotnet-discord) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://img.shields.io/opencollective/backers/Avalonia?logo=opencollective)](#backers) [![Sponsors on Open Collective](https://img.shields.io/opencollective/sponsors/Avalonia?logo=opencollective)](#sponsors) [![GitHub Sponsors](https://img.shields.io/github/sponsors/AvaloniaUI?logo=github)](https://github.com/sponsors/AvaloniaUI) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg)
<br /> <br />
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg)

1
samples/ControlCatalog/Pages/PointerCanvas.cs

@ -74,6 +74,7 @@ public class PointerCanvas : Control
public void HandleEvent(PointerEventArgs e, Visual v) public void HandleEvent(PointerEventArgs e, Visual v)
{ {
e.Handled = true; e.Handled = true;
e.PreventGestureRecognition();
var currentPoint = e.GetCurrentPoint(v); var currentPoint = e.GetCurrentPoint(v);
if (e.RoutedEvent == PointerPressedEvent) if (e.RoutedEvent == PointerPressedEvent)
AddPoint(currentPoint.Position, Brushes.Green, 10); AddPoint(currentPoint.Position, Brushes.Green, 10);

1
samples/ControlCatalog/Pages/PointerContactsTab.cs

@ -72,6 +72,7 @@ public class PointerContactsTab : Control
UpdatePointer(e); UpdatePointer(e);
e.Pointer.Capture(this); e.Pointer.Capture(this);
e.Handled = true; e.Handled = true;
e.PreventGestureRecognition();
base.OnPointerPressed(e); base.OnPointerPressed(e);
} }

1
samples/ControlCatalog/Pages/PointersPage.xaml.cs

@ -72,6 +72,7 @@ Position: ??? ???";
{ {
e.Pointer.Capture(sender as Border); e.Pointer.Capture(sender as Border);
e.Handled = true; e.Handled = true;
e.PreventGestureRecognition();
} }
private void InitializeComponent() private void InitializeComponent()

51
src/Avalonia.Base/Animation/Transition.cs

@ -1,53 +1,22 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Animation.Easings;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
/// Defines how a property should be animated using a transition. /// Defines how a property should be animated using a transition.
/// </summary> /// </summary>
public abstract class Transition<T> : AvaloniaObject, ITransition public abstract class Transition<T> : TransitionBase
{ {
private AvaloniaProperty? _prop; static Transition()
/// <summary>
/// Gets or sets the duration of the transition.
/// </summary>
public TimeSpan Duration { get; set; }
/// <summary>
/// Gets or sets delay before starting the transition.
/// </summary>
public TimeSpan Delay { get; set; } = TimeSpan.Zero;
/// <summary>
/// Gets the easing class to be used.
/// </summary>
public Easing Easing { get; set; } = new LinearEasing();
/// <inheritdocs/>
[DisallowNull]
public AvaloniaProperty? Property
{ {
get PropertyProperty.Changed.AddClassHandler<Transition<T>>((x, e) => x.OnPropertyPropertyChanged(e));
{
return _prop;
}
set
{
if (!(value.PropertyType.IsAssignableFrom(typeof(T))))
throw new InvalidCastException
($"Invalid property type \"{typeof(T).Name}\" for this transition: {GetType().Name}.");
_prop = value;
}
} }
AvaloniaProperty ITransition.Property private void OnPropertyPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{ {
get => Property ?? throw new InvalidOperationException("Transition has no property specified."); if ((e.NewValue is AvaloniaProperty newValue) && !newValue.PropertyType.IsAssignableFrom(typeof(T)))
set => Property = value; throw new InvalidCastException
($"Invalid property type \"{typeof(T).Name}\" for this transition: {GetType().Name}.");
} }
/// <summary> /// <summary>
@ -55,11 +24,7 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
internal abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue); internal abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue);
/// <inheritdocs/> internal override IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
IDisposable ITransition.Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
=> Apply(control, clock, oldValue, newValue);
internal virtual IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
{ {
if (Property is null) if (Property is null)
throw new InvalidOperationException("Transition has no property specified."); throw new InvalidOperationException("Transition has no property specified.");

100
src/Avalonia.Base/Animation/TransitionBase.cs

@ -0,0 +1,100 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Animation.Easings;
namespace Avalonia.Animation
{
/// <summary>
/// Defines how a property should be animated using a transition.
/// </summary>
public abstract class TransitionBase : AvaloniaObject, ITransition
{
/// <summary>
/// Defines the <see cref="Duration"/> property.
/// </summary>
public static readonly DirectProperty<TransitionBase, TimeSpan> DurationProperty =
AvaloniaProperty.RegisterDirect<TransitionBase, TimeSpan>(
nameof(Duration),
o => o._duration,
(o, v) => o._duration = v);
/// <summary>
/// Defines the <see cref="Delay"/> property.
/// </summary>
public static readonly DirectProperty<TransitionBase, TimeSpan> DelayProperty =
AvaloniaProperty.RegisterDirect<TransitionBase, TimeSpan>(
nameof(Delay),
o => o._delay,
(o, v) => o._delay = v);
/// <summary>
/// Defines the <see cref="Easing"/> property.
/// </summary>
public static readonly DirectProperty<TransitionBase, Easing> EasingProperty =
AvaloniaProperty.RegisterDirect<TransitionBase, Easing>(
nameof(Easing),
o => o._easing,
(o, v) => o._easing = v);
/// <summary>
/// Defines the <see cref="Property"/> property.
/// </summary>
public static readonly DirectProperty<TransitionBase, AvaloniaProperty?> PropertyProperty =
AvaloniaProperty.RegisterDirect<TransitionBase, AvaloniaProperty?>(
nameof(Property),
o => o._prop,
(o, v) => o._prop = v);
private TimeSpan _duration;
private TimeSpan _delay = TimeSpan.Zero;
private Easing _easing = new LinearEasing();
private AvaloniaProperty? _prop;
/// <summary>
/// Gets or sets the duration of the transition.
/// </summary>
public TimeSpan Duration
{
get { return _duration; }
set { SetAndRaise(DurationProperty, ref _duration, value); }
}
/// <summary>
/// Gets or sets delay before starting the transition.
/// </summary>
public TimeSpan Delay
{
get { return _delay; }
set { SetAndRaise(DelayProperty, ref _delay, value); }
}
/// <summary>
/// Gets the easing class to be used.
/// </summary>
public Easing Easing
{
get { return _easing; }
set { SetAndRaise(EasingProperty, ref _easing, value); }
}
/// <inheritdocs/>
[DisallowNull]
public AvaloniaProperty? Property
{
get { return _prop; }
set { SetAndRaise(PropertyProperty, ref _prop, value); }
}
AvaloniaProperty ITransition.Property
{
get => Property ?? throw new InvalidOperationException("Transition has no property specified.");
set => Property = value;
}
/// <inheritdocs/>
IDisposable ITransition.Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
=> Apply(control, clock, oldValue, newValue);
internal abstract IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue);
}
}

1
src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs

@ -71,6 +71,7 @@ public class Rotate3DTransition: PageSlide
{ {
Easing = SlideOutEasing, Easing = SlideOutEasing,
Duration = Duration, Duration = Duration,
FillMode = FillMode.Forward,
Children = Children =
{ {
CreateKeyFrame(0d, 0d, 2), CreateKeyFrame(0d, 0d, 2),

15
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs

@ -1,3 +1,4 @@
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
@ -59,6 +60,20 @@ namespace Avalonia.Input.GestureRecognizers
return e.Handled; return e.Handled;
} }
internal void HandleCaptureLost(IPointer pointer)
{
if (_recognizers == null || pointer is not Pointer p)
return;
foreach (var r in _recognizers)
{
if (p.CapturedGestureRecognizer == r)
continue;
r.PointerCaptureLostInternal(pointer);
}
}
internal bool HandlePointerReleased(PointerReleasedEventArgs e) internal bool HandlePointerReleased(PointerReleasedEventArgs e)
{ {
if (_recognizers == null) if (_recognizers == null)

4
src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs

@ -1,4 +1,5 @@
using Avalonia.Input.GestureRecognizers; using System.Diagnostics;
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -110,6 +111,7 @@ namespace Avalonia.Input
_secondContact = null; _secondContact = null;
} }
Target?.RaiseEvent(new PinchEndedEventArgs()); Target?.RaiseEvent(new PinchEndedEventArgs());
} }
} }

6
src/Avalonia.Base/Input/InputElement.cs

@ -230,6 +230,7 @@ namespace Avalonia.Input
PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerMoved(e), handledEventsToo: true); PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerMoved(e), handledEventsToo: true);
PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerPressed(e), handledEventsToo: true); PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerPressed(e), handledEventsToo: true);
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerReleased(e), handledEventsToo: true); PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerReleased(e), handledEventsToo: true);
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerCaptureLost(e), handledEventsToo: true);
} }
public InputElement() public InputElement()
@ -615,6 +616,11 @@ namespace Avalonia.Input
} }
} }
private void OnGesturePointerCaptureLost(PointerCaptureLostEventArgs e)
{
_gestureRecognizers?.HandleCaptureLost(e.Pointer);
}
private void OnGesturePointerPressed(PointerPressedEventArgs e) private void OnGesturePointerPressed(PointerPressedEventArgs e)
{ {
if (!e.IsGestureRecognitionSkipped) if (!e.IsGestureRecognitionSkipped)

8
src/Avalonia.Base/Input/Pointer.cs

@ -91,12 +91,16 @@ namespace Avalonia.Input
internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer) internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer)
{ {
if (CapturedGestureRecognizer != gestureRecognizer) if (CapturedGestureRecognizer != gestureRecognizer)
{
CapturedGestureRecognizer?.PointerCaptureLostInternal(this); CapturedGestureRecognizer?.PointerCaptureLostInternal(this);
}
CapturedGestureRecognizer = gestureRecognizer;
if (gestureRecognizer != null) if (gestureRecognizer != null)
{
Capture(null); Capture(null);
}
CapturedGestureRecognizer = gestureRecognizer;
} }
} }
} }

7
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -1278,14 +1278,11 @@ namespace Avalonia.Media.TextFormatting
var start = GetParagraphOffsetX(width, widthIncludingWhitespace); var start = GetParagraphOffsetX(width, widthIncludingWhitespace);
var overhangLeading = Math.Max(0, bounds.Left - start); var overhangLeading = Math.Max(0, bounds.Left - start);
var overhangTrailing = Math.Max(0, bounds.Width - widthIncludingWhitespace); var overhangTrailing = Math.Max(0, bounds.Width - widthIncludingWhitespace);
var hasOverflowed = overhangLeading + widthIncludingWhitespace + overhangTrailing > _paragraphWidth; var hasOverflowed = width > _paragraphWidth;
if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight)) if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight))
{ {
if (lineHeight > height) height = lineHeight;
{
height = lineHeight;
}
} }
return new TextLineMetrics return new TextLineMetrics

109
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -1,4 +1,4 @@
// (c) Copyright Microsoft Corporation. // (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL). // This source is subject to the Microsoft Public License (Ms-PL).
// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved. // All other rights reserved.
@ -165,6 +165,16 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private bool _allowWrite; private bool _allowWrite;
/// <summary>
/// A boolean indicating if a cancellation was requested
/// </summary>
private bool _cancelRequested;
/// <summary>
/// A boolean indicating if filtering is in action
/// </summary>
private bool _filterInAction;
/// <summary> /// <summary>
/// The TextBox template part. /// The TextBox template part.
/// </summary> /// </summary>
@ -1379,6 +1389,15 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private void RefreshView() private void RefreshView()
{ {
// If we have a running filter, trigger a request first
if (_filterInAction)
{
_cancelRequested = true;
}
// Indicate that filtering is ongoing
_filterInAction = true;
if (_items == null) if (_items == null)
{ {
ClearView(); ClearView();
@ -1391,79 +1410,65 @@ namespace Avalonia.Controls
// Determine if any filtering mode is on // Determine if any filtering mode is on
bool stringFiltering = TextFilter != null; bool stringFiltering = TextFilter != null;
bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null; bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null;
int view_index = 0;
int view_count = _view!.Count;
List<object> items = _items; List<object> items = _items;
// cache properties
var textFilter = TextFilter;
var itemFilter = ItemFilter;
var _newViewItems = new Collection<object>();
// if the mode is objectFiltering and itemFilter is null, we throw an exception
if (objectFiltering && itemFilter is null)
{
// indicate that filtering is not ongoing anymore
_filterInAction = false;
_cancelRequested = false;
throw new Exception(
"ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom");
}
foreach (object item in items) foreach (object item in items)
{ {
// Exit the fitter when requested if cancellation is requested
if (_cancelRequested)
{
return;
}
bool inResults = !(stringFiltering || objectFiltering); bool inResults = !(stringFiltering || objectFiltering);
if (!inResults) if (!inResults)
{ {
if (stringFiltering) if (stringFiltering)
{ {
inResults = TextFilter!(text, FormatValue(item)); inResults = textFilter!(text, FormatValue(item));
} }
else else if (objectFiltering)
{ {
if (ItemFilter is null) inResults = itemFilter!(text, item);
{
throw new Exception("ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom");
}
else
{
inResults = ItemFilter(text, item);
}
} }
} }
if (view_count > view_index && inResults && _view[view_index] == item) if (inResults)
{ {
// Item is still in the view _newViewItems.Add(item);
view_index++;
}
else if (inResults)
{
// Insert the item
if (view_count > view_index && _view[view_index] != item)
{
// Replace item
// Unfortunately replacing via index throws a fatal
// exception: View[view_index] = item;
// Cost: O(n) vs O(1)
_view.RemoveAt(view_index);
_view.Insert(view_index, item);
view_index++;
}
else
{
// Add the item
if (view_index == view_count)
{
// Constant time is preferred (Add).
_view.Add(item);
}
else
{
_view.Insert(view_index, item);
}
view_index++;
view_count++;
}
}
else if (view_count > view_index && _view[view_index] == item)
{
// Remove the item
_view.RemoveAt(view_index);
view_count--;
} }
} }
_view?.Clear();
_view?.AddRange(_newViewItems);
// Clear the evaluator to discard a reference to the last item // Clear the evaluator to discard a reference to the last item
if (_valueBindingEvaluator != null) if (_valueBindingEvaluator != null)
{ {
_valueBindingEvaluator.ClearDataContext(); _valueBindingEvaluator.ClearDataContext();
} }
// indicate that filtering is not ongoing anymore
_filterInAction = false;
_cancelRequested = false;
} }
/// <summary> /// <summary>

26
src/Avalonia.Controls/Documents/Inline.cs

@ -10,12 +10,14 @@ namespace Avalonia.Controls.Documents
/// </summary> /// </summary>
public abstract class Inline : TextElement public abstract class Inline : TextElement
{ {
// TODO12: change the field type to an AttachedProperty for consistency (breaking change)
/// <summary> /// <summary>
/// AvaloniaProperty for <see cref="TextDecorations" /> property. /// AvaloniaProperty for <see cref="TextDecorations" /> property.
/// </summary> /// </summary>
public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty = public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty =
AvaloniaProperty.Register<Inline, TextDecorationCollection?>( AvaloniaProperty.RegisterAttached<Inline, Inline, TextDecorationCollection?>(
nameof(TextDecorations)); nameof(TextDecorations),
inherits: true);
/// <summary> /// <summary>
/// AvaloniaProperty for <see cref="BaselineAlignment" /> property. /// AvaloniaProperty for <see cref="BaselineAlignment" /> property.
@ -43,7 +45,27 @@ namespace Avalonia.Controls.Documents
get { return GetValue(BaselineAlignmentProperty); } get { return GetValue(BaselineAlignmentProperty); }
set { SetValue(BaselineAlignmentProperty, value); } set { SetValue(BaselineAlignmentProperty, value); }
} }
/// <summary>
/// Gets the value of the attached <see cref="TextDecorationsProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font style.</returns>
public static TextDecorationCollection? GetTextDecorations(Control control)
{
return control.GetValue(TextDecorationsProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="TextDecorationsProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetTextDecorations(Control control, TextDecorationCollection? value)
{
control.SetValue(TextDecorationsProperty, value);
}
internal abstract void BuildTextRun(IList<TextRun> textRuns); internal abstract void BuildTextRun(IList<TextRun> textRuns);
internal abstract void AppendText(StringBuilder stringBuilder); internal abstract void AppendText(StringBuilder stringBuilder);

28
src/Avalonia.Controls/ItemsControl.cs

@ -67,6 +67,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty = public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding)); AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
private static readonly AttachedProperty<ControlTheme?> AppliedItemContainerTheme =
AvaloniaProperty.RegisterAttached<ItemsControl, Control, ControlTheme?>("AppliedItemContainerTheme");
/// <summary> /// <summary>
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item. /// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary> /// </summary>
@ -663,13 +666,26 @@ namespace Avalonia.Controls
internal void PrepareItemContainer(Control container, object? item, int index) internal void PrepareItemContainer(Control container, object? item, int index)
{ {
var itemContainerTheme = ItemContainerTheme; // If the container has no theme set, or we've already applied our ItemContainerTheme
// (and it hasn't changed since) then we're in control of the container's Theme and may
if (itemContainerTheme is not null && // need to update it.
!container.IsSet(ThemeProperty) && if (!container.IsSet(ThemeProperty) || container.GetValue(AppliedItemContainerTheme) == container.Theme)
StyledElement.GetStyleKey(container) == itemContainerTheme.TargetType)
{ {
container.Theme = itemContainerTheme; var itemContainerTheme = ItemContainerTheme;
if (itemContainerTheme?.TargetType?.IsAssignableFrom(GetStyleKey(container)) == true)
{
// We have an ItemContainerTheme and it matches the container. Set the Theme
// property, and mark the container as having had ItemContainerTheme applied.
container.SetCurrentValue(ThemeProperty, itemContainerTheme);
container.SetValue(AppliedItemContainerTheme, itemContainerTheme);
}
else
{
// Otherwise clear the theme and the AppliedItemContainerTheme property.
container.ClearValue(ThemeProperty);
container.ClearValue(AppliedItemContainerTheme);
}
} }
if (item is not Control) if (item is not Control)

2
src/Avalonia.Controls/Notifications/NotificationCard.cs

@ -39,7 +39,7 @@ namespace Avalonia.Controls.Notifications
this.GetObservable(ContentProperty) this.GetObservable(ContentProperty)
.Subscribe(x => .Subscribe(x =>
{ {
if (x is Notification notification) if (x is INotification notification)
{ {
switch (notification.Type) switch (notification.Type)
{ {

11
src/Avalonia.Controls/Primitives/AccessText.cs

@ -3,6 +3,7 @@ using Avalonia.Input;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using System;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -68,11 +69,15 @@ namespace Avalonia.Controls.Primitives
if (underscore != -1 && ShowAccessKey) if (underscore != -1 && ShowAccessKey)
{ {
var rect = TextLayout!.HitTestTextPosition(underscore); var rect = TextLayout!.HitTestTextPosition(underscore);
var offset = new Vector(0, -1.5);
var x1 = Math.Round(rect.Left, MidpointRounding.AwayFromZero);
var x2 = Math.Round(rect.Right, MidpointRounding.AwayFromZero);
var y = Math.Round(rect.Bottom, MidpointRounding.AwayFromZero) - 1.5;
context.DrawLine( context.DrawLine(
new Pen(Foreground, 1), new Pen(Foreground, 1),
rect.BottomLeft + offset, new Point(x1, y),
rect.BottomRight + offset); new Point(x2, y));
} }
} }

7
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -110,6 +110,13 @@ namespace Avalonia.Controls.Primitives
get get
{ {
var rc = new Rect(default, _overlayLayer.AvailableSize); var rc = new Rect(default, _overlayLayer.AvailableSize);
var topLevel = TopLevel.GetTopLevel(this);
if(topLevel != null)
{
var padding = topLevel.InsetsManager?.SafeAreaPadding ?? default;
rc = rc.Deflate(padding);
}
return new[] {new ManagedPopupPositionerScreenInfo(rc, rc)}; return new[] {new ManagedPopupPositionerScreenInfo(rc, rc)};
} }
} }

5
src/Avalonia.Controls/Shapes/Polygon.cs

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Data;
namespace Avalonia.Controls.Shapes namespace Avalonia.Controls.Shapes
{ {
@ -15,9 +16,9 @@ namespace Avalonia.Controls.Shapes
public Polygon() public Polygon()
{ {
Points = new Points(); SetValue(PointsProperty, new Points(), BindingPriority.Template);
} }
public IList<Point> Points public IList<Point> Points
{ {
get => GetValue(PointsProperty); get => GetValue(PointsProperty);

5
src/Avalonia.Controls/Shapes/Polyline.cs

@ -1,9 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Data;
namespace Avalonia.Controls.Shapes namespace Avalonia.Controls.Shapes
{ {
public class Polyline: Shape public class Polyline : Shape
{ {
public static readonly StyledProperty<IList<Point>> PointsProperty = public static readonly StyledProperty<IList<Point>> PointsProperty =
AvaloniaProperty.Register<Polyline, IList<Point>>("Points"); AvaloniaProperty.Register<Polyline, IList<Point>>("Points");
@ -16,7 +17,7 @@ namespace Avalonia.Controls.Shapes
public Polyline() public Polyline()
{ {
Points = new Points(); SetValue(PointsProperty, new Points(), BindingPriority.Template);
} }
public IList<Point> Points public IList<Point> Points

2
src/Avalonia.Controls/TextBlock.cs

@ -135,7 +135,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="TextDecorations"/> property. /// Defines the <see cref="TextDecorations"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty = public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty =
AvaloniaProperty.Register<TextBlock, TextDecorationCollection?>(nameof(TextDecorations)); Inline.TextDecorationsProperty.AddOwner<TextBlock>();
/// <summary> /// <summary>
/// Defines the <see cref="Inlines"/> property. /// Defines the <see cref="Inlines"/> property.

12
src/Avalonia.Controls/ToggleSwitch.cs

@ -243,10 +243,6 @@ namespace Avalonia.Controls
} }
UpdateKnobTransitions(); UpdateKnobTransitions();
} }
else
{
base.Toggle();
}
_isDragging = false; _isDragging = false;
@ -276,14 +272,6 @@ namespace Avalonia.Controls
} }
} }
protected override void Toggle()
{
if ((_switchKnob != null) && (!_switchKnob.IsPointerOver))
{
base.Toggle();
}
}
private void UpdateKnobPos(bool value) private void UpdateKnobPos(bool value)
{ {
if ((_switchKnob != null) && (_knobsPanel != null)) if ((_switchKnob != null) && (_knobsPanel != null))

10
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -29,7 +29,7 @@ namespace Avalonia.DesignerSupport
if (assemblyPath != null) if (assemblyPath != null)
{ {
if (xamlFileProjectPath == null) if (xamlFileProjectPath == null)
xamlFileProjectPath = "/Designer/Fake.xaml"; xamlFileProjectPath = "/Fake.xaml";
//Fabricate fake Uri //Fabricate fake Uri
baseUri = baseUri =
new Uri($"avares://{Path.GetFileNameWithoutExtension(assemblyPath)}{xamlFileProjectPath}"); new Uri($"avares://{Path.GetFileNameWithoutExtension(assemblyPath)}{xamlFileProjectPath}");
@ -43,7 +43,7 @@ namespace Avalonia.DesignerSupport
{ {
LocalAssembly = localAsm, LocalAssembly = localAsm,
DesignMode = true, DesignMode = true,
UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue ) && parsedValue UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue) && parsedValue
}); });
var style = loaded as IStyle; var style = loaded as IStyle;
var resources = loaded as ResourceDictionary; var resources = loaded as ResourceDictionary;
@ -90,14 +90,14 @@ namespace Avalonia.DesignerSupport
}; };
} }
else if (loaded is Application) else if (loaded is Application)
control = new TextBlock {Text = "Application can't be previewed in design view"}; control = new TextBlock { Text = "Application can't be previewed in design view" };
else else
control = (Control) loaded; control = (Control)loaded;
window = control as Window; window = control as Window;
if (window == null) if (window == null)
{ {
window = new Window() {Content = (Control)control}; window = new Window() { Content = (Control)control };
} }
if (window.PlatformImpl is OffscreenTopLevelImplBase offscreenImpl) if (window.PlatformImpl is OffscreenTopLevelImplBase offscreenImpl)

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Bold.ttf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Light.ttf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Medium.ttf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Regular.ttf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.ttf

Binary file not shown.

BIN
src/Avalonia.Fonts.Inter/Assets/Inter-Thin.ttf

Binary file not shown.

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -108,7 +108,7 @@ namespace Avalonia.Native
.Bind<IPlatformSettings>().ToConstant(new NativePlatformSettings(_factory.CreatePlatformSettings())) .Bind<IPlatformSettings>().ToConstant(new NativePlatformSettings(_factory.CreatePlatformSettings()))
.Bind<IWindowingPlatform>().ToConstant(this) .Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) .Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) .Bind<IRenderTimer>().ToConstant(new AvaloniaNativeRenderTimer(_factory.CreatePlatformRenderTimer()))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory)) .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform) .Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform)

58
src/Avalonia.Native/AvaloniaNativeRenderTimer.cs

@ -0,0 +1,58 @@
using System;
using System.Diagnostics;
using Avalonia.Native.Interop;
using Avalonia.Rendering;
#nullable enable
namespace Avalonia.Native;
internal sealed class AvaloniaNativeRenderTimer : NativeCallbackBase, IRenderTimer, IAvnActionCallback
{
private readonly IAvnPlatformRenderTimer _platformRenderTimer;
private readonly Stopwatch _stopwatch;
private Action<TimeSpan>? _tick;
private int _subscriberCount;
private bool registered;
public AvaloniaNativeRenderTimer(IAvnPlatformRenderTimer platformRenderTimer)
{
_platformRenderTimer = platformRenderTimer;
_stopwatch = Stopwatch.StartNew();
}
public event Action<TimeSpan> Tick
{
add
{
_tick += value;
if (!registered)
{
registered = true;
_platformRenderTimer.RegisterTick(this);
}
if (_subscriberCount++ == 0)
{
_platformRenderTimer.Start();
}
}
remove
{
if (--_subscriberCount == 0)
{
_platformRenderTimer.Stop();
}
_tick -= value;
}
}
public bool RunsInBackground => _platformRenderTimer.RunsInBackground().FromComBool();
public void Run()
{
_tick?.Invoke(_stopwatch.Elapsed);
}
}

10
src/Avalonia.Native/avn.idl

@ -507,6 +507,7 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreateApplicationCommands(IAvnApplicationCommands** ppv); HRESULT CreateApplicationCommands(IAvnApplicationCommands** ppv);
HRESULT CreatePlatformSettings(IAvnPlatformSettings** ppv); HRESULT CreatePlatformSettings(IAvnPlatformSettings** ppv);
HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv); HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv);
HRESULT CreatePlatformRenderTimer(IAvnPlatformRenderTimer** ppv);
} }
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] [uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]
@ -999,3 +1000,12 @@ interface IAvnPlatformBehaviorInhibition : IUnknown
{ {
void SetInhibitAppSleep(bool inhibitAppSleep, char* reason); void SetInhibitAppSleep(bool inhibitAppSleep, char* reason);
} }
[uuid(22edf20d-5803-2d3f-9247-b4842e5e9322)]
interface IAvnPlatformRenderTimer : IUnknown
{
HRESULT RegisterTick(IAvnActionCallback* callback);
void Start();
void Stop();
bool RunsInBackground();
}

2
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -108,8 +108,6 @@ namespace Avalonia.OpenGL.Controls
_visual.Size = new Vector(Bounds.Width, Bounds.Height); _visual.Size = new Vector(Bounds.Width, Bounds.Height);
_visual.Surface = _resources.Surface; _visual.Surface = _resources.Surface;
ElementComposition.SetElementChildVisual(this, _visual); ElementComposition.SetElementChildVisual(this, _visual);
using (_resources.Context.MakeCurrent())
OnOpenGlInit(_resources.Context.GlInterface);
return true; return true;
} }

6
src/Avalonia.Themes.Fluent/Controls/DropDownButton.xaml

@ -71,6 +71,9 @@
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" /> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> <Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style> </Style>
<Style Selector="^:pointerover /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<!-- Pressed State --> <!-- Pressed State -->
<Style Selector="^:pressed"> <Style Selector="^:pressed">
@ -82,6 +85,9 @@
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" /> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> <Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style> </Style>
<Style Selector="^:pressed /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<!-- Disabled State --> <!-- Disabled State -->
<Style Selector="^:disabled /template/ Border#RootBorder"> <Style Selector="^:disabled /template/ Border#RootBorder">

34
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs

@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using XamlX.Ast; using XamlX.Ast;
using XamlX.Transform; using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem; using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
@ -22,26 +23,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IXamlAstTypeReference targetType; IXamlAstTypeReference targetType;
var templatableBaseType = context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control"); var templatableBaseType = context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control");
if ((tt?.Values.FirstOrDefault() is XamlTypeExtensionNode tn)) targetType = tt?.Values.FirstOrDefault() switch
{
targetType = tn.Value;
}
else
{ {
var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>() XamlTypeExtensionNode tn => tn.Value,
.FirstOrDefault(); XamlAstTextNode textNode => TypeReferenceResolver.ResolveType(context, textNode.Text, false, textNode, true),
if (parentScope?.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) _ when context.ParentNodes()
targetType = parentScope.TargetType; .OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlAstObjectNode directParentNode .FirstOrDefault() is { ScopeType: AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style } parentScope => parentScope.TargetType,
&& templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType())) _ when context.ParentNodes().Skip(1).FirstOrDefault() is XamlAstObjectNode directParentNode
targetType = directParentNode.Type; && templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType()) => directParentNode.Type,
else _ => new XamlAstClrTypeReference(node,
targetType = new XamlAstClrTypeReference(node, templatableBaseType, false)
templatableBaseType, false); };
}
return new AvaloniaXamlIlTargetTypeMetadataNode(on, targetType, return new AvaloniaXamlIlTargetTypeMetadataNode(on, targetType,
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate); AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
@ -59,7 +53,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ControlTemplate, ControlTemplate,
Transitions Transitions
} }
public AvaloniaXamlIlTargetTypeMetadataNode(IXamlAstValueNode value, IXamlAstTypeReference targetType, public AvaloniaXamlIlTargetTypeMetadataNode(IXamlAstValueNode value, IXamlAstTypeReference targetType,
ScopeTypes type) ScopeTypes type)
: base(value, value) : base(value, value)

12
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -32,14 +32,20 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var stack = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>(); var stack = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>();
var provideTarget = serviceProvider.GetService<IProvideValueTarget>(); var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
var targetObject = provideTarget?.TargetObject; var targetObject = provideTarget?.TargetObject;
var targetProperty = provideTarget?.TargetProperty; var targetProperty = provideTarget?.TargetProperty switch
{
AvaloniaProperty ap => ap,
PropertyInfo pi => new Avalonia.Data.Core.ReflectionClrPropertyInfo(pi),
_ => provideTarget?.TargetProperty,
};
var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant
?? GetDictionaryVariant(serviceProvider); ?? GetDictionaryVariant(serviceProvider);
var targetType = targetProperty switch var targetType = targetProperty switch
{ {
AvaloniaProperty ap => ap.PropertyType, AvaloniaProperty ap => ap.PropertyType,
PropertyInfo pi => pi.PropertyType, Avalonia.Data.Core.IPropertyInfo cpi => cpi.PropertyType,
_ => null _ => null
}; };
@ -62,7 +68,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
} }
if (targetObject is Control target && if (targetObject is Control target &&
targetProperty is PropertyInfo property) targetProperty is Avalonia.Data.Core.IPropertyInfo property)
{ {
// This is stored locally to avoid allocating closure in the outer scope. // This is stored locally to avoid allocating closure in the outer scope.
var localTargetType = targetType; var localTargetType = targetType;

11
src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Logging; using Avalonia.Logging;
namespace Avalonia.Markup.Data namespace Avalonia.Markup.Data
@ -56,11 +57,11 @@ namespace Avalonia.Markup.Data
/// <param name="target">The control.</param> /// <param name="target">The control.</param>
/// <param name="property">The property on the control to bind to.</param> /// <param name="property">The property on the control to bind to.</param>
/// <param name="value">A function which returns the value.</param> /// <param name="value">A function which returns the value.</param>
public static void Add(StyledElement target, PropertyInfo property, Func<StyledElement, object?> value) public static void Add(StyledElement target, IPropertyInfo property, Func<StyledElement, object?> value)
{ {
if (target.IsInitialized) if (target.IsInitialized)
{ {
property.SetValue(target, value(target)); property.Set(target, value(target));
} }
else else
{ {
@ -125,20 +126,20 @@ namespace Avalonia.Markup.Data
private class ClrPropertyValueEntry : Entry private class ClrPropertyValueEntry : Entry
{ {
public ClrPropertyValueEntry(PropertyInfo property, Func<StyledElement, object?> value) public ClrPropertyValueEntry(IPropertyInfo property, Func<StyledElement, object?> value)
{ {
Property = property; Property = property;
Value = value; Value = value;
} }
public PropertyInfo Property { get; } public IPropertyInfo Property { get; }
public Func<StyledElement, object?> Value { get; } public Func<StyledElement, object?> Value { get; }
public override void Apply(StyledElement control) public override void Apply(StyledElement control)
{ {
try try
{ {
Property.SetValue(control, Value(control)); Property.Set(control, Value(control));
} }
catch (Exception e) catch (Exception e)
{ {

42
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -49,7 +49,6 @@ namespace Avalonia.Win32
public Win32Platform() public Win32Platform()
{ {
SetDpiAwareness();
CreateMessageWindow(); CreateMessageWindow();
_dispatcher = new Win32DispatcherImpl(_hwnd); _dispatcher = new Win32DispatcherImpl(_hwnd);
} }
@ -80,6 +79,9 @@ namespace Avalonia.Win32
public static void Initialize(Win32PlatformOptions options) public static void Initialize(Win32PlatformOptions options)
{ {
s_options = options; s_options = options;
SetDpiAwareness();
var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60); var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60);
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
@ -264,12 +266,31 @@ namespace Avalonia.Win32
var user32 = LoadLibrary("user32.dll"); var user32 = LoadLibrary("user32.dll");
var method = GetProcAddress(user32, nameof(SetProcessDpiAwarenessContext)); var method = GetProcAddress(user32, nameof(SetProcessDpiAwarenessContext));
var dpiAwareness = Options.DpiAwareness;
if (method != IntPtr.Zero) if (method != IntPtr.Zero)
{ {
if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) || if (dpiAwareness == Win32DpiAwareness.Unaware)
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
{ {
return; if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE))
{
return;
}
}
else if (dpiAwareness == Win32DpiAwareness.SystemDpiAware)
{
if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE))
{
return;
}
}
else if (dpiAwareness == Win32DpiAwareness.PerMonitorDpiAware)
{
if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) ||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
{
return;
}
} }
} }
@ -278,11 +299,20 @@ namespace Avalonia.Win32
if (method != IntPtr.Zero) if (method != IntPtr.Zero)
{ {
SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); var awareness = (dpiAwareness) switch
{
Win32DpiAwareness.Unaware => PROCESS_DPI_AWARENESS.PROCESS_DPI_UNAWARE,
Win32DpiAwareness.SystemDpiAware => PROCESS_DPI_AWARENESS.PROCESS_SYSTEM_DPI_AWARE,
Win32DpiAwareness.PerMonitorDpiAware => PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE,
_ => PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE,
};
SetProcessDpiAwareness(awareness);
return; return;
} }
SetProcessDPIAware(); if (dpiAwareness != Win32DpiAwareness.Unaware)
SetProcessDPIAware();
} }
} }
} }

26
src/Windows/Avalonia.Win32/Win32PlatformOptions.cs

@ -25,6 +25,27 @@ public enum Win32RenderingMode
Wgl = 3 Wgl = 3
} }
/// <summary>
/// Represents the DPI Awareness for the application.
/// </summary>
public enum Win32DpiAwareness
{
/// <summary>
/// The application is DPI unaware.
/// </summary>
Unaware,
/// <summary>
/// The application is system DPI aware. It will query DPI once and will not adjust to new DPI changes
/// </summary>
SystemDpiAware,
/// <summary>
/// The application is per-monitor DPI aware. It adjust its scale factor whenever DPI changes.
/// </summary>
PerMonitorDpiAware
}
/// <summary> /// <summary>
/// Represents the Win32 window composition mode. /// Represents the Win32 window composition mode.
/// </summary> /// </summary>
@ -137,4 +158,9 @@ public class Win32PlatformOptions
/// and <see cref="CompositionMode"/> only accepts null or <see cref="Win32CompositionMode.RedirectionSurface"/>. /// and <see cref="CompositionMode"/> only accepts null or <see cref="Win32CompositionMode.RedirectionSurface"/>.
/// </summary> /// </summary>
public IPlatformGraphics? CustomPlatformGraphics { get; set; } public IPlatformGraphics? CustomPlatformGraphics { get; set; }
/// <summary>
/// Gets or sets the application's DPI awareness.
/// </summary>
public Win32DpiAwareness DpiAwareness { get; set; } = Win32DpiAwareness.PerMonitorDpiAware;
} }

2
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -552,7 +552,7 @@ namespace Avalonia.Win32
get get
{ {
// Windows 10 and 11 add a 7 pixel invisible border on the left/right/bottom of windows for resizing // Windows 10 and 11 add a 7 pixel invisible border on the left/right/bottom of windows for resizing
if (Win32Platform.WindowsVersion.Major < 10 || !HasFullDecorations) if (Win32Platform.WindowsVersion.Major < 10 || !HasFullDecorations || GetStyle().HasFlag(WindowStyles.WS_POPUP))
{ {
return PixelSize.Empty; return PixelSize.Empty;
} }

34
tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs

@ -487,6 +487,40 @@ namespace Avalonia.Base.UnitTests.Input
Assert.True(raised); Assert.True(raised);
} }
[Fact]
public void Gestures_Should_Be_Cancelled_When_Pointer_Capture_Is_Lost()
{
Border border = new Border()
{
Width = 100,
Height = 100,
Background = new SolidColorBrush(Colors.Red)
};
border.GestureRecognizers.Add(new PinchGestureRecognizer());
var root = new TestRoot
{
Child = border
};
var raised = false;
root.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10);
var firstTouch = new TouchTestHelper();
var secondTouch = new TouchTestHelper();
firstTouch.Down(border, position: firstPoint);
firstTouch.Cancel();
secondTouch.Down(border, position: secondPoint);
secondTouch.Move(border, position: new Point(20, 20));
Assert.False(raised);
}
[Fact] [Fact]
public void Scrolling_Should_Start_After_Start_Distance_Is_Exceeded() public void Scrolling_Should_Start_After_Start_Distance_Is_Exceeded()
{ {

152
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -14,6 +14,7 @@ using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml.Templates; using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -128,6 +129,155 @@ namespace Avalonia.Controls.UnitTests
Assert.Same(container.Theme, theme); Assert.Same(container.Theme, theme);
} }
[Fact]
public void Container_Should_Have_Theme_Set_To_ItemContainerTheme_With_Base_TargetType()
{
using var app = Start();
var theme = new ControlTheme { TargetType = typeof(Control) };
var target = CreateTarget(
itemsSource: new[] { "Foo" },
itemContainerTheme: theme);
var container = GetContainer(target);
Assert.Same(container.Theme, theme);
}
[Fact]
public void ItemContainerTheme_Can_Be_Changed()
{
using var app = Start();
var theme1 = new ControlTheme
{
TargetType = typeof(ContentPresenter),
Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Red) }
};
var theme2 = new ControlTheme
{
TargetType = typeof(ContentPresenter),
Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Green) }
};
var target = CreateTarget(
itemsSource: new[] { "Foo" },
itemContainerTheme: theme1);
var container = GetContainer(target);
Assert.Same(container.Theme, theme1);
Assert.Equal(container.Background, Brushes.Red);
target.ItemContainerTheme = theme2;
container = GetContainer(target);
Assert.Same(container.Theme, theme2);
Assert.Equal(container.Background, Brushes.Green);
}
[Fact]
public void ItemContainerTheme_Can_Be_Changed_Virtualizing()
{
using var app = Start();
var theme1 = new ControlTheme
{
TargetType = typeof(ContentPresenter),
Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Red) }
};
var theme2 = new ControlTheme
{
TargetType = typeof(ContentPresenter),
Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Green) }
};
var itemsPanel = new FuncTemplate<Panel?>(() => new VirtualizingStackPanel());
var target = CreateTarget(
itemsSource: new[] { "Foo" },
itemContainerTheme: theme1,
itemsPanel: itemsPanel);
var container = GetContainer(target);
Assert.Same(container.Theme, theme1);
Assert.Equal(container.Background, Brushes.Red);
target.ItemContainerTheme = theme2;
Layout(target);
container = GetContainer(target);
Assert.Same(container.Theme, theme2);
Assert.Equal(container.Background, Brushes.Green);
}
[Fact]
public void ItemContainerTheme_Can_Be_Cleared()
{
using var app = Start();
var theme = new ControlTheme
{
TargetType = typeof(ContentPresenter),
Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Red) }
};
var target = CreateTarget(
itemsSource: new[] { "Foo" },
itemContainerTheme: theme);
var container = GetContainer(target);
Assert.Same(container.Theme, theme);
Assert.Equal(container.Background, Brushes.Red);
target.ItemContainerTheme = null;
container = GetContainer(target);
Assert.Null(container.Theme);
Assert.Null(container.Background);
}
[Fact]
public void ItemContainerTheme_Should_Not_Override_LocalValue_Theme()
{
using var app = Start();
var theme1 = new ControlTheme
{
TargetType = typeof(ContentPresenter),
Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Red) }
};
var theme2 = new ControlTheme
{
TargetType = typeof(Control),
Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Green) }
};
var items = new object[]
{
new ContentPresenter(),
new ContentPresenter
{
Theme = theme2
},
};
var target = CreateTarget(
itemsSource: items,
itemContainerTheme: theme1);
Assert.Same(theme1, GetContainer(target, 0).Theme);
Assert.Same(theme2, GetContainer(target, 1).Theme);
target.ItemContainerTheme = null;
Assert.Null(GetContainer(target, 0).Theme);
Assert.Same(theme2, GetContainer(target, 1).Theme);
}
[Fact] [Fact]
public void Container_Should_Have_LogicalParent_Set_To_ItemsControl() public void Container_Should_Have_LogicalParent_Set_To_ItemsControl()
{ {
@ -851,6 +1001,7 @@ namespace Avalonia.Controls.UnitTests
IList? itemsSource = null, IList? itemsSource = null,
ControlTheme? itemContainerTheme = null, ControlTheme? itemContainerTheme = null,
IDataTemplate? itemTemplate = null, IDataTemplate? itemTemplate = null,
ITemplate<Panel?>? itemsPanel = null,
IEnumerable<IDataTemplate>? dataTemplates = null, IEnumerable<IDataTemplate>? dataTemplates = null,
bool performLayout = true) bool performLayout = true)
{ {
@ -861,6 +1012,7 @@ namespace Avalonia.Controls.UnitTests
itemsSource: itemsSource, itemsSource: itemsSource,
itemContainerTheme: itemContainerTheme, itemContainerTheme: itemContainerTheme,
itemTemplate: itemTemplate, itemTemplate: itemTemplate,
itemsPanel: itemsPanel,
dataTemplates: dataTemplates, dataTemplates: dataTemplates,
performLayout: performLayout); performLayout: performLayout);
} }

21
tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

@ -202,5 +202,26 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, target.Inlines.Count); Assert.Equal(0, target.Inlines.Count);
} }
} }
[Fact]
public void Setting_TextDecorations_Should_Update_Inlines()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new TextBlock();
target.Inlines.Add(new Run("Hello World"));
Assert.Equal(1, target.Inlines.Count);
Assert.Null(target.Inlines[0].TextDecorations);
var underline = TextDecorations.Underline;
target.TextDecorations = underline;
Assert.Equal(underline, target.Inlines[0].TextDecorations);
}
}
} }
} }

18
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs

@ -286,6 +286,24 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.IsType(typeof(ContentPresenter), template.Build(new ContentControl()).Result); Assert.IsType(typeof(ContentPresenter), template.Build(new ContentControl()).Result);
} }
[Fact]
public void ControlTemplate_With_String_TargetType()
{
var xaml = @"
<ControlTemplate xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
TargetType='ContentControl'>
<ContentPresenter Content='{TemplateBinding Content}' />
</ControlTemplate>
";
var template = AvaloniaRuntimeXamlLoader.Parse<ControlTemplate>(xaml);
Assert.Equal(typeof(ContentControl), template.TargetType);
Assert.IsType(typeof(ContentPresenter), template.Build(new ContentControl()).Result);
}
[Fact] [Fact]
public void ControlTemplate_With_Panel_Children_Are_Added() public void ControlTemplate_With_Panel_Children_Are_Added()
{ {

6
tests/Avalonia.UnitTests/TouchTestHelper.cs

@ -61,5 +61,11 @@ namespace Avalonia.UnitTests
Down(target, source, position, modifiers); Down(target, source, position, modifiers);
Up(target, source, position, modifiers); Up(target, source, position, modifiers);
} }
public void Cancel()
{
_pointer.Capture(null);
_pointer.CaptureGestureRecognizer(null);
}
} }
} }

Loading…
Cancel
Save