Browse Source

Merge branch 'master' into feature/add-option-to-disable-app-nap

pull/8894/head
Max Katz 4 years ago
committed by GitHub
parent
commit
acf1b83b60
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  2. 11
      samples/interop/WindowsInteropTest/Program.cs
  3. 7
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  4. 4
      src/Avalonia.Base/AvaloniaObject.cs
  5. 47
      src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
  6. 49
      src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
  7. 25
      src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
  8. 2
      src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs
  9. 2
      src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs
  10. 5
      src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs
  11. 17
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  12. 5
      src/Avalonia.Base/Rendering/DefaultRenderTimer.cs
  13. 5
      src/Avalonia.Base/Rendering/IRenderLoop.cs
  14. 1
      src/Avalonia.Base/Rendering/RenderLoop.cs
  15. 9
      src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs
  16. 2
      src/Avalonia.Controls/Border.cs
  17. 2
      src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
  18. 2
      src/Avalonia.Controls/Grid.cs
  19. 2
      src/Avalonia.Controls/Image.cs
  20. 2
      src/Avalonia.Controls/Panel.cs
  21. 2
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  22. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  23. 5
      src/Avalonia.Controls/Primitives/AccessText.cs
  24. 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  25. 2
      src/Avalonia.Controls/Shapes/Shape.cs
  26. 8
      src/Avalonia.Controls/TextBlock.cs
  27. 2
      src/Avalonia.Controls/TickBar.cs
  28. 9
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  29. 14
      src/Windows/Avalonia.Win32/TrayIconImpl.cs
  30. 24
      src/Windows/Avalonia.Win32/Win32Platform.cs
  31. 36
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  32. 18
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs
  33. 25
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

10
samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs

@ -59,10 +59,12 @@ namespace ControlCatalog.Pages
}; };
StreamGeometry sg = new StreamGeometry(); StreamGeometry sg = new StreamGeometry();
var cntx = sg.Open(); using (var cntx = sg.Open())
cntx.BeginFigure(new Point(-25.0d, -10.0d), false); {
cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise); cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
cntx.EndFigure(true); cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
cntx.EndFigure(true);
}
_smileGeometry = sg.Clone(); _smileGeometry = sg.Clone();
} }

11
samples/interop/WindowsInteropTest/Program.cs

@ -1,5 +1,4 @@
using System; using System;
using Avalonia.Controls;
using ControlCatalog; using ControlCatalog;
using Avalonia; using Avalonia;
@ -15,7 +14,15 @@ namespace WindowsInteropTest
{ {
System.Windows.Forms.Application.EnableVisualStyles(); System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().SetupWithoutStarting(); AppBuilder.Configure<App>()
.UseWin32()
.UseDirect2D1()
.With(new Win32PlatformOptions
{
UseWindowsUIComposition = false,
ShouldRenderOnUIThread = true // necessary for WPF
})
.SetupWithoutStarting();
System.Windows.Forms.Application.Run(new SelectorForm()); System.Windows.Forms.Application.Run(new SelectorForm());
} }
} }

7
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net461</TargetFramework> <TargetFramework>net461</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
</PropertyGroup> </PropertyGroup>
@ -10,9 +10,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj" />
<ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj"> <ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj" />
<Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
<Name>ControlCatalog</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

4
src/Avalonia.Base/AvaloniaObject.cs

