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 */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
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 */; };
/* End PBXBuildFile section */
@ -122,6 +123,7 @@
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>"; };
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>"; };
/* End PBXFileReference section */
@ -162,6 +164,7 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */,
855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */,
8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */,
8D300D68292E1E5D00320C49 /* AvnTextInputMethod.mm */,
@ -333,6 +336,7 @@
18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */,
18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */,
18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */,
ED754D262A97306B0078B4DF /* PlatformRenderTimer.mm in Sources */,
1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
18391AC16726CBC45856233B /* AvnWindow.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 IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern IAvnPlatformSettings* CreatePlatformSettings();
extern IAvnPlatformRenderTimer* CreatePlatformRenderTimer();
extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu ();

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

@ -446,6 +446,17 @@ public:
return S_OK;
}
}
virtual HRESULT CreatePlatformRenderTimer(IAvnPlatformRenderTimer** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreatePlatformRenderTimer();
return S_OK;
}
}
};
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)
[![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 />
[![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)
{
e.Handled = true;
e.PreventGestureRecognition();
var currentPoint = e.GetCurrentPoint(v);
if (e.RoutedEvent == PointerPressedEvent)
AddPoint(currentPoint.Position, Brushes.Green, 10);

1
samples/ControlCatalog/Pages/PointerContactsTab.cs

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

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

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

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

@ -1,53 +1,22 @@
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 Transition<T> : AvaloniaObject, ITransition
public abstract class Transition<T> : TransitionBase
{
private AvaloniaProperty? _prop;
/// <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
static Transition()
{
get
{
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;
}
PropertyProperty.Changed.AddClassHandler<Transition<T>>((x, e) => x.OnPropertyPropertyChanged(e));
}
AvaloniaProperty ITransition.Property
private void OnPropertyPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
get => Property ?? throw new InvalidOperationException("Transition has no property specified.");
set => Property = value;
if ((e.NewValue is AvaloniaProperty newValue) && !newValue.PropertyType.IsAssignableFrom(typeof(T)))
throw new InvalidCastException
($"Invalid property type \"{typeof(T).Name}\" for this transition: {GetType().Name}.");
}
/// <summary>
@ -55,11 +24,7 @@ namespace Avalonia.Animation
/// </summary>
internal abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue);
/// <inheritdocs/>
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)
internal override IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
{
if (Property is null)
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,
Duration = Duration,
FillMode = FillMode.Forward,
Children =
{
CreateKeyFrame(0d, 0d, 2),

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

@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Controls;
@ -59,6 +60,20 @@ namespace Avalonia.Input.GestureRecognizers
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)
{
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
{
@ -110,6 +111,7 @@ namespace Avalonia.Input
_secondContact = null;
}
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);
PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerPressed(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()
@ -615,6 +616,11 @@ namespace Avalonia.Input
}
}
private void OnGesturePointerCaptureLost(PointerCaptureLostEventArgs e)
{
_gestureRecognizers?.HandleCaptureLost(e.Pointer);
}
private void OnGesturePointerPressed(PointerPressedEventArgs e)
{
if (!e.IsGestureRecognitionSkipped)

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

@ -91,12 +91,16 @@ namespace Avalonia.Input
internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer)
{
if (CapturedGestureRecognizer != gestureRecognizer)
{
CapturedGestureRecognizer?.PointerCaptureLostInternal(this);
}
CapturedGestureRecognizer = gestureRecognizer;
if (gestureRecognizer != 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 overhangLeading = Math.Max(0, bounds.Left - start);
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 (lineHeight > height)
{
height = lineHeight;
}
height = lineHeight;
}
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).
// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
@ -165,6 +165,16 @@ namespace Avalonia.Controls
/// </summary>
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>
/// The TextBox template part.
/// </summary>
@ -1379,6 +1389,15 @@ namespace Avalonia.Controls
/// </summary>
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)
{
ClearView();
@ -1391,79 +1410,65 @@ namespace Avalonia.Controls
// Determine if any filtering mode is on
bool stringFiltering = TextFilter != null;
bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null;
int view_index = 0;
int view_count = _view!.Count;
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)
{
// Exit the fitter when requested if cancellation is requested
if (_cancelRequested)
{
return;
}
bool inResults = !(stringFiltering || objectFiltering);
if (!inResults)
{
if (stringFiltering)
{
inResults = TextFilter!(text, FormatValue(item));
inResults = textFilter!(text, FormatValue(item));
}
else
else if (objectFiltering)
{
if (ItemFilter is null)
{
throw new Exception("ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom");
}
else
{
inResults = ItemFilter(text, item);
}
inResults = itemFilter!(text, item);
}
}
if (view_count > view_index && inResults && _view[view_index] == item)
if (inResults)
{
// Item is still in the view
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--;
_newViewItems.Add(item);
}
}
_view?.Clear();
_view?.AddRange(_newViewItems);
// Clear the evaluator to discard a reference to the last item
if (_valueBindingEvaluator != null)
{
_valueBindingEvaluator.ClearDataContext();
}
// indicate that filtering is not ongoing anymore
_filterInAction = false;
_cancelRequested = false;
}
/// <summary>

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

@ -10,12 +10,14 @@ namespace Avalonia.Controls.Documents
/// </summary>
public abstract class Inline : TextElement
{
// TODO12: change the field type to an AttachedProperty for consistency (breaking change)
/// <summary>
/// AvaloniaProperty for <see cref="TextDecorations" /> property.
/// </summary>
public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty =
AvaloniaProperty.Register<Inline, TextDecorationCollection?>(
nameof(TextDecorations));
AvaloniaProperty.RegisterAttached<Inline, Inline, TextDecorationCollection?>(
nameof(TextDecorations),
inherits: true);
/// <summary>
/// AvaloniaProperty for <see cref="BaselineAlignment" /> property.
@ -43,7 +45,27 @@ namespace Avalonia.Controls.Documents
get { return GetValue(BaselineAlignmentProperty); }
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 AppendText(StringBuilder stringBuilder);

28
src/Avalonia.Controls/ItemsControl.cs

@ -67,6 +67,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
private static readonly AttachedProperty<ControlTheme?> AppliedItemContainerTheme =
AvaloniaProperty.RegisterAttached<ItemsControl, Control, ControlTheme?>("AppliedItemContainerTheme");
/// <summary>
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary>
@ -663,13 +666,26 @@ namespace Avalonia.Controls
internal void PrepareItemContainer(Control container, object? item, int index)
{
var itemContainerTheme = ItemContainerTheme;
if (itemContainerTheme is not null &&
!container.IsSet(ThemeProperty) &&
StyledElement.GetStyleKey(container) == itemContainerTheme.TargetType)
// 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
// need to update it.
if (!container.IsSet(ThemeProperty) || container.GetValue(AppliedItemContainerTheme) == container.Theme)
{
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)

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

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

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

@ -3,6 +3,7 @@ using Avalonia.Input;
using Avalonia.Reactive;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using System;
namespace Avalonia.Controls.Primitives
{
@ -68,11 +69,15 @@ namespace Avalonia.Controls.Primitives
if (underscore != -1 && ShowAccessKey)
{
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(
new Pen(Foreground, 1),
rect.BottomLeft + offset,
rect.BottomRight + offset);
new Point(x1, y),
new Point(x2, y));
}
}

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

@ -110,6 +110,13 @@ namespace Avalonia.Controls.Primitives
get
{
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)};
}
}

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

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

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

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

2
src/Avalonia.Controls/TextBlock.cs

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

12
src/Avalonia.Controls/ToggleSwitch.cs

@ -243,10 +243,6 @@ namespace Avalonia.Controls
}
UpdateKnobTransitions();
}
else
{
base.Toggle();
}
_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)
{
if ((_switchKnob != null) && (_knobsPanel != null))

10
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -29,7 +29,7 @@ namespace Avalonia.DesignerSupport
if (assemblyPath != null)
{
if (xamlFileProjectPath == null)
xamlFileProjectPath = "/Designer/Fake.xaml";
xamlFileProjectPath = "/Fake.xaml";
//Fabricate fake Uri
baseUri =
new Uri($"avares://{Path.GetFileNameWithoutExtension(assemblyPath)}{xamlFileProjectPath}");
@ -43,7 +43,7 @@ namespace Avalonia.DesignerSupport
{
LocalAssembly = localAsm,
DesignMode = true,
UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue ) && parsedValue
UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue) && parsedValue
});
var style = loaded as IStyle;
var resources = loaded as ResourceDictionary;
@ -90,14 +90,14 @@ namespace Avalonia.DesignerSupport
};
}
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
control = (Control) loaded;
control = (Control)loaded;
window = control as Window;
if (window == null)
{
window = new Window() {Content = (Control)control};
window = new Window() { Content = (Control)control };
}
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<IWindowingPlatform>().ToConstant(this)
.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<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.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 CreatePlatformSettings(IAvnPlatformSettings** ppv);
HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv);
HRESULT CreatePlatformRenderTimer(IAvnPlatformRenderTimer** ppv);
}
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]
@ -999,3 +1000,12 @@ interface IAvnPlatformBehaviorInhibition : IUnknown
{
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.Surface = _resources.Surface;
ElementComposition.SetElementChildVisual(this, _visual);
using (_resources.Context.MakeCurrent())
OnOpenGlInit(_resources.Context.GlInterface);
return true;
}

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

@ -71,6 +71,9 @@
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<Style Selector="^:pointerover /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<!-- Pressed State -->
<Style Selector="^:pressed">
@ -82,6 +85,9 @@
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<Style Selector="^:pressed /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<!-- Disabled State -->
<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 XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
@ -22,26 +23,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IXamlAstTypeReference targetType;
var templatableBaseType = context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control");
if ((tt?.Values.FirstOrDefault() is XamlTypeExtensionNode tn))
{
targetType = tn.Value;
}
else
targetType = tt?.Values.FirstOrDefault() switch
{
var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
.FirstOrDefault();
if (parentScope?.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
targetType = parentScope.TargetType;
else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlAstObjectNode directParentNode
&& templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType()))
targetType = directParentNode.Type;
else
targetType = new XamlAstClrTypeReference(node,
templatableBaseType, false);
}
XamlTypeExtensionNode tn => tn.Value,
XamlAstTextNode textNode => TypeReferenceResolver.ResolveType(context, textNode.Text, false, textNode, true),
_ when context.ParentNodes()
.OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
.FirstOrDefault() is { ScopeType: AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style } parentScope => parentScope.TargetType,
_ when context.ParentNodes().Skip(1).FirstOrDefault() is XamlAstObjectNode directParentNode
&& templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType()) => directParentNode.Type,
_ => new XamlAstClrTypeReference(node,
templatableBaseType, false)
};
return new AvaloniaXamlIlTargetTypeMetadataNode(on, targetType,
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
@ -59,7 +53,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ControlTemplate,
Transitions
}
public AvaloniaXamlIlTargetTypeMetadataNode(IXamlAstValueNode value, IXamlAstTypeReference targetType,
ScopeTypes type)
: 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 provideTarget = serviceProvider.GetService<IProvideValueTarget>();
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
?? GetDictionaryVariant(serviceProvider);
var targetType = targetProperty switch
{
AvaloniaProperty ap => ap.PropertyType,
PropertyInfo pi => pi.PropertyType,
Avalonia.Data.Core.IPropertyInfo cpi => cpi.PropertyType,
_ => null
};
@ -62,7 +68,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
}
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.
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.Runtime.CompilerServices;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Logging;
namespace Avalonia.Markup.Data
@ -56,11 +57,11 @@ namespace Avalonia.Markup.Data
/// <param name="target">The control.</param>
/// <param name="property">The property on the control to bind to.</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)
{
property.SetValue(target, value(target));
property.Set(target, value(target));
}
else
{
@ -125,20 +126,20 @@ namespace Avalonia.Markup.Data
private class ClrPropertyValueEntry : Entry
{
public ClrPropertyValueEntry(PropertyInfo property, Func<StyledElement, object?> value)
public ClrPropertyValueEntry(IPropertyInfo property, Func<StyledElement, object?> value)
{
Property = property;
Value = value;
}
public PropertyInfo Property { get; }
public IPropertyInfo Property { get; }
public Func<StyledElement, object?> Value { get; }
public override void Apply(StyledElement control)
{
try
{
Property.SetValue(control, Value(control));
Property.Set(control, Value(control));
}
catch (Exception e)
{

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

@ -49,7 +49,6 @@ namespace Avalonia.Win32
public Win32Platform()
{
SetDpiAwareness();
CreateMessageWindow();
_dispatcher = new Win32DispatcherImpl(_hwnd);
}
@ -80,6 +79,9 @@ namespace Avalonia.Win32
public static void Initialize(Win32PlatformOptions options)
{
s_options = options;
SetDpiAwareness();
var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60);
AvaloniaLocator.CurrentMutable
@ -264,12 +266,31 @@ namespace Avalonia.Win32
var user32 = LoadLibrary("user32.dll");
var method = GetProcAddress(user32, nameof(SetProcessDpiAwarenessContext));
var dpiAwareness = Options.DpiAwareness;
if (method != IntPtr.Zero)
{
if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) ||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
if (dpiAwareness == Win32DpiAwareness.Unaware)
{
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)
{
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;
}
SetProcessDPIAware();
if (dpiAwareness != Win32DpiAwareness.Unaware)
SetProcessDPIAware();
}
}
}

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

@ -25,6 +25,27 @@ public enum Win32RenderingMode
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>
/// Represents the Win32 window composition mode.
/// </summary>
@ -137,4 +158,9 @@ public class Win32PlatformOptions
/// and <see cref="CompositionMode"/> only accepts null or <see cref="Win32CompositionMode.RedirectionSurface"/>.
/// </summary>
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
{
// 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;
}

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

@ -487,6 +487,40 @@ namespace Avalonia.Base.UnitTests.Input
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]
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.LogicalTree;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
@ -128,6 +129,155 @@ namespace Avalonia.Controls.UnitTests
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]
public void Container_Should_Have_LogicalParent_Set_To_ItemsControl()
{
@ -851,6 +1001,7 @@ namespace Avalonia.Controls.UnitTests
IList? itemsSource = null,
ControlTheme? itemContainerTheme = null,
IDataTemplate? itemTemplate = null,
ITemplate<Panel?>? itemsPanel = null,
IEnumerable<IDataTemplate>? dataTemplates = null,
bool performLayout = true)
{
@ -861,6 +1012,7 @@ namespace Avalonia.Controls.UnitTests
itemsSource: itemsSource,
itemContainerTheme: itemContainerTheme,
itemTemplate: itemTemplate,
itemsPanel: itemsPanel,
dataTemplates: dataTemplates,
performLayout: performLayout);
}

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

@ -202,5 +202,26 @@ namespace Avalonia.Controls.UnitTests
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);
}
[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]
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);
Up(target, source, position, modifiers);
}
public void Cancel()
{
_pointer.Capture(null);
_pointer.CaptureGestureRecognizer(null);
}
}
}

Loading…
Cancel
Save