diff --git a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs index 11e5e32cf1..938c45a4e2 100644 --- a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs +++ b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs @@ -59,10 +59,12 @@ namespace ControlCatalog.Pages }; StreamGeometry sg = new StreamGeometry(); - 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.EndFigure(true); + 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.EndFigure(true); + } _smileGeometry = sg.Clone(); } diff --git a/samples/interop/WindowsInteropTest/Program.cs b/samples/interop/WindowsInteropTest/Program.cs index fac06d74b0..c2d30c67bb 100644 --- a/samples/interop/WindowsInteropTest/Program.cs +++ b/samples/interop/WindowsInteropTest/Program.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.Controls; using ControlCatalog; using Avalonia; @@ -15,7 +14,15 @@ namespace WindowsInteropTest { System.Windows.Forms.Application.EnableVisualStyles(); System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); - AppBuilder.Configure().UseWin32().UseDirect2D1().SetupWithoutStarting(); + AppBuilder.Configure() + .UseWin32() + .UseDirect2D1() + .With(new Win32PlatformOptions + { + UseWindowsUIComposition = false, + ShouldRenderOnUIThread = true // necessary for WPF + }) + .SetupWithoutStarting(); System.Windows.Forms.Application.Run(new SelectorForm()); } } diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index 1643ca3ee2..95f77f6df9 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -2,7 +2,7 @@ WinExe net461 - + x64 true true @@ -10,9 +10,6 @@ - - {d0a739b9-3c68-4ba6-a328-41606954b6bd} - ControlCatalog - + diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 50a7a5c831..74dc55355b 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -277,8 +277,8 @@ namespace Avalonia /// The property. /// True if the property is set, otherwise false. /// - /// Checks whether a value is assigned to the property, or that there is a binding to the - /// property that is producing a value other than . + /// Returns true if is a styled property which has a value + /// assigned to it or a binding targeting it; otherwise false. /// public bool IsSet(AvaloniaProperty property) { diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 11dc80ef8f..e1ff0970c2 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -16,6 +16,8 @@ namespace Avalonia.PropertyStore private IDisposable? _subscription; private bool _hasValue; private TValue? _value; + private TValue? _defaultValue; + private bool _isDefaultValueInitialized; protected BindingEntryBase( ValueFrame frame, @@ -89,6 +91,7 @@ namespace Avalonia.PropertyStore protected abstract BindingValue ConvertAndValidate(TSource value); protected abstract BindingValue ConvertAndValidate(BindingValue value); + protected abstract TValue GetDefaultValue(Type ownerType); 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 value) { static void Execute(BindingEntryBase instance, BindingValue value) @@ -124,24 +116,20 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value); - if (value.HasValue) - { - if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, value.Value)) - { - 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) + var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); + + if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, effectiveValue)) { - instance.ClearValue(); + instance._value = effectiveValue; + instance._hasValue = true; 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()) { Execute(this, value); @@ -161,5 +149,16 @@ namespace Avalonia.PropertyStore _subscription = null; Frame.OnBindingCompleted(this); } + + private TValue GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index f89cb029b6..5908d9e535 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -10,6 +10,8 @@ namespace Avalonia.PropertyStore { private readonly ValueStore _owner; private IDisposable? _subscription; + private T? _defaultValue; + private bool _isDefaultValueInitialized; public LocalValueBindingObserver(ValueStore owner, StyledProperty property) { @@ -41,26 +43,28 @@ namespace Avalonia.PropertyStore public void OnNext(T value) { - static void Execute(ValueStore owner, StyledProperty property, T value) + static void Execute(LocalValueBindingObserver instance, T value) { - if (property.ValidateValue?.Invoke(value) != false) - owner.SetValue(property, value, BindingPriority.LocalValue); - else - owner.ClearLocalValue(property); + var owner = instance._owner; + var property = instance.Property; + + if (property.ValidateValue?.Invoke(value) == false) + value = instance.GetCachedDefaultValue(); + + owner.SetValue(property, value, BindingPriority.LocalValue); } if (Dispatcher.UIThread.CheckAccess()) { - Execute(_owner, Property, value); + Execute(this, value); } else { // 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. - var instance = _owner; - var property = Property; + var instance = this; 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); if (value.HasValue) - owner.SetValue(property, value.Value, BindingPriority.LocalValue); - else if (value.Type != BindingValueType.DataValidationError) - owner.ClearLocalValue(property); + { + var effectiveValue = value.Value; + 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()) { Execute(this, value); @@ -92,5 +106,16 @@ namespace Avalonia.PropertyStore Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } + + private T GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs index 2d157b2519..46e6ed810a 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs @@ -1,5 +1,4 @@ using System; -using System.Security.Cryptography; using Avalonia.Data; using Avalonia.Threading; @@ -10,6 +9,8 @@ namespace Avalonia.PropertyStore { private readonly ValueStore _owner; private IDisposable? _subscription; + private T? _defaultValue; + private bool _isDefaultValueInitialized; public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty property) { @@ -49,11 +50,7 @@ namespace Avalonia.PropertyStore if (value == AvaloniaProperty.UnsetValue) { - owner.ClearLocalValue(property); - } - else if (value == BindingOperations.DoNothing) - { - // Do nothing! + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); } else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue)) { @@ -61,11 +58,14 @@ namespace Avalonia.PropertyStore } else { - owner.ClearLocalValue(property); + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value); } } + if (value == BindingOperations.DoNothing) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -79,5 +79,16 @@ namespace Avalonia.PropertyStore Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } + + private T GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs index b56d0d4529..b82714817b 100644 --- a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs @@ -31,5 +31,7 @@ namespace Avalonia.PropertyStore { throw new NotSupportedException(); } + + protected override TTarget GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType); } } diff --git a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs index 697725c87b..550f5c0001 100644 --- a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs @@ -48,5 +48,7 @@ namespace Avalonia.PropertyStore return value; } + + protected override T GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType); } } diff --git a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs index f8becb2e06..a77d7fddb6 100644 --- a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs @@ -29,5 +29,10 @@ namespace Avalonia.PropertyStore { throw new NotSupportedException(); } + + protected override object? GetDefaultValue(Type ownerType) + { + return ((IStyledPropertyMetadata)Property.GetMetadata(ownerType)).DefaultValue; + } } } diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index f36a96992b..a758360545 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -380,23 +380,6 @@ namespace Avalonia.PropertyStore } } - /// - /// Called by non-LocalValue binding entries to re-evaluate the effective value when the - /// binding produces an unset value. - /// - /// The bound property. - /// The priority of binding which produced a new value. - 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); - } - } - /// /// Called by a when its /// state changes. diff --git a/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs index d0d3dd9715..7b0fecf675 100644 --- a/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs +++ b/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs @@ -1,6 +1,4 @@ using System; -using System.Diagnostics; -using System.Threading.Tasks; using Avalonia.Platform; namespace Avalonia.Rendering @@ -59,7 +57,8 @@ namespace Avalonia.Rendering } } - public bool RunsInBackground => true; + /// + public virtual bool RunsInBackground => true; /// /// Starts the timer. diff --git a/src/Avalonia.Base/Rendering/IRenderLoop.cs b/src/Avalonia.Base/Rendering/IRenderLoop.cs index e500ecdf8b..ebe683949d 100644 --- a/src/Avalonia.Base/Rendering/IRenderLoop.cs +++ b/src/Avalonia.Base/Rendering/IRenderLoop.cs @@ -27,7 +27,10 @@ namespace Avalonia.Rendering /// /// The update task. void Remove(IRenderLoopTask i); - + + /// + /// Indicates if the rendering is done on a non-UI thread. + /// bool RunsInBackground { get; } } } diff --git a/src/Avalonia.Base/Rendering/RenderLoop.cs b/src/Avalonia.Base/Rendering/RenderLoop.cs index 1f58ca3827..185f44d29a 100644 --- a/src/Avalonia.Base/Rendering/RenderLoop.cs +++ b/src/Avalonia.Base/Rendering/RenderLoop.cs @@ -87,6 +87,7 @@ namespace Avalonia.Rendering } } + /// public bool RunsInBackground => Timer.RunsInBackground; private void TimerTick(TimeSpan time) diff --git a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs index 1bbf804b5f..7f2eedc98c 100644 --- a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs +++ b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs @@ -8,13 +8,20 @@ namespace Avalonia.Rendering /// /// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms /// - public class UiThreadRenderTimer : DefaultRenderTimer { + /// + /// Initializes a new instance of the class. + /// + /// The number of frames per second at which the loop should run. public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond) { } + /// + public override bool RunsInBackground => false; + + /// protected override IDisposable StartCore(Action tick) { bool cancelled = false; diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index 1bb574acd2..78ba23c1dd 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -225,7 +225,7 @@ namespace Avalonia.Controls /// Renders the control. /// /// The drawing context. - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { _borderRenderHelper.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush, BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray); diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index e4487d29fa..e1f840672d 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -80,7 +80,7 @@ namespace Avalonia.Controls _subscription?.Dispose(); } - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc) { diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 2501440ff2..ff9fb4e31d 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -3256,7 +3256,7 @@ namespace Avalonia.Controls /// /// UpdateRenderBounds. /// - public override void Render(DrawingContext drawingContext) + public sealed override void Render(DrawingContext drawingContext) { var grid = this.GetVisualParent(); diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 2cf0fc3ec4..3e76835e92 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -73,7 +73,7 @@ namespace Avalonia.Controls /// Renders the control. /// /// The drawing context. - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { var source = Source; diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 007d18c813..a7dc035459 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -68,7 +68,7 @@ namespace Avalonia.Controls /// Renders the visual to a . /// /// The drawing context. - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { var background = Background; if (background != null) diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 584dfea97f..be61bb18a1 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -534,7 +534,7 @@ namespace Avalonia.Controls.Presenters } /// - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { _borderRenderer.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush, BoxShadow); diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index f599511392..3481b1ecf3 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -388,7 +388,7 @@ namespace Avalonia.Controls.Presenters TextLayout.Draw(context, new Point(left, top)); } - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 49e76d0728..ed3412bb45 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -60,10 +60,9 @@ namespace Avalonia.Controls.Primitives /// Renders the to a drawing context. /// /// The drawing context. - public override void Render(DrawingContext context) + protected internal override void RenderCore(DrawingContext context) { - base.Render(context); - + base.RenderCore(context); int underscore = Text?.IndexOf('_') ?? -1; if (underscore != -1 && ShowAccessKey) diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index 5cefb0d89f..b9854ab837 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -69,7 +69,7 @@ namespace Avalonia.Controls.Remote 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) { diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index c8bf95b3f7..461dc1c947 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -193,7 +193,7 @@ namespace Avalonia.Controls.Shapes set { SetValue(StrokeJoinProperty, value); } } - public override void Render(DrawingContext context) + public sealed override void Render(DrawingContext context) { var geometry = RenderedGeometry; diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index ec31470126..df98d1073e 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -549,7 +549,13 @@ namespace Avalonia.Controls /// Renders the to a drawing context. /// /// The drawing context. - 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; diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs index 4d902d3d5f..63fe9c9384 100644 --- a/src/Avalonia.Controls/TickBar.cs +++ b/src/Avalonia.Controls/TickBar.cs @@ -213,7 +213,7 @@ namespace Avalonia.Controls /// /// Brush that use to fill ticks is specified by Fill property. /// - public override void Render(DrawingContext dc) + public sealed override void Render(DrawingContext dc) { var size = new Size(Bounds.Width, Bounds.Height); var range = Maximum - Minimum; diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 2d0f351d58..13eae1992c 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -45,13 +45,6 @@ namespace Avalonia.Win32.Interop.Wpf ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); } - protected override void HandleResized(Size clientSize, PlatformResizeReason reason) - { - ClientSize = clientSize; - LayoutManager.ExecuteLayoutPass(); - Renderer?.Resized(clientSize); - } - public Size AllocatedSize => ClientSize; } @@ -223,7 +216,7 @@ namespace Avalonia.Win32.Interop.Wpf (Key)e.Key, 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)); void ITopLevelImpl.SetCursor(ICursorImpl cursor) diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index ab70d77a09..fe920ff1a1 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -81,19 +81,18 @@ namespace Avalonia.Win32 private void UpdateIcon(bool remove = false) { - var iconData = new NOTIFYICONDATA() + var iconData = new NOTIFYICONDATA { hWnd = Win32Platform.Instance.Handle, - uID = _uniqueId, - uFlags = NIF.TIP | NIF.MESSAGE, - uCallbackMessage = (int)CustomWindowsMessage.WM_TRAYMOUSE, - hIcon = _icon?.HIcon ?? s_emptyIcon, - szTip = _tooltipText ?? "" + uID = _uniqueId }; 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) { @@ -107,6 +106,7 @@ namespace Avalonia.Win32 } else { + iconData.uFlags = 0; Shell_NotifyIcon(NIM.DELETE, iconData); _iconAdded = false; } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index f16a1ca8cf..c34be9008a 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -6,12 +6,10 @@ using System.IO; using Avalonia.Reactive; using System.Runtime.InteropServices; using System.Threading; -using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; -using Avalonia.Media; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; @@ -20,7 +18,6 @@ using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; -using Avalonia.Win32.WinRT; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia @@ -68,6 +65,7 @@ namespace Avalonia /// /// Render Avalonia to a Texture inside the Windows.UI.Composition tree. + /// This setting is true by default. /// /// /// 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 /// and stylings / blurrings offered by
/// This is mutually exclusive with - /// which if active will override this setting. + /// which if active will override this setting. + /// This setting is false by default. /// - public bool UseLowLatencyDxgiSwapChain { get; set; } = false; + public bool UseLowLatencyDxgiSwapChain { get; set; } + + /// + /// Render directly on the UI thread instead of using a dedicated render thread. + /// Only applicable if both and + /// 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. + /// + public bool ShouldRenderOnUIThread { get; set; } /// /// 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; public static Win32PlatformOptions Options { get; private set; } - + internal static Compositor Compositor { get; private set; } public static void Initialize() @@ -139,6 +147,8 @@ namespace Avalonia.Win32 public static void Initialize(Win32PlatformOptions options) { Options = options; + var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60); + AvaloniaLocator.CurrentMutable .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) @@ -146,7 +156,7 @@ namespace Avalonia.Win32 .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) - .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(renderTimer) .Bind().ToConstant(s_instance) .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control) { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 030d6ba215..baaed5104d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -372,6 +372,20 @@ namespace Avalonia.Base.UnitTests Assert.Null(target.GetValue(property)); } + [Fact] + public void LocalValue_Bind_Generic_To_ValueType_Accepts_UnsetValue() + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.QuxProperty, source); + source.OnNext(6.7); + source.OnNext(BindingValue.Unset); + + Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); + } + [Fact] public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue() { @@ -383,7 +397,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); - Assert.False(target.IsSet(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); } [Fact] @@ -397,7 +411,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); - Assert.False(target.IsSet(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); } [Fact] @@ -1285,6 +1299,24 @@ namespace Avalonia.Base.UnitTests 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>(); + + target.Bind(Class1.FooProperty, source, priority); + + source.OnNext("foo"); + Assert.Equal("foo", target.GetValue(Class1.FooProperty)); + source.OnNext(BindingValue.Unset); + Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); + source.OnNext("bar"); + Assert.Equal("bar", target.GetValue(Class1.FooProperty)); + } + [Fact] public void Produces_Correct_Values_And_Base_Values_With_Multiple_Animation_Bindings() { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index e8175cf477..513aeb65ab 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -66,7 +66,7 @@ namespace Avalonia.Base.UnitTests } [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 source = new Subject(); @@ -75,7 +75,21 @@ namespace Avalonia.Base.UnitTests target.Bind(Class1.FooProperty, source, BindingPriority.StyleTrigger); 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>(); + + target.Bind(Class1.FooProperty, source, priority); + source.OnNext(150); + + Assert.Equal(11, target.GetValue(Class1.FooProperty)); } [Fact] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 6cab83751f..bea6bc4dc8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -307,7 +307,30 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.True(buttonResources.ContainsDeferredKey("Red")); } } - + + [Fact] + public void Dynamically_Changing_Referenced_Resources_Works_With_DynamicResource() + { + var xaml = @" + + + Red + + +"; + + 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) { var services = TestServices.StyledWindow.With(