diff --git a/Avalonia.sln b/Avalonia.sln index 3a2c619d5b..f6dc039c2f 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -201,9 +201,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}" EndProject @@ -211,8 +211,8 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5 - src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4 - src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 5 + src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5 src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5 src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 1cf3bc75b0..6800ff7d68 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -278,6 +278,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0; virtual HRESULT GetWindowState(AvnWindowState*ret) = 0; + virtual HRESULT TakeFocusFromChildren() = 0; }; AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown @@ -493,8 +494,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown virtual void* GetParentHandle() = 0; virtual HRESULT InitializeWithChildHandle(void* child) = 0; virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0; - virtual void MoveTo(float x, float y, float width, float height) = 0; - virtual void Hide() = 0; + virtual void ShowInBounds(float x, float y, float width, float height) = 0; + virtual void HideWithSize(float width, float height) = 0; virtual void ReleaseChild() = 0; }; diff --git a/native/Avalonia.Native/src/OSX/controlhost.mm b/native/Avalonia.Native/src/OSX/controlhost.mm index 315ec2f310..5ee2344ac7 100644 --- a/native/Avalonia.Native/src/OSX/controlhost.mm +++ b/native/Avalonia.Native/src/OSX/controlhost.mm @@ -97,7 +97,7 @@ public: return S_OK; }; - virtual void MoveTo(float x, float y, float width, float height) override + virtual void ShowInBounds(float x, float y, float width, float height) override { if(_child == nil) return; @@ -106,7 +106,7 @@ public: IAvnNativeControlHostTopLevelAttachment* slf = this; slf->AddRef(); dispatch_async(dispatch_get_main_queue(), ^{ - slf->MoveTo(x, y, width, height); + slf->ShowInBounds(x, y, width, height); slf->Release(); }); return; @@ -122,9 +122,24 @@ public: [[_holder superview] setNeedsDisplay:true]; } - virtual void Hide() override + virtual void HideWithSize(float width, float height) override { + if(_child == nil) + return; + if(AvnInsidePotentialDeadlock::IsInside()) + { + IAvnNativeControlHostTopLevelAttachment* slf = this; + slf->AddRef(); + dispatch_async(dispatch_get_main_queue(), ^{ + slf->HideWithSize(width, height); + slf->Release(); + }); + return; + } + + NSRect frame = {0, 0, width, height}; [_holder setHidden: true]; + [_child setFrame: frame]; } virtual void ReleaseChild() override diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 86b3584681..7f8a6e1393 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -116,10 +116,15 @@ public: { SetPosition(lastPositionSet); UpdateStyle(); - - [Window makeKeyAndOrderFront:Window]; - [NSApp activateIgnoringOtherApps:YES]; - + if(ShouldTakeFocusOnShow()) + { + [Window makeKeyAndOrderFront:Window]; + [NSApp activateIgnoringOtherApps:YES]; + } + else + { + [Window orderFront: Window]; + } [Window setTitle:_lastTitle]; _shown = true; @@ -128,6 +133,11 @@ public: } } + virtual bool ShouldTakeFocusOnShow() + { + return true; + } + virtual HRESULT Hide () override { @autoreleasepool @@ -774,6 +784,15 @@ private: } } + virtual HRESULT TakeFocusFromChildren () override + { + if(Window == nil) + return S_OK; + if([Window isKeyWindow]) + [Window makeFirstResponder: View]; + return S_OK; + } + void EnterFullScreenMode () { _fullScreenActive = true; @@ -1858,7 +1877,6 @@ private: WindowEvents = events; [Window setLevel:NSPopUpMenuWindowLevel]; } - protected: virtual NSWindowStyleMask GetStyle() override { @@ -1876,6 +1894,11 @@ protected: return S_OK; } } +public: + virtual bool ShouldTakeFocusOnShow() override + { + return false; + } }; extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl) diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml index 13bae59805..2ec0b48c76 100644 --- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml +++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml @@ -1,29 +1,19 @@ - + ProgressBar A progress bar control - - - + + + - - + - - + - - + + diff --git a/samples/interop/NativeEmbedSample/MainWindow.xaml b/samples/interop/NativeEmbedSample/MainWindow.xaml index dcec9035e0..f2161a1bea 100644 --- a/samples/interop/NativeEmbedSample/MainWindow.xaml +++ b/samples/interop/NativeEmbedSample/MainWindow.xaml @@ -20,7 +20,16 @@ + + + + Text + + + Tooltip + + diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs index d915887e4c..b52829a60f 100644 --- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs +++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs @@ -140,6 +140,7 @@ namespace Avalonia.Collections } } + [Obsolete("Causes memory leaks. Use DynamicData or similar instead.")] public static IAvaloniaReadOnlyList CreateDerivedList( this IAvaloniaReadOnlyList collection, Func select) diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index 94ef0b2284..20eac11c2c 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -1,7 +1,9 @@ +using System; +using System.Collections.Generic; using Avalonia.Controls.Platform; -using Avalonia.LogicalTree; using Avalonia.Platform; using Avalonia.Threading; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -12,14 +14,18 @@ namespace Avalonia.Controls private INativeControlHostControlTopLevelAttachment _attachment; private IPlatformHandle _nativeControlHandle; private bool _queuedForDestruction; + private bool _queuedForMoveResize; + private readonly List _propertyChangedSubscriptions = new List(); + private readonly EventHandler _propertyChangedHandler; static NativeControlHost() { IsVisibleProperty.Changed.AddClassHandler(OnVisibleChanged); - TransformedBoundsProperty.Changed.AddClassHandler(OnBoundsChanged); } - private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) - => host.UpdateHost(); + public NativeControlHost() + { + _propertyChangedHandler = PropertyChangedHandler; + } private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) => host.UpdateHost(); @@ -27,21 +33,46 @@ namespace Avalonia.Controls protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = e.Root as TopLevel; + var visual = (IVisual)this; + while (visual != _currentRoot) + { + + if (visual is Visual v) + { + v.PropertyChanged += _propertyChangedHandler; + _propertyChangedSubscriptions.Add(v); + } + + visual = visual.GetVisualParent(); + } + UpdateHost(); } + private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.IsEffectiveValueChange && e.Property == BoundsProperty) + EnqueueForMoveResize(); + } + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = null; + if (_propertyChangedSubscriptions != null) + { + foreach (var v in _propertyChangedSubscriptions) + v.PropertyChanged -= _propertyChangedHandler; + _propertyChangedSubscriptions.Clear(); + } UpdateHost(); } - void UpdateHost() + private void UpdateHost() { + _queuedForMoveResize = false; _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost; var needsAttachment = _currentHost != null; - var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue; if (needsAttachment) { @@ -93,22 +124,46 @@ namespace Avalonia.Controls } } - if (needsShow) - _attachment?.ShowInBounds(TransformedBounds.Value); - else if (needsAttachment) - _attachment?.Hide(); + if (_attachment?.AttachedTo != _currentHost) + return; + + TryUpdateNativeControlPosition(); + } + + + private Rect? GetAbsoluteBounds() + { + var bounds = Bounds; + var position = this.TranslatePoint(bounds.Position, _currentRoot); + if (position == null) + return null; + return new Rect(position.Value, bounds.Size); + } + + void EnqueueForMoveResize() + { + if(_queuedForMoveResize) + return; + _queuedForMoveResize = true; + Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render); } public bool TryUpdateNativeControlPosition() { - var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue; + if (_currentHost == null) + return false; + + var bounds = GetAbsoluteBounds(); + var needsShow = IsEffectivelyVisible && bounds.HasValue; - if(needsShow) - _attachment?.ShowInBounds(TransformedBounds.Value); - return needsShow; + if (needsShow) + _attachment?.ShowInBounds(bounds.Value); + else + _attachment?.HideWithSize(Bounds.Size); + return false; } - void CheckDestruction() + private void CheckDestruction() { _queuedForDestruction = false; if (_currentRoot == null) @@ -117,10 +172,12 @@ namespace Avalonia.Controls protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) { + if (_currentHost == null) + throw new InvalidOperationException(); return _currentHost.CreateDefaultChild(parent); } - void DestroyNativeControl() + private void DestroyNativeControl() { if (_nativeControlHandle != null) { diff --git a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs index 7a4568abc6..c6b1d09849 100644 --- a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs +++ b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs @@ -21,8 +21,8 @@ namespace Avalonia.Controls.Platform { INativeControlHostImpl AttachedTo { get; set; } bool IsCompatibleWith(INativeControlHostImpl host); - void Hide(); - void ShowInBounds(TransformedBounds transformedBounds); + void HideWithSize(Size size); + void ShowInBounds(Rect rect); } public interface ITopLevelImplWithNativeControlHost diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index c7356f2b4d..e904957429 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -1,8 +1,7 @@ - using System; using Avalonia.Controls.Primitives; -using Avalonia.Data; using Avalonia.Layout; +using Avalonia.Media; namespace Avalonia.Controls { @@ -11,6 +10,92 @@ namespace Avalonia.Controls /// public class ProgressBar : RangeBase { + public class ProgressBarTemplateProperties : AvaloniaObject + { + private double _container2Width; + private double _containerWidth; + private double _containerAnimationStartPosition; + private double _containerAnimationEndPosition; + private double _container2AnimationStartPosition; + private double _container2AnimationEndPosition; + + public static readonly DirectProperty ContainerAnimationStartPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(ContainerAnimationStartPosition), + p => p.ContainerAnimationStartPosition, + (p, o) => p.ContainerAnimationStartPosition = o, 0d); + + public static readonly DirectProperty ContainerAnimationEndPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(ContainerAnimationEndPosition), + p => p.ContainerAnimationEndPosition, + (p, o) => p.ContainerAnimationEndPosition = o, 0d); + + public static readonly DirectProperty Container2AnimationStartPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(Container2AnimationStartPosition), + p => p.Container2AnimationStartPosition, + (p, o) => p.Container2AnimationStartPosition = o, 0d); + + public static readonly DirectProperty Container2AnimationEndPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(Container2AnimationEndPosition), + p => p.Container2AnimationEndPosition, + (p, o) => p.Container2AnimationEndPosition = o); + + public static readonly DirectProperty Container2WidthProperty = + AvaloniaProperty.RegisterDirect( + nameof(Container2Width), + p => p.Container2Width, + (p, o) => p.Container2Width = o); + + public static readonly DirectProperty ContainerWidthProperty = + AvaloniaProperty.RegisterDirect( + nameof(ContainerWidth), + p => p.ContainerWidth, + (p, o) => p.ContainerWidth = o); + + public double ContainerAnimationStartPosition + { + get => _containerAnimationStartPosition; + set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, value); + } + + public double ContainerAnimationEndPosition + { + get => _containerAnimationEndPosition; + set => SetAndRaise(ContainerAnimationEndPositionProperty, ref _containerAnimationEndPosition, value); + } + + public double Container2AnimationStartPosition + { + get => _container2AnimationStartPosition; + set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, value); + } + + public double Container2Width + { + get => _container2Width; + set => SetAndRaise(Container2WidthProperty, ref _container2Width, value); + } + + public double ContainerWidth + { + get => _containerWidth; + set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value); + } + + public double Container2AnimationEndPosition + { + get => _container2AnimationEndPosition; + set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value); + } + } + + private double _indeterminateStartingOffset; + private double _indeterminateEndingOffset; + private Border _indicator; + public static readonly StyledProperty IsIndeterminateProperty = AvaloniaProperty.Register(nameof(IsIndeterminate)); @@ -20,19 +105,33 @@ namespace Avalonia.Controls public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal); - private static readonly DirectProperty IndeterminateStartingOffsetProperty = + [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] + public static readonly DirectProperty IndeterminateStartingOffsetProperty = AvaloniaProperty.RegisterDirect( nameof(IndeterminateStartingOffset), p => p.IndeterminateStartingOffset, (p, o) => p.IndeterminateStartingOffset = o); - private static readonly DirectProperty IndeterminateEndingOffsetProperty = + [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] + public static readonly DirectProperty IndeterminateEndingOffsetProperty = AvaloniaProperty.RegisterDirect( nameof(IndeterminateEndingOffset), p => p.IndeterminateEndingOffset, (p, o) => p.IndeterminateEndingOffset = o); - private Border _indicator; + [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] + public double IndeterminateStartingOffset + { + get => _indeterminateStartingOffset; + set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value); + } + + [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")] + public double IndeterminateEndingOffset + { + get => _indeterminateEndingOffset; + set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value); + } static ProgressBar() { @@ -45,6 +144,8 @@ namespace Avalonia.Controls UpdatePseudoClasses(IsIndeterminate, Orientation); } + public ProgressBarTemplateProperties TemplateProperties { get; } = new ProgressBarTemplateProperties(); + public bool IsIndeterminate { get => GetValue(IsIndeterminateProperty); @@ -62,19 +163,6 @@ namespace Avalonia.Controls get => GetValue(OrientationProperty); set => SetValue(OrientationProperty, value); } - private double _indeterminateStartingOffset; - private double IndeterminateStartingOffset - { - get => _indeterminateStartingOffset; - set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value); - } - - private double _indeterminateEndingOffset; - private double IndeterminateEndingOffset - { - get => _indeterminateEndingOffset; - set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value); - } /// protected override Size ArrangeOverride(Size finalSize) @@ -111,21 +199,33 @@ namespace Avalonia.Controls { if (IsIndeterminate) { - if (Orientation == Orientation.Horizontal) - { - var width = bounds.Width / 5.0; - IndeterminateStartingOffset = -width; - _indicator.Width = width; - IndeterminateEndingOffset = bounds.Width; + // Pulled from ModernWPF. - } - else - { - var height = bounds.Height / 5.0; - IndeterminateStartingOffset = -bounds.Height; - _indicator.Height = height; - IndeterminateEndingOffset = height; - } + var dim = Orientation == Orientation.Horizontal ? bounds.Width : bounds.Height; + var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar + var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar + + TemplateProperties.ContainerWidth = barIndicatorWidth; + TemplateProperties.Container2Width = barIndicatorWidth2; + + TemplateProperties.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180% + TemplateProperties.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300% + + TemplateProperties.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150% + TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166% + + // Remove these properties when we switch to fluent as default and removed the old one. + IndeterminateStartingOffset = -(dim / 5d); + IndeterminateEndingOffset = dim; + + var padding = Padding; + var rectangle = new RectangleGeometry( + new Rect( + padding.Left, + padding.Top, + bounds.Width - (padding.Right + padding.Left), + bounds.Height - (padding.Bottom + padding.Top) + )); } else { diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 6c3a2d4a03..f058942116 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -340,6 +340,9 @@ namespace Avalonia.Controls _globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved; } + Renderer?.Dispose(); + Renderer = null; + var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); @@ -349,8 +352,7 @@ namespace Avalonia.Controls (this as IInputRoot).MouseDevice?.TopLevelClosed(this); PlatformImpl = null; OnClosed(EventArgs.Empty); - Renderer?.Dispose(); - Renderer = null; + LayoutManager?.Dispose(); } diff --git a/src/Avalonia.Controls/Utils/IEnumerableUtils.cs b/src/Avalonia.Controls/Utils/IEnumerableUtils.cs index 9b6444fc66..9614d079d9 100644 --- a/src/Avalonia.Controls/Utils/IEnumerableUtils.cs +++ b/src/Avalonia.Controls/Utils/IEnumerableUtils.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; namespace Avalonia.Controls.Utils @@ -15,12 +16,14 @@ namespace Avalonia.Controls.Utils { if (items != null) { - var collection = items as ICollection; - - if (collection != null) + if (items is ICollection collection) { return collection.Count; } + else if (items is IReadOnlyCollection readOnly) + { + return readOnly.Count; + } else { return Enumerable.Count(items.Cast()); diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index b3a8f4745e..4899be2955 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -45,7 +45,15 @@ namespace Avalonia.Diagnostics window.Closed += DevToolsClosed; s_open.Add(root, window); - window.Show(); + + if (root is Window inspectedWindow) + { + window.Show(inspectedWindow); + } + else + { + window.Show(); + } } return Disposable.Create(() => window?.Close()); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs index a7c2997346..38788ef8ee 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.LogicalTree; @@ -9,7 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels public LogicalTreeNode(ILogical logical, TreeNode parent) : base((Control)logical, parent) { - Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this)); + Children = new LogicalTreeNodeCollection(this, logical); } public static LogicalTreeNode[] Create(object control) @@ -17,5 +18,31 @@ namespace Avalonia.Diagnostics.ViewModels var logical = control as ILogical; return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null; } + + internal class LogicalTreeNodeCollection : TreeNodeCollection + { + private readonly ILogical _control; + private IDisposable _subscription; + + public LogicalTreeNodeCollection(TreeNode owner, ILogical control) + : base(owner) + { + _control = control; + } + + public override void Dispose() + { + base.Dispose(); + _subscription?.Dispose(); + } + + protected override void Initialize(AvaloniaList nodes) + { + _subscription = _control.LogicalChildren.ForEachItem( + (i, item) => nodes.Insert(i, new LogicalTreeNode(item, Owner)), + (i, item) => nodes.RemoveAt(i), + () => nodes.Clear()); + } + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 1d19e1a346..acc3ef16c2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; @@ -12,6 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels private readonly TreePageViewModel _logicalTree; private readonly TreePageViewModel _visualTree; private readonly EventsPageViewModel _events; + private readonly IDisposable _pointerOverSubscription; private ViewModelBase _content; private int _selectedTab; private string _focusedControl; @@ -25,16 +27,9 @@ namespace Avalonia.Diagnostics.ViewModels _events = new EventsPageViewModel(root); UpdateFocusedControl(); - KeyboardDevice.Instance.PropertyChanged += (s, e) => - { - if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) - { - UpdateFocusedControl(); - } - }; - + KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged; SelectedTab = 0; - root.GetObservable(TopLevel.PointerOverElementProperty) + _pointerOverSubscription = root.GetObservable(TopLevel.PointerOverElementProperty) .Subscribe(x => PointerOverElement = x?.GetType().Name); Console = new ConsoleViewModel(UpdateConsoleContext); } @@ -129,6 +124,8 @@ namespace Avalonia.Diagnostics.ViewModels public void Dispose() { + KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged; + _pointerOverSubscription.Dispose(); _logicalTree.Dispose(); _visualTree.Dispose(); } @@ -137,5 +134,13 @@ namespace Avalonia.Diagnostics.ViewModels { FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; } + + private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) + { + UpdateFocusedControl(); + } + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index aa27538abc..cb5f5b1fda 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -3,15 +3,15 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Reactive; using System.Reactive.Linq; -using Avalonia.Collections; using Avalonia.Controls; using Avalonia.LogicalTree; using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels { - internal class TreeNode : ViewModelBase + internal class TreeNode : ViewModelBase, IDisposable { + private IDisposable _classesSubscription; private string _classes; private bool _isExpanded; @@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels x => control.Classes.CollectionChanged -= x) .TakeUntil(removed); - classesChanged.Select(_ => Unit.Default) + _classesSubscription = classesChanged.Select(_ => Unit.Default) .StartWith(Unit.Default) .Subscribe(_ => { @@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - public IAvaloniaReadOnlyList Children + public TreeNodeCollection Children { get; protected set; @@ -104,6 +104,12 @@ namespace Avalonia.Diagnostics.ViewModels } } + public void Dispose() + { + _classesSubscription.Dispose(); + Children.Dispose(); + } + private static int IndexOf(IReadOnlyList collection, TreeNode item) { var count = collection.Count; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs new file mode 100644 index 0000000000..8b4f03bd23 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using Avalonia.Collections; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList, IDisposable + { + private AvaloniaList _inner; + + public TreeNodeCollection(TreeNode owner) => Owner = owner; + + public TreeNode this[int index] + { + get + { + EnsureInitialized(); + return _inner[index]; + } + } + + public int Count + { + get + { + EnsureInitialized(); + return _inner.Count; + } + } + + protected TreeNode Owner { get; } + + public event NotifyCollectionChangedEventHandler CollectionChanged + { + add => _inner.CollectionChanged += value; + remove => _inner.CollectionChanged -= value; + } + + public event PropertyChangedEventHandler PropertyChanged + { + add => _inner.PropertyChanged += value; + remove => _inner.PropertyChanged -= value; + } + + public virtual void Dispose() + { + if (_inner is object) + { + foreach (var node in _inner) + { + node.Dispose(); + } + } + } + + public IEnumerator GetEnumerator() + { + EnsureInitialized(); + return _inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + protected abstract void Initialize(AvaloniaList nodes); + + private void EnsureInitialized() + { + if (_inner is null) + { + _inner = new AvaloniaList(); + Initialize(_inner); + } + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index 38ac88a83c..ec48cff399 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -62,7 +62,15 @@ namespace Avalonia.Diagnostics.ViewModels } } - public void Dispose() => _details?.Dispose(); + public void Dispose() + { + foreach (var node in Nodes) + { + node.Dispose(); + } + + _details?.Dispose(); + } public TreeNode FindNode(IControl control) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index 5383cb2b68..bc40edf477 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Collections; using Avalonia.Styling; using Avalonia.VisualTree; @@ -9,16 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels public VisualTreeNode(IVisual visual, TreeNode parent) : base(visual, parent) { - var host = visual as IVisualTreeHost; - - if (host?.Root == null) - { - Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this)); - } - else - { - Children = new AvaloniaList(new[] { new VisualTreeNode(host.Root, this) }); - } + Children = new VisualTreeNodeCollection(this, visual); if ((Visual is IStyleable styleable)) { @@ -33,5 +25,30 @@ namespace Avalonia.Diagnostics.ViewModels var visual = control as IVisual; return visual != null ? new[] { new VisualTreeNode(visual, null) } : null; } + + internal class VisualTreeNodeCollection : TreeNodeCollection + { + private readonly IVisual _control; + private IDisposable _subscription; + + public VisualTreeNodeCollection(TreeNode owner, IVisual control) + : base(owner) + { + _control = control; + } + + public override void Dispose() + { + _subscription?.Dispose(); + } + + protected override void Initialize(AvaloniaList nodes) + { + _subscription = _control.VisualChildren.ForEachItem( + (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)), + (i, item) => nodes.RemoveAt(i), + () => nodes.Clear()); + } + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 3abdb5034a..10861538ae 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -14,8 +14,8 @@ namespace Avalonia.Diagnostics.Views { internal class MainWindow : Window, IStyleHost { + private readonly IDisposable _keySubscription; private TopLevel _root; - private IDisposable _keySubscription; public MainWindow() { @@ -33,8 +33,22 @@ namespace Avalonia.Diagnostics.Views { if (_root != value) { + if (_root != null) + { + _root.Closed -= RootClosed; + } + _root = value; - DataContext = new MainViewModel(value); + + if (_root != null) + { + _root.Closed += RootClosed; + DataContext = new MainViewModel(value); + } + else + { + DataContext = null; + } } } } @@ -45,6 +59,8 @@ namespace Avalonia.Diagnostics.Views { base.OnClosed(e); _keySubscription.Dispose(); + _root.Closed -= RootClosed; + _root = null; ((MainViewModel)DataContext)?.Dispose(); } @@ -70,5 +86,7 @@ namespace Avalonia.Diagnostics.Views } } } + + private void RootClosed(object sender, EventArgs e) => Close(); } } diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 792de774d1..fc988a8d6c 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -106,8 +106,6 @@ namespace Avalonia.Layout if (!_running) { - _running = true; - Stopwatch? stopwatch = null; const LogEventLevel timingLogLevel = LogEventLevel.Information; @@ -128,15 +126,24 @@ namespace Avalonia.Layout _toMeasure.BeginLoop(MaxPasses); _toArrange.BeginLoop(MaxPasses); - for (var pass = 0; pass < MaxPasses; ++pass) + try { - InnerLayoutPass(); + _running = true; - if (!RaiseEffectiveViewportChanged()) + for (var pass = 0; pass < MaxPasses; ++pass) { - break; + InnerLayoutPass(); + + if (!RaiseEffectiveViewportChanged()) + { + break; + } } } + finally + { + _running = false; + } _toMeasure.EndLoop(); _toArrange.EndLoop(); @@ -221,23 +228,16 @@ namespace Avalonia.Layout private void InnerLayoutPass() { - try + for (var pass = 0; pass < MaxPasses; ++pass) { - for (var pass = 0; pass < MaxPasses; ++pass) - { - ExecuteMeasurePass(); - ExecuteArrangePass(); + ExecuteMeasurePass(); + ExecuteArrangePass(); - if (_toMeasure.Count == 0) - { - break; - } + if (_toMeasure.Count == 0) + { + break; } } - finally - { - _running = false; - } } private void ExecuteMeasurePass() @@ -362,7 +362,7 @@ namespace Avalonia.Layout } } - return startCount != _toMeasure.Count + _toMeasure.Count; + return startCount != _toMeasure.Count + _toArrange.Count; } private Rect CalculateEffectiveViewport(IVisual control) diff --git a/src/Avalonia.Native/NativeControlHostImpl.cs b/src/Avalonia.Native/NativeControlHostImpl.cs index 0777c6416b..a46528dc48 100644 --- a/src/Avalonia.Native/NativeControlHostImpl.cs +++ b/src/Avalonia.Native/NativeControlHostImpl.cs @@ -114,19 +114,18 @@ namespace Avalonia.Native public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl; - public void Hide() + public void HideWithSize(Size size) { - _native?.Hide(); + _native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height)); } - public void ShowInBounds(TransformedBounds transformedBounds) + public void ShowInBounds(Rect bounds) { if (_attachedTo == null) throw new InvalidOperationException("Native control isn't attached to a toplevel"); - var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform); bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width), Math.Max(1, bounds.Height)); - _native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height); + _native.ShowInBounds((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height); } public void InitWithChild(IPlatformHandle handle) diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index c41be1723b..b0da5fdc43 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -10,6 +10,7 @@ namespace Avalonia.Native private readonly IAvaloniaNativeFactory _factory; private readonly AvaloniaNativePlatformOptions _opts; private readonly GlPlatformFeature _glFeature; + private readonly IWindowBaseImpl _parent; public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, @@ -19,6 +20,7 @@ namespace Avalonia.Native _factory = factory; _opts = opts; _glFeature = glFeature; + _parent = parent; using (var e = new PopupEvents(this)) { var context = _opts.UseGpu ? glFeature?.DeferredContext : null; @@ -58,6 +60,16 @@ namespace Avalonia.Native } } + public override void Show() + { + var parent = _parent; + while (parent is PopupImpl p) + parent = p._parent; + if (parent is WindowImpl w) + w.Native.TakeFocusFromChildren(); + base.Show(); + } + public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this); public void SetWindowManagerAddShadowHint(bool enabled) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 930b5800ba..9a90f65d1b 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -319,7 +319,7 @@ namespace Avalonia.Native } - public void Show() + public virtual void Show() { _native.Show(); } diff --git a/src/Avalonia.Themes.Default/ProgressBar.xaml b/src/Avalonia.Themes.Default/ProgressBar.xaml index d3c2f0c784..43a0752bc8 100644 --- a/src/Avalonia.Themes.Default/ProgressBar.xaml +++ b/src/Avalonia.Themes.Default/ProgressBar.xaml @@ -1,4 +1,12 @@ + + + + + + + + + + + 0 + 2 + 1 + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/ProgressBar.xaml index d3c2f0c784..ac5dd7c0ed 100644 --- a/src/Avalonia.Themes.Fluent/ProgressBar.xaml +++ b/src/Avalonia.Themes.Fluent/ProgressBar.xaml @@ -1,81 +1,176 @@ - + + + + + + + + + - + + + + + + + + - + + + + diff --git a/src/Avalonia.Themes.Fluent/ToolTip.xaml b/src/Avalonia.Themes.Fluent/ToolTip.xaml index cf6f32f9bc..47ad494bbf 100644 --- a/src/Avalonia.Themes.Fluent/ToolTip.xaml +++ b/src/Avalonia.Themes.Fluent/ToolTip.xaml @@ -1,11 +1,14 @@ - + + @@ -19,7 +22,7 @@ @@ -34,6 +37,10 @@ + + 320 + +