@ -277,8 +277,8 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <returns>True if the property is set, otherwise false.</returns> /// <returns>True if the property is set, otherwise false.</returns>
/// <remarks> /// <remarks>
/// Checks whether a value is assigned to the property, or that there is a binding to the /// Returns true if <paramref name="property"/> is a styled property which has a value
/// property that is producing a value other than <see cref="AvaloniaProperty.UnsetValue"/>. /// assigned to it or a binding targeting it; otherwise false.
/// </remarks> /// </remarks>
public bool IsSet(AvaloniaProperty property) public bool IsSet(AvaloniaProperty property)
{ {

47
src/Avalonia.Base/PropertyStore/BindingEntryBase.cs

@ -16,6 +16,8 @@ namespace Avalonia.PropertyStore
private IDisposable? _subscription; private IDisposable? _subscription;
private bool _hasValue; private bool _hasValue;
private TValue? _value; private TValue? _value;
private TValue? _defaultValue;
private bool _isDefaultValueInitialized;
protected BindingEntryBase( protected BindingEntryBase(
ValueFrame frame, ValueFrame frame,
@ -89,6 +91,7 @@ namespace Avalonia.PropertyStore
protected abstract BindingValue<TValue> ConvertAndValidate(TSource value); protected abstract BindingValue<TValue> ConvertAndValidate(TSource value);
protected abstract BindingValue<TValue> ConvertAndValidate(BindingValue<TSource> value); protected abstract BindingValue<TValue> ConvertAndValidate(BindingValue<TSource> value);
protected abstract TValue GetDefaultValue(Type ownerType);
protected virtual void Start(bool produceValue) protected virtual void Start(bool produceValue)
{ {
@ -104,17 +107,6 @@ namespace Avalonia.PropertyStore
}; };
} }
private void ClearValue()
{
if (_hasValue)
{
_hasValue = false;
_value = default;
if (_subscription is not null)
Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
}
}
private void SetValue(BindingValue<TValue> value) private void SetValue(BindingValue<TValue> value)
{ {
static void Execute(BindingEntryBase<TValue, TSource> instance, BindingValue<TValue> value) static void Execute(BindingEntryBase<TValue, TSource> instance, BindingValue<TValue> value)
@ -124,24 +116,20 @@ namespace Avalonia.PropertyStore
LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value); LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value);
if (value.HasValue) var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue();
{
if (!instance._hasValue || !EqualityComparer<TValue>.Default.Equals(instance._value, value.Value)) if (!instance._hasValue || !EqualityComparer<TValue>.Default.Equals(instance._value, effectiveValue))
{
instance._value = value.Value;
instance._hasValue = true;
if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
}
}
else if (value.Type != BindingValueType.DoNothing)
{ {
instance.ClearValue(); instance._value = effectiveValue;
instance._hasValue = true;
if (instance._subscription is not null && instance._subscription != s_creatingQuiet) if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
instance.Frame.Owner?.OnBindingValueCleared(instance.Property, instance.Frame.Priority); instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
} }
} }
if (value.Type == BindingValueType.DoNothing)
return;
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {
Execute(this, value); Execute(this, value);
@ -161,5 +149,16 @@ namespace Avalonia.PropertyStore
_subscription = null; _subscription = null;
Frame.OnBindingCompleted(this); Frame.OnBindingCompleted(this);
} }
private TValue GetCachedDefaultValue()
{
if (!_isDefaultValueInitialized)
{
_defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType());
_isDefaultValueInitialized = true;
}
return _defaultValue!;
}
} }
} }

49
src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs

@ -10,6 +10,8 @@ namespace Avalonia.PropertyStore
{ {
private readonly ValueStore _owner; private readonly ValueStore _owner;
private IDisposable? _subscription; private IDisposable? _subscription;
private T? _defaultValue;
private bool _isDefaultValueInitialized;
public LocalValueBindingObserver(ValueStore owner, StyledProperty<T> property) public LocalValueBindingObserver(ValueStore owner, StyledProperty<T> property)
{ {
@ -41,26 +43,28 @@ namespace Avalonia.PropertyStore
public void OnNext(T value) public void OnNext(T value)
{ {
static void Execute(ValueStore owner, StyledProperty<T> property, T value) static void Execute(LocalValueBindingObserver<T> instance, T value)
{ {
if (property.ValidateValue?.Invoke(value) != false) var owner = instance._owner;
owner.SetValue(property, value, BindingPriority.LocalValue); var property = instance.Property;
else
owner.ClearLocalValue(property); if (property.ValidateValue?.Invoke(value) == false)
value = instance.GetCachedDefaultValue();
owner.SetValue(property, value, BindingPriority.LocalValue);
} }
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {
Execute(_owner, Property, value); Execute(this, value);
} }
else else
{ {
// To avoid allocating closure in the outer scope we need to capture variables // To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread. // locally. This allows us to skip most of the allocations when on UI thread.
var instance = _owner; var instance = this;
var property = Property;
var newValue = value; var newValue = value;
Dispatcher.UIThread.Post(() => Execute(instance, property, newValue)); Dispatcher.UIThread.Post(() => Execute(instance, newValue));
} }
} }
@ -74,11 +78,21 @@ namespace Avalonia.PropertyStore
LoggingUtils.LogIfNecessary(owner.Owner, property, value); LoggingUtils.LogIfNecessary(owner.Owner, property, value);
if (value.HasValue) if (value.HasValue)
owner.SetValue(property, value.Value, BindingPriority.LocalValue); {
else if (value.Type != BindingValueType.DataValidationError) var effectiveValue = value.Value;
owner.ClearLocalValue(property); if (property.ValidateValue?.Invoke(effectiveValue) == false)
effectiveValue = instance.GetCachedDefaultValue();
owner.SetValue(property, effectiveValue, BindingPriority.LocalValue);
}
else
{
owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
}
} }
if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError)
return;
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {
Execute(this, value); Execute(this, value);
@ -92,5 +106,16 @@ namespace Avalonia.PropertyStore
Dispatcher.UIThread.Post(() => Execute(instance, newValue)); Dispatcher.UIThread.Post(() => Execute(instance, newValue));
} }
} }
private T GetCachedDefaultValue()
{
if (!_isDefaultValueInitialized)
{
_defaultValue = Property.GetDefaultValue(_owner.Owner.GetType());
_isDefaultValueInitialized = true;
}
return _defaultValue!;
}
} }
} }

25
src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs

@ -1,5 +1,4 @@
using System; using System;
using System.Security.Cryptography;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Threading; using Avalonia.Threading;
@ -10,6 +9,8 @@ namespace Avalonia.PropertyStore
{ {
private readonly ValueStore _owner; private readonly ValueStore _owner;
private IDisposable? _subscription; private IDisposable? _subscription;
private T? _defaultValue;
private bool _isDefaultValueInitialized;
public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty<T> property) public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty<T> property)
{ {
@ -49,11 +50,7 @@ namespace Avalonia.PropertyStore
if (value == AvaloniaProperty.UnsetValue) if (value == AvaloniaProperty.UnsetValue)
{ {
owner.ClearLocalValue(property); owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
}
else if (value == BindingOperations.DoNothing)
{
// Do nothing!
} }
else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue)) else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue))
{ {
@ -61,11 +58,14 @@ namespace Avalonia.PropertyStore
} }
else else
{ {
owner.ClearLocalValue(property); owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value); LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value);
} }
} }
if (value == BindingOperations.DoNothing)
return;
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {
Execute(this, value); Execute(this, value);
@ -79,5 +79,16 @@ namespace Avalonia.PropertyStore
Dispatcher.UIThread.Post(() => Execute(instance, newValue)); Dispatcher.UIThread.Post(() => Execute(instance, newValue));
} }
} }
private T GetCachedDefaultValue()
{
if (!_isDefaultValueInitialized)
{
_defaultValue = Property.GetDefaultValue(_owner.Owner.GetType());
_isDefaultValueInitialized = true;
}
return _defaultValue!;
}
} }
} }

2
src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs

@ -31,5 +31,7 @@ namespace Avalonia.PropertyStore
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
protected override TTarget GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType);
} }
} }

2
src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs

@ -48,5 +48,7 @@ namespace Avalonia.PropertyStore
return value; return value;
} }
protected override T GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType);
} }
} }

5
src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs

@ -29,5 +29,10 @@ namespace Avalonia.PropertyStore
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
protected override object? GetDefaultValue(Type ownerType)
{
return ((IStyledPropertyMetadata)Property.GetMetadata(ownerType)).DefaultValue;
}
} }
} }

17
src/Avalonia.Base/PropertyStore/ValueStore.cs

@ -380,23 +380,6 @@ namespace Avalonia.PropertyStore
} }
} }
/// <summary>
/// Called by non-LocalValue binding entries to re-evaluate the effective value when the
/// binding produces an unset value.
/// </summary>
/// <param name="property">The bound property.</param>
/// <param name="priority">The priority of binding which produced a new value.</param>
public void OnBindingValueCleared(AvaloniaProperty property, BindingPriority priority)
{
Debug.Assert(priority != BindingPriority.LocalValue);
if (TryGetEffectiveValue(property, out var existing))
{
if (priority <= existing.Priority)
ReevaluateEffectiveValue(property, existing);
}
}
/// <summary> /// <summary>
/// Called by a <see cref="ValueFrame"/> when its <see cref="ValueFrame.IsActive"/> /// Called by a <see cref="ValueFrame"/> when its <see cref="ValueFrame.IsActive"/>
/// state changes. /// state changes.

5
src/Avalonia.Base/Rendering/DefaultRenderTimer.cs

@ -1,6 +1,4 @@
using System; using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
namespace Avalonia.Rendering namespace Avalonia.Rendering
@ -59,7 +57,8 @@ namespace Avalonia.Rendering
} }
} }
public bool RunsInBackground => true; /// <inheritdoc />
public virtual bool RunsInBackground => true;
/// <summary> /// <summary>
/// Starts the timer. /// Starts the timer.

5
src/Avalonia.Base/Rendering/IRenderLoop.cs

@ -27,7 +27,10 @@ namespace Avalonia.Rendering
/// </summary> /// </summary>
/// <param name="i">The update task.</param> /// <param name="i">The update task.</param>
void Remove(IRenderLoopTask i); void Remove(IRenderLoopTask i);
/// <summary>
/// Indicates if the rendering is done on a non-UI thread.
/// </summary>
bool RunsInBackground { get; } bool RunsInBackground { get; }
} }
} }

1
src/Avalonia.Base/Rendering/RenderLoop.cs

@ -87,6 +87,7 @@ namespace Avalonia.Rendering
} }
} }
/// <inheritdoc />
public bool RunsInBackground => Timer.RunsInBackground; public bool RunsInBackground => Timer.RunsInBackground;
private void TimerTick(TimeSpan time) private void TimerTick(TimeSpan time)

9
src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs

@ -8,13 +8,20 @@ namespace Avalonia.Rendering
/// <summary> /// <summary>
/// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms /// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms
/// </summary> /// </summary>
public class UiThreadRenderTimer : DefaultRenderTimer public class UiThreadRenderTimer : DefaultRenderTimer
{ {
/// <summary>
/// Initializes a new instance of the <see cref="UiThreadRenderTimer"/> class.
/// </summary>
/// <param name="framesPerSecond">The number of frames per second at which the loop should run.</param>
public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond) public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond)
{ {
} }
/// <inheritdoc />
public override bool RunsInBackground => false;
/// <inheritdoc />
protected override IDisposable StartCore(Action<TimeSpan> tick) protected override IDisposable StartCore(Action<TimeSpan> tick)
{ {
bool cancelled = false; bool cancelled = false;

2
src/Avalonia.Controls/Border.cs

@ -225,7 +225,7 @@ namespace Avalonia.Controls
/// Renders the control. /// Renders the control.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context) public sealed override void Render(DrawingContext context)
{ {
_borderRenderHelper.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush, _borderRenderHelper.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush,
BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray); BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray);

2
src/Avalonia.Controls/ExperimentalAcrylicBorder.cs

@ -80,7 +80,7 @@ namespace Avalonia.Controls
_subscription?.Dispose(); _subscription?.Dispose();
} }
public override void Render(DrawingContext context) public sealed override void Render(DrawingContext context)
{ {
if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc) if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc)
{ {

2
src/Avalonia.Controls/Grid.cs

@ -3256,7 +3256,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// UpdateRenderBounds. /// UpdateRenderBounds.
/// </summary> /// </summary>
public override void Render(DrawingContext drawingContext) public sealed override void Render(DrawingContext drawingContext)
{ {
var grid = this.GetVisualParent<Grid>(); var grid = this.GetVisualParent<Grid>();

2
src/Avalonia.Controls/Image.cs

@ -73,7 +73,7 @@ namespace Avalonia.Controls
/// Renders the control. /// Renders the control.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context) public sealed override void Render(DrawingContext context)
{ {
var source = Source; var source = Source;

2
src/Avalonia.Controls/Panel.cs

@ -68,7 +68,7 @@ namespace Avalonia.Controls
/// Renders the visual to a <see cref="DrawingContext"/>. /// Renders the visual to a <see cref="DrawingContext"/>.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context) public sealed override void Render(DrawingContext context)
{ {
var background = Background; var background = Background;
if (background != null) if (background != null)

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

@ -534,7 +534,7 @@ namespace Avalonia.Controls.Presenters
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Render(DrawingContext context) public sealed override void Render(DrawingContext context)
{ {
_borderRenderer.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush, _borderRenderer.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush,
BoxShadow); BoxShadow);

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

@ -388,7 +388,7 @@ namespace Avalonia.Controls.Presenters
TextLayout.Draw(context, new Point(left, top)); TextLayout.Draw(context, new Point(left, top));
} }
public override void Render(DrawingContext context) public sealed override void Render(DrawingContext context)
{ {
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;

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

@ -60,10 +60,9 @@ namespace Avalonia.Controls.Primitives
/// Renders the <see cref="AccessText"/> to a drawing context. /// Renders the <see cref="AccessText"/> to a drawing context.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context) protected internal override void RenderCore(DrawingContext context)
{ {
base.Render(context); base.RenderCore(context);
int underscore = Text?.IndexOf('_') ?? -1; int underscore = Text?.IndexOf('_') ?? -1;
if (underscore != -1 && ShowAccessKey) if (underscore != -1 && ShowAccessKey)

2
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -69,7 +69,7 @@ namespace Avalonia.Controls.Remote
base.ArrangeCore(finalRect); base.ArrangeCore(finalRect);
} }
public override void Render(DrawingContext context) public sealed override void Render(DrawingContext context)
{ {
if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0) if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0)
{ {

2
src/Avalonia.Controls/Shapes/Shape.cs

@ -193,7 +193,7 @@ namespace Avalonia.Controls.Shapes
set { SetValue(StrokeJoinProperty, value); } set { SetValue(StrokeJoinProperty, value); }
} }
public override void Render(DrawingContext context) public sealed override void Render(DrawingContext context)
{ {
var geometry = RenderedGeometry; var geometry = RenderedGeometry;

8
src/Avalonia.Controls/TextBlock.cs

@ -549,7 +549,13 @@ namespace Avalonia.Controls
/// Renders the <see cref="TextBlock"/> to a drawing context. /// Renders the <see cref="TextBlock"/> to a drawing context.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context) public sealed override void Render(DrawingContext context)
{
RenderCore(context);
}
// Workaround to seal Render method, we need to make so because AccessText was overriding Render method which is sealed now.
internal protected virtual void RenderCore(DrawingContext context)
{ {
var background = Background; var background = Background;

2
src/Avalonia.Controls/TickBar.cs

@ -213,7 +213,7 @@ namespace Avalonia.Controls
/// ///
/// Brush that use to fill ticks is specified by Fill property. /// Brush that use to fill ticks is specified by Fill property.
/// </summary> /// </summary>
public override void Render(DrawingContext dc) public sealed override void Render(DrawingContext dc)
{ {
var size = new Size(Bounds.Width, Bounds.Height); var size = new Size(Bounds.Width, Bounds.Height);
var range = Maximum - Minimum; var range = Maximum - Minimum;

9
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -45,13 +45,6 @@ namespace Avalonia.Win32.Interop.Wpf
((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); ((FrameworkElement)PlatformImpl)?.InvalidateMeasure();
} }
protected override void HandleResized(Size clientSize, PlatformResizeReason reason)
{
ClientSize = clientSize;
LayoutManager.ExecuteLayoutPass();
Renderer?.Resized(clientSize);
}
public Size AllocatedSize => ClientSize; public Size AllocatedSize => ClientSize;
} }
@ -223,7 +216,7 @@ namespace Avalonia.Win32.Interop.Wpf
(Key)e.Key, (Key)e.Key,
GetModifiers(null))); GetModifiers(null)));
protected override void OnTextInput(TextCompositionEventArgs e) protected override void OnTextInput(TextCompositionEventArgs e)
=> _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, _inputRoot, e.Text)); => _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, _inputRoot, e.Text));
void ITopLevelImpl.SetCursor(ICursorImpl cursor) void ITopLevelImpl.SetCursor(ICursorImpl cursor)

14
src/Windows/Avalonia.Win32/TrayIconImpl.cs

@ -81,19 +81,18 @@ namespace Avalonia.Win32
private void UpdateIcon(bool remove = false) private void UpdateIcon(bool remove = false)
{ {
var iconData = new NOTIFYICONDATA() var iconData = new NOTIFYICONDATA
{ {
hWnd = Win32Platform.Instance.Handle, hWnd = Win32Platform.Instance.Handle,
uID = _uniqueId, uID = _uniqueId
uFlags = NIF.TIP | NIF.MESSAGE,
uCallbackMessage = (int)CustomWindowsMessage.WM_TRAYMOUSE,
hIcon = _icon?.HIcon ?? s_emptyIcon,
szTip = _tooltipText ?? ""
}; };
if (!remove) if (!remove)
{ {
iconData.uFlags |= NIF.ICON; iconData.uFlags = NIF.TIP | NIF.MESSAGE | NIF.ICON;
iconData.uCallbackMessage = (int)CustomWindowsMessage.WM_TRAYMOUSE;
iconData.hIcon = _icon?.HIcon ?? s_emptyIcon;
iconData.szTip = _tooltipText ?? "";
if (!_iconAdded) if (!_iconAdded)
{ {
@ -107,6 +106,7 @@ namespace Avalonia.Win32
} }
else else
{ {
iconData.uFlags = 0;
Shell_NotifyIcon(NIM.DELETE, iconData); Shell_NotifyIcon(NIM.DELETE, iconData);
_iconAdded = false; _iconAdded = false;
} }

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

@ -6,12 +6,10 @@ using System.IO;
using Avalonia.Reactive; using Avalonia.Reactive;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Media;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
@ -20,7 +18,6 @@ using Avalonia.Threading;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Win32.Input; using Avalonia.Win32.Input;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using Avalonia.Win32.WinRT;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia namespace Avalonia
@ -68,6 +65,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Render Avalonia to a Texture inside the Windows.UI.Composition tree. /// Render Avalonia to a Texture inside the Windows.UI.Composition tree.
/// This setting is true by default.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Supported on Windows 10 build 16299 and above. Ignored on other versions. /// Supported on Windows 10 build 16299 and above. Ignored on other versions.
@ -88,9 +86,19 @@ namespace Avalonia
/// This is only recommended if low input latency is desirable, and there is no need for the transparency /// This is only recommended if low input latency is desirable, and there is no need for the transparency
/// and stylings / blurrings offered by <see cref="UseWindowsUIComposition"/><br/> /// and stylings / blurrings offered by <see cref="UseWindowsUIComposition"/><br/>
/// This is mutually exclusive with /// This is mutually exclusive with
/// <see cref="UseWindowsUIComposition"/> which if active will override this setting. /// <see cref="UseWindowsUIComposition"/> which if active will override this setting.
/// This setting is false by default.
/// </summary> /// </summary>
public bool UseLowLatencyDxgiSwapChain { get; set; } = false; public bool UseLowLatencyDxgiSwapChain { get; set; }
/// <summary>
/// Render directly on the UI thread instead of using a dedicated render thread.
/// Only applicable if both <see cref="UseWindowsUIComposition"/> and <see cref="UseLowLatencyDxgiSwapChain"/>
/// are false.
/// This setting is only recommended for interop with systems that must render on the UI thread, such as WPF.
/// This setting is false by default.
/// </summary>
public bool ShouldRenderOnUIThread { get; set; }
/// <summary> /// <summary>
/// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu
@ -128,7 +136,7 @@ namespace Avalonia.Win32
internal static bool UseOverlayPopups => Options.OverlayPopups; internal static bool UseOverlayPopups => Options.OverlayPopups;
public static Win32PlatformOptions Options { get; private set; } public static Win32PlatformOptions Options { get; private set; }
internal static Compositor Compositor { get; private set; } internal static Compositor Compositor { get; private set; }
public static void Initialize() public static void Initialize()
@ -139,6 +147,8 @@ namespace Avalonia.Win32
public static void Initialize(Win32PlatformOptions options) public static void Initialize(Win32PlatformOptions options)
{ {
Options = options; Options = options;
var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60);
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardImpl>() .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<ICursorFactory>().ToConstant(CursorFactory.Instance) .Bind<ICursorFactory>().ToConstant(CursorFactory.Instance)
@ -146,7 +156,7 @@ namespace Avalonia.Win32
.Bind<IPlatformSettings>().ToSingleton<Win32PlatformSettings>() .Bind<IPlatformSettings>().ToSingleton<Win32PlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance) .Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<IRenderLoop>().ToConstant(new RenderLoop()) .Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) .Bind<IRenderTimer>().ToConstant(renderTimer)
.Bind<IWindowingPlatform>().ToConstant(s_instance) .Bind<IWindowingPlatform>().ToConstant(s_instance)
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control) .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control)
{ {

36
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -372,6 +372,20 @@ namespace Avalonia.Base.UnitTests
Assert.Null(target.GetValue(property)); Assert.Null(target.GetValue(property));
} }
[Fact]
public void LocalValue_Bind_Generic_To_ValueType_Accepts_UnsetValue()
{
var target = new Class1();
var source = new Subject<BindingValue<double>>();
target.Bind(Class1.QuxProperty, source);
source.OnNext(6.7);
source.OnNext(BindingValue<double>.Unset);
Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
Assert.True(target.IsSet(Class1.QuxProperty));
}
[Fact] [Fact]
public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue() public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue()
{ {
@ -383,7 +397,7 @@ namespace Avalonia.Base.UnitTests
source.OnNext(AvaloniaProperty.UnsetValue); source.OnNext(AvaloniaProperty.UnsetValue);
Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
Assert.False(target.IsSet(Class1.QuxProperty)); Assert.True(target.IsSet(Class1.QuxProperty));
} }
[Fact] [Fact]
@ -397,7 +411,7 @@ namespace Avalonia.Base.UnitTests
source.OnNext(AvaloniaProperty.UnsetValue); source.OnNext(AvaloniaProperty.UnsetValue);
Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
Assert.False(target.IsSet(Class1.QuxProperty)); Assert.True(target.IsSet(Class1.QuxProperty));
} }
[Fact] [Fact]
@ -1285,6 +1299,24 @@ namespace Avalonia.Base.UnitTests
subscription.Dispose(); subscription.Dispose();
} }
[Theory]
[InlineData(BindingPriority.LocalValue)]
[InlineData(BindingPriority.Style)]
public void Binding_Producing_UnsetValue_Does_Not_Cause_Unsubscribe(BindingPriority priority)
{
var target = new Class1();
var source = new Subject<BindingValue<string>>();
target.Bind(Class1.FooProperty, source, priority);
source.OnNext("foo");
Assert.Equal("foo", target.GetValue(Class1.FooProperty));
source.OnNext(BindingValue<string>.Unset);
Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
source.OnNext("bar");
Assert.Equal("bar", target.GetValue(Class1.FooProperty));
}
[Fact] [Fact]
public void Produces_Correct_Values_And_Base_Values_With_Multiple_Animation_Bindings() public void Produces_Correct_Values_And_Base_Values_With_Multiple_Animation_Bindings()
{ {

18
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs

@ -66,7 +66,7 @@ namespace Avalonia.Base.UnitTests
} }
[Fact] [Fact]
public void Reverts_To_Lower_Priority_If_Style_Binding_Fails_Validation() public void Reverts_To_DefaultValue_If_Style_Binding_Fails_Validation_2()
{ {
var target = new Class1(); var target = new Class1();
var source = new Subject<int>(); var source = new Subject<int>();
@ -75,7 +75,21 @@ namespace Avalonia.Base.UnitTests
target.Bind(Class1.FooProperty, source, BindingPriority.StyleTrigger); target.Bind(Class1.FooProperty, source, BindingPriority.StyleTrigger);
source.OnNext(150); source.OnNext(150);
Assert.Equal(10, target.GetValue(Class1.FooProperty)); Assert.Equal(11, target.GetValue(Class1.FooProperty));
}
[Theory]
[InlineData(BindingPriority.LocalValue)]
[InlineData(BindingPriority.Style)]
public void Reverts_To_DefaultValue_If_Style_Binding_Fails_Validation_3(BindingPriority priority)
{
var target = new Class1();
var source = new Subject<BindingValue<int>>();
target.Bind(Class1.FooProperty, source, priority);
source.OnNext(150);
Assert.Equal(11, target.GetValue(Class1.FooProperty));
} }
[Fact] [Fact]

25
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

@ -307,7 +307,30 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.True(buttonResources.ContainsDeferredKey("Red")); Assert.True(buttonResources.ContainsDeferredKey("Red"));
} }
} }
[Fact]
public void Dynamically_Changing_Referenced_Resources_Works_With_DynamicResource()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<Color x:Key='color'>Red</Color>
<SolidColorBrush x:Key='brush' Color='{DynamicResource color}' />
</UserControl.Resources>
</UserControl>";
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
Assert.Equal(Colors.Red, ((ISolidColorBrush)userControl.FindResource("brush")!).Color);
userControl.Resources.Remove("color");
Assert.Equal(default, ((ISolidColorBrush)userControl.FindResource("brush")!).Color);
userControl.Resources.Add("color", Colors.Blue);
Assert.Equal(Colors.Blue, ((ISolidColorBrush)userControl.FindResource("brush")!).Color);
}
private IDisposable StyledWindow(params (string, string)[] assets) private IDisposable StyledWindow(params (string, string)[] assets)
{ {
var services = TestServices.StyledWindow.With( var services = TestServices.StyledWindow.With(

Loading…
Cancel
Save