From 8cfa7c175c4e9094d52448137db7731532a50c26 Mon Sep 17 00:00:00 2001 From: MarkusKgit <13160892+MarkusKgit@users.noreply.github.com> Date: Fri, 17 May 2019 15:58:36 +0200 Subject: [PATCH 001/130] Add blank cursor --- src/Avalonia.Input/Cursors.cs | 1 + src/Avalonia.X11/X11CursorFactory.cs | 22 ++++++++++++++++++++- src/Gtk/Avalonia.Gtk3/CursorFactory.cs | 7 ++++--- src/Gtk/Avalonia.Gtk3/GdkCursor.cs | 1 + src/Windows/Avalonia.Win32/CursorFactory.cs | 5 +++-- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Input/Cursors.cs b/src/Avalonia.Input/Cursors.cs index d3618f30f3..8139af1659 100644 --- a/src/Avalonia.Input/Cursors.cs +++ b/src/Avalonia.Input/Cursors.cs @@ -38,6 +38,7 @@ namespace Avalonia.Input DragMove, DragCopy, DragLink, + None, // Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/ // We might enable them later, preferably, by loading pixmax direclty from theme with fallback image diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 40b01117e3..c5566318a2 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -8,6 +8,8 @@ namespace Avalonia.X11 { class X11CursorFactory : IStandardCursorFactory { + private static IntPtr _nullCursor; + private readonly IntPtr _display; private Dictionary _cursors; @@ -42,16 +44,34 @@ namespace Avalonia.X11 public X11CursorFactory(IntPtr display) { _display = display; + _nullCursor = GetNullCursor(display); _cursors = Enum.GetValues(typeof(CursorFontShape)).Cast() .ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id)); } public IPlatformHandle GetCursor(StandardCursorType cursorType) { - var handle = s_mapping.TryGetValue(cursorType, out var shape) + IntPtr handle; + if (cursorType == StandardCursorType.None) + { + handle = _nullCursor; + } + else + { + handle = s_mapping.TryGetValue(cursorType, out var shape) ? _cursors[shape] : _cursors[CursorFontShape.XC_top_left_arrow]; + } return new PlatformHandle(handle, "XCURSOR"); } + + private static IntPtr GetNullCursor(IntPtr display) + { + XColor color = new XColor(); + byte[] data = new byte[] { 0 }; + IntPtr window = XLib.XRootWindow(display, 0); + IntPtr pixmap = XLib.XCreatePixmapFromBitmapData(display, window, data, 1, 1, IntPtr.Zero, IntPtr.Zero, 0); + return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0); + } } } diff --git a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs index a28b1cbb1a..f36e6b986d 100644 --- a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs +++ b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs @@ -12,7 +12,8 @@ namespace Avalonia.Gtk3 private static readonly Dictionary CursorTypeMapping = new Dictionary { - {StandardCursorType.AppStarting, CursorType.Watch}, + {StandardCursorType.None, CursorType.Blank}, + { StandardCursorType.AppStarting, CursorType.Watch}, {StandardCursorType.Arrow, CursorType.LeftPtr}, {StandardCursorType.Cross, CursorType.Cross}, {StandardCursorType.Hand, CursorType.Hand1}, @@ -36,7 +37,7 @@ namespace Avalonia.Gtk3 {StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner}, {StandardCursorType.DragCopy, CursorType.CenterPtr}, {StandardCursorType.DragMove, CursorType.Fleur}, - {StandardCursorType.DragLink, CursorType.Cross}, + {StandardCursorType.DragLink, CursorType.Cross}, }; private static readonly Dictionary Cache = @@ -80,4 +81,4 @@ namespace Avalonia.Gtk3 return rv; } } -} \ No newline at end of file +} diff --git a/src/Gtk/Avalonia.Gtk3/GdkCursor.cs b/src/Gtk/Avalonia.Gtk3/GdkCursor.cs index 4fad8208b3..aa0f8cde0d 100644 --- a/src/Gtk/Avalonia.Gtk3/GdkCursor.cs +++ b/src/Gtk/Avalonia.Gtk3/GdkCursor.cs @@ -2,6 +2,7 @@ { enum GdkCursorType { + Blank = -2, CursorIsPixmap = -1, XCursor = 0, Arrow = 2, diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index e582b5fb82..df413addef 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -41,7 +41,8 @@ namespace Avalonia.Win32 private static readonly Dictionary CursorTypeMapping = new Dictionary { - {StandardCursorType.AppStarting, 32650}, + {StandardCursorType.None, 0}, + { StandardCursorType.AppStarting, 32650}, {StandardCursorType.Arrow, 32512}, {StandardCursorType.Cross, 32515}, {StandardCursorType.Hand, 32649}, @@ -69,7 +70,7 @@ namespace Avalonia.Win32 // Fallback, should have been loaded from ole32.dll {StandardCursorType.DragMove, 32516}, {StandardCursorType.DragCopy, 32516}, - {StandardCursorType.DragLink, 32516}, + {StandardCursorType.DragLink, 32516}, }; private static readonly Dictionary Cache = From 4ad1acd24eeb836ff3b4fb06cb83011ed635d1e0 Mon Sep 17 00:00:00 2001 From: MarkusKgit <13160892+MarkusKgit@users.noreply.github.com> Date: Mon, 20 May 2019 12:05:49 +0200 Subject: [PATCH 002/130] Fix X11 NullCursor --- src/Avalonia.X11/X11CursorFactory.cs | 8 ++++---- src/Avalonia.X11/XLib.cs | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index c5566318a2..c020c44662 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -51,7 +51,7 @@ namespace Avalonia.X11 public IPlatformHandle GetCursor(StandardCursorType cursorType) { - IntPtr handle; + IntPtr handle; if (cursorType == StandardCursorType.None) { handle = _nullCursor; @@ -66,12 +66,12 @@ namespace Avalonia.X11 } private static IntPtr GetNullCursor(IntPtr display) - { + { XColor color = new XColor(); byte[] data = new byte[] { 0 }; IntPtr window = XLib.XRootWindow(display, 0); - IntPtr pixmap = XLib.XCreatePixmapFromBitmapData(display, window, data, 1, 1, IntPtr.Zero, IntPtr.Zero, 0); - return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0); + IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, data, 1, 1); + return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0); } } } diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 8a146f922d..3c41f7bdde 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -321,6 +321,9 @@ namespace Avalonia.X11 public static extern IntPtr XCreatePixmapCursor(IntPtr display, IntPtr source, IntPtr mask, ref XColor foreground_color, ref XColor background_color, int x_hot, int y_hot); + [DllImport(libX11)] + public static extern IntPtr XCreateBitmapFromData(IntPtr display, IntPtr drawable, byte[] data, int width, int height); + [DllImport(libX11)] public static extern IntPtr XCreatePixmapFromBitmapData(IntPtr display, IntPtr drawable, byte[] data, int width, int height, IntPtr fg, IntPtr bg, int depth); From 3860fafa000e9127f436745b998f6d8994cdb715 Mon Sep 17 00:00:00 2001 From: MarkusKgit <13160892+MarkusKgit@users.noreply.github.com> Date: Mon, 20 May 2019 12:10:49 +0200 Subject: [PATCH 003/130] Fix formatting --- src/Avalonia.X11/X11CursorFactory.cs | 10 +++++----- src/Gtk/Avalonia.Gtk3/CursorFactory.cs | 4 ++-- src/Windows/Avalonia.Win32/CursorFactory.cs | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index c020c44662..0a8b1ee9c4 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -9,7 +9,7 @@ namespace Avalonia.X11 class X11CursorFactory : IStandardCursorFactory { private static IntPtr _nullCursor; - + private readonly IntPtr _display; private Dictionary _cursors; @@ -48,10 +48,10 @@ namespace Avalonia.X11 _cursors = Enum.GetValues(typeof(CursorFontShape)).Cast() .ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id)); } - + public IPlatformHandle GetCursor(StandardCursorType cursorType) { - IntPtr handle; + IntPtr handle; if (cursorType == StandardCursorType.None) { handle = _nullCursor; @@ -61,7 +61,7 @@ namespace Avalonia.X11 handle = s_mapping.TryGetValue(cursorType, out var shape) ? _cursors[shape] : _cursors[CursorFontShape.XC_top_left_arrow]; - } + } return new PlatformHandle(handle, "XCURSOR"); } @@ -71,7 +71,7 @@ namespace Avalonia.X11 byte[] data = new byte[] { 0 }; IntPtr window = XLib.XRootWindow(display, 0); IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, data, 1, 1); - return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0); + return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0); } } } diff --git a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs index f36e6b986d..95fa3ba9e3 100644 --- a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs +++ b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs @@ -13,7 +13,7 @@ namespace Avalonia.Gtk3 { {StandardCursorType.None, CursorType.Blank}, - { StandardCursorType.AppStarting, CursorType.Watch}, + {StandardCursorType.AppStarting, CursorType.Watch}, {StandardCursorType.Arrow, CursorType.LeftPtr}, {StandardCursorType.Cross, CursorType.Cross}, {StandardCursorType.Hand, CursorType.Hand1}, @@ -37,7 +37,7 @@ namespace Avalonia.Gtk3 {StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner}, {StandardCursorType.DragCopy, CursorType.CenterPtr}, {StandardCursorType.DragMove, CursorType.Fleur}, - {StandardCursorType.DragLink, CursorType.Cross}, + {StandardCursorType.DragLink, CursorType.Cross}, }; private static readonly Dictionary Cache = diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index df413addef..f1fd74f931 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -42,7 +42,7 @@ namespace Avalonia.Win32 { {StandardCursorType.None, 0}, - { StandardCursorType.AppStarting, 32650}, + {StandardCursorType.AppStarting, 32650}, {StandardCursorType.Arrow, 32512}, {StandardCursorType.Cross, 32515}, {StandardCursorType.Hand, 32649}, @@ -70,7 +70,7 @@ namespace Avalonia.Win32 // Fallback, should have been loaded from ole32.dll {StandardCursorType.DragMove, 32516}, {StandardCursorType.DragCopy, 32516}, - {StandardCursorType.DragLink, 32516}, + {StandardCursorType.DragLink, 32516}, }; private static readonly Dictionary Cache = From c3f142af2e75c83c57262e9ab27efa7c25e02043 Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 22 May 2019 18:35:47 +0300 Subject: [PATCH 004/130] Add ViewModelViewHost and TransitioningUserControl --- src/Avalonia.ReactiveUI/RoutedViewHost.cs | 143 +++--------------- .../TransitioningUserControl.cs | 127 ++++++++++++++++ src/Avalonia.ReactiveUI/ViewModelViewHost.cs | 75 +++++++++ 3 files changed, 219 insertions(+), 126 deletions(-) create mode 100644 src/Avalonia.ReactiveUI/TransitioningUserControl.cs create mode 100644 src/Avalonia.ReactiveUI/ViewModelViewHost.cs diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 4bd86a67c0..bf295aafca 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -53,33 +53,13 @@ namespace Avalonia.ReactiveUI /// ReactiveUI routing documentation website for more info. /// /// - public class RoutedViewHost : UserControl, IActivatable, IEnableLogger + public class RoutedViewHost : TransitioningUserControl, IActivatable, IEnableLogger { /// - /// The router dependency property. + /// for the property. /// public static readonly AvaloniaProperty RouterProperty = AvaloniaProperty.Register(nameof(Router)); - - /// - /// The default content property. - /// - public static readonly AvaloniaProperty DefaultContentProperty = - AvaloniaProperty.Register(nameof(DefaultContent)); - - /// - /// Fade in animation property. - /// - public static readonly AvaloniaProperty FadeInAnimationProperty = - AvaloniaProperty.Register(nameof(DefaultContent), - CreateOpacityAnimation(0d, 1d, TimeSpan.FromSeconds(0.25))); - - /// - /// Fade out animation property. - /// - public static readonly AvaloniaProperty FadeOutAnimationProperty = - AvaloniaProperty.Register(nameof(DefaultContent), - CreateOpacityAnimation(1d, 0d, TimeSpan.FromSeconds(0.25))); /// /// Initializes a new instance of the class. @@ -104,42 +84,6 @@ namespace Avalonia.ReactiveUI set => SetValue(RouterProperty, value); } - /// - /// Gets or sets the content displayed whenever there is no page currently routed. - /// - public object DefaultContent - { - get => GetValue(DefaultContentProperty); - set => SetValue(DefaultContentProperty, value); - } - - /// - /// Gets or sets the animation played when page appears. - /// - public IAnimation FadeInAnimation - { - get => GetValue(FadeInAnimationProperty); - set => SetValue(FadeInAnimationProperty, value); - } - - /// - /// Gets or sets the animation played when page disappears. - /// - public IAnimation FadeOutAnimation - { - get => GetValue(FadeOutAnimationProperty); - set => SetValue(FadeOutAnimationProperty, value); - } - - /// - /// Duplicates the Content property with a private setter. - /// - public new object Content - { - get => base.Content; - private set => base.Content = value; - } - /// /// Gets or sets the ReactiveUI view locator used by this router. /// @@ -149,82 +93,29 @@ namespace Avalonia.ReactiveUI /// Invoked when ReactiveUI router navigates to a view model. /// /// ViewModel to which the user navigates. - /// - /// Thrown when ViewLocator is unable to find the appropriate view. - /// - private void NavigateToViewModel(IRoutableViewModel viewModel) + private void NavigateToViewModel(object viewModel) { if (viewModel == null) { - this.Log().Info("ViewModel is null, falling back to default content."); - UpdateContent(DefaultContent); + this.Log().Info("ViewModel is null. Falling back to default content."); + Content = DefaultContent; return; } var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current; - var view = viewLocator.ResolveView(viewModel); - if (view == null) throw new Exception($"Couldn't find view for '{viewModel}'. Is it registered?"); + var viewInstance = viewLocator.ResolveView(viewModel); + if (viewInstance == null) + { + this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + Content = DefaultContent; + return; + } - this.Log().Info($"Ready to show {view} with autowired {viewModel}."); - view.ViewModel = viewModel; - if (view is IStyledElement styled) + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + viewInstance.ViewModel = viewModel; + if (viewInstance is IStyledElement styled) styled.DataContext = viewModel; - UpdateContent(view); - } - - /// - /// Updates the content with transitions. - /// - /// New content to set. - private async void UpdateContent(object newContent) - { - if (FadeOutAnimation != null) - await FadeOutAnimation.RunAsync(this, Clock); - Content = newContent; - if (FadeInAnimation != null) - await FadeInAnimation.RunAsync(this, Clock); - } - - /// - /// Creates opacity animation for this routed view host. - /// - /// Opacity to start from. - /// Opacity to finish with. - /// Duration of the animation. - /// Animation object instance. - private static IAnimation CreateOpacityAnimation(double from, double to, TimeSpan duration) - { - return new Avalonia.Animation.Animation - { - Duration = duration, - Children = - { - new KeyFrame - { - Setters = - { - new Setter - { - Property = OpacityProperty, - Value = from - } - }, - Cue = new Cue(0d) - }, - new KeyFrame - { - Setters = - { - new Setter - { - Property = OpacityProperty, - Value = to - } - }, - Cue = new Cue(1d) - } - } - }; + Content = viewInstance; } } -} +} \ No newline at end of file diff --git a/src/Avalonia.ReactiveUI/TransitioningUserControl.cs b/src/Avalonia.ReactiveUI/TransitioningUserControl.cs new file mode 100644 index 0000000000..fb5258f2e4 --- /dev/null +++ b/src/Avalonia.ReactiveUI/TransitioningUserControl.cs @@ -0,0 +1,127 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Styling; + +namespace Avalonia.ReactiveUI +{ + /// + /// A ContentControl that animates the transition when its content is changed. + /// + public class TransitioningUserControl : UserControl + { + /// + /// for the property. + /// + public static readonly AvaloniaProperty FadeInAnimationProperty = + AvaloniaProperty.Register(nameof(DefaultContent), + CreateOpacityAnimation(0d, 1d, TimeSpan.FromSeconds(0.25))); + + /// + /// for the property. + /// + public static readonly AvaloniaProperty FadeOutAnimationProperty = + AvaloniaProperty.Register(nameof(DefaultContent), + CreateOpacityAnimation(1d, 0d, TimeSpan.FromSeconds(0.25))); + + /// + /// for the property. + /// + public static readonly AvaloniaProperty DefaultContentProperty = + AvaloniaProperty.Register(nameof(DefaultContent)); + + /// + /// Gets or sets the animation played when content appears. + /// + public IAnimation FadeInAnimation + { + get => GetValue(FadeInAnimationProperty); + set => SetValue(FadeInAnimationProperty, value); + } + + /// + /// Gets or sets the animation played when content disappears. + /// + public IAnimation FadeOutAnimation + { + get => GetValue(FadeOutAnimationProperty); + set => SetValue(FadeOutAnimationProperty, value); + } + + /// + /// Gets or sets the content displayed whenever there is no page currently routed. + /// + public object DefaultContent + { + get => GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } + + /// + /// Gets or sets the content with animation. + /// + public new object Content + { + get => base.Content; + set => UpdateContentWithTransition(value); + } + + /// + /// Updates the content with transitions. + /// + /// New content to set. + private async void UpdateContentWithTransition(object content) + { + if (FadeOutAnimation != null) + await FadeOutAnimation.RunAsync(this, Clock); + base.Content = content; + if (FadeInAnimation != null) + await FadeInAnimation.RunAsync(this, Clock); + } + + /// + /// Creates opacity animation for this routed view host. + /// + /// Opacity to start from. + /// Opacity to finish with. + /// Duration of the animation. + /// Animation object instance. + private static IAnimation CreateOpacityAnimation(double from, double to, TimeSpan duration) + { + return new Avalonia.Animation.Animation + { + Duration = duration, + Children = + { + new KeyFrame + { + Setters = + { + new Setter + { + Property = OpacityProperty, + Value = from + } + }, + Cue = new Cue(0d) + }, + new KeyFrame + { + Setters = + { + new Setter + { + Property = OpacityProperty, + Value = to + } + }, + Cue = new Cue(1d) + } + } + }; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs new file mode 100644 index 0000000000..84108a9b52 --- /dev/null +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -0,0 +1,75 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using ReactiveUI; +using Splat; + +namespace Avalonia.ReactiveUI +{ + /// + /// This content control will automatically load the View associated with + /// the ViewModel property and display it. This control is very useful + /// inside a DataTemplate to display the View associated with a ViewModel. + /// + public class ViewModelViewHost : TransitioningUserControl, IViewFor, IEnableLogger + { + /// + /// for the property. + /// + public static readonly AvaloniaProperty ViewModelProperty = + AvaloniaProperty.Register(nameof(ViewModel)); + + /// + /// Gets or sets the ViewModel to display. + /// + public object ViewModel + { + get => GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + /// + /// Gets or sets the view locator. + /// + public IViewLocator ViewLocator { get; set; } + + /// + /// Updates the Content when ViewModel changes. + /// + /// Property changed event arguments. + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.Property.Name == nameof(ViewModel)) NavigateToViewModel(e.NewValue); + base.OnPropertyChanged(e); + } + + /// + /// Invoked when ReactiveUI router navigates to a view model. + /// + /// ViewModel to which the user navigates. + private void NavigateToViewModel(object viewModel) + { + if (viewModel == null) + { + this.Log().Info("ViewModel is null. Falling back to default content."); + Content = DefaultContent; + return; + } + + var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current; + var viewInstance = viewLocator.ResolveView(viewModel); + if (viewInstance == null) + { + this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + Content = DefaultContent; + return; + } + + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + viewInstance.ViewModel = viewModel; + if (viewInstance is IStyledElement styled) + styled.DataContext = viewModel; + Content = viewInstance; + } + } +} \ No newline at end of file From 97b44c02d25d07c0e2adfecb842e016c54d2e434 Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 22 May 2019 19:09:51 +0300 Subject: [PATCH 005/130] Add a unit test for ViewModelViewHost --- src/Avalonia.ReactiveUI/ViewModelViewHost.cs | 27 ++++--- .../Attributes.cs | 6 ++ .../AvaloniaActivationForViewFetcherTest.cs | 2 +- .../RoutedViewHostTest.cs | 2 +- .../ViewModelViewHostTest.cs | 72 +++++++++++++++++++ 5 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs create mode 100644 tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs index 84108a9b52..6f80bff4b8 100644 --- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using System.Reactive.Disposables; using ReactiveUI; using Splat; @@ -18,7 +20,20 @@ namespace Avalonia.ReactiveUI /// public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty.Register(nameof(ViewModel)); - + + /// + /// Initializes a new instance of the class. + /// + public ViewModelViewHost() + { + this.WhenActivated(disposables => + { + this.WhenAnyValue(x => x.ViewModel) + .Subscribe(NavigateToViewModel) + .DisposeWith(disposables); + }); + } + /// /// Gets or sets the ViewModel to display. /// @@ -33,16 +48,6 @@ namespace Avalonia.ReactiveUI /// public IViewLocator ViewLocator { get; set; } - /// - /// Updates the Content when ViewModel changes. - /// - /// Property changed event arguments. - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) - { - if (e.Property.Name == nameof(ViewModel)) NavigateToViewModel(e.NewValue); - base.OnPropertyChanged(e); - } - /// /// Invoked when ReactiveUI router navigates to a view model. /// diff --git a/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs b/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs new file mode 100644 index 0000000000..79e58f63e9 --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs @@ -0,0 +1,6 @@ +using Xunit; + +// Required to avoid InvalidOperationException sometimes thrown +// from Splat.MemoizingMRUCache.cs which is not thread-safe. +// Thrown when trying to access WhenActivated concurrently. +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs index d9f1ce47dd..f4dffdc0c3 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs @@ -13,7 +13,7 @@ using Splat; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -namespace Avalonia +namespace Avalonia.ReactiveUI.UnitTests { public class AvaloniaActivationForViewFetcherTest { diff --git a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs index 401d169896..c6017d3f5f 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs @@ -16,7 +16,7 @@ using System.Threading.Tasks; using System.Reactive; using Avalonia.ReactiveUI; -namespace Avalonia +namespace Avalonia.ReactiveUI.UnitTests { public class RoutedViewHostTest { diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs new file mode 100644 index 0000000000..5d5d15358e --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs @@ -0,0 +1,72 @@ +using Avalonia.Controls; +using Avalonia.UnitTests; +using ReactiveUI; +using Splat; +using Xunit; + +namespace Avalonia.ReactiveUI.UnitTests +{ + public class ViewModelViewHostTest + { + public class FirstViewModel { } + + public class FirstView : ReactiveUserControl { } + + public class SecondViewModel : ReactiveObject { } + + public class SecondView : ReactiveUserControl { } + + public ViewModelViewHostTest() + { + Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); + Locator.CurrentMutable.Register(() => new FirstView(), typeof(IViewFor)); + Locator.CurrentMutable.Register(() => new SecondView(), typeof(IViewFor)); + } + + [Fact] + public void ViewModelViewHost_View_Should_Stay_In_Sync_With_ViewModel() + { + var defaultContent = new TextBlock(); + var host = new ViewModelViewHost + { + DefaultContent = defaultContent, + FadeOutAnimation = null, + FadeInAnimation = null + }; + + var root = new TestRoot + { + Child = host + }; + + Assert.NotNull(host.Content); + Assert.Equal(typeof(TextBlock), host.Content.GetType()); + Assert.Equal(defaultContent, host.Content); + + var first = new FirstViewModel(); + host.ViewModel = first; + Assert.NotNull(host.Content); + Assert.Equal(typeof(FirstView), host.Content.GetType()); + Assert.Equal(first, ((FirstView)host.Content).DataContext); + Assert.Equal(first, ((FirstView)host.Content).ViewModel); + + var second = new SecondViewModel(); + host.ViewModel = second; + Assert.NotNull(host.Content); + Assert.Equal(typeof(SecondView), host.Content.GetType()); + Assert.Equal(second, ((SecondView)host.Content).DataContext); + Assert.Equal(second, ((SecondView)host.Content).ViewModel); + + host.ViewModel = null; + Assert.NotNull(host.Content); + Assert.Equal(typeof(TextBlock), host.Content.GetType()); + Assert.Equal(defaultContent, host.Content); + + host.ViewModel = first; + Assert.NotNull(host.Content); + Assert.Equal(typeof(FirstView), host.Content.GetType()); + Assert.Equal(first, ((FirstView)host.Content).DataContext); + Assert.Equal(first, ((FirstView)host.Content).ViewModel); + } + } +} \ No newline at end of file From 724a5da8963ea83d616fce8691a9ded211ccb4ef Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 22 May 2019 19:10:40 +0300 Subject: [PATCH 006/130] Add license headers --- tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs | 3 +++ .../AvaloniaActivationForViewFetcherTest.cs | 3 +++ tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs | 3 +++ tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs b/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs index 79e58f63e9..a79647d355 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs @@ -1,3 +1,6 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using Xunit; // Required to avoid InvalidOperationException sometimes thrown diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs index f4dffdc0c3..2c81e8fea3 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs @@ -1,3 +1,6 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using System; using System.Reactive.Concurrency; using System.Reactive.Disposables; diff --git a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs index c6017d3f5f..e873c60e36 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs @@ -1,3 +1,6 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using System; using System.Reactive.Concurrency; using System.Reactive.Disposables; diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs index 5d5d15358e..a21bf34ef5 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs @@ -1,3 +1,6 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using Avalonia.Controls; using Avalonia.UnitTests; using ReactiveUI; From e8c0012c318522529f0e572a7535ab60d628d96e Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 22 May 2019 19:26:29 +0300 Subject: [PATCH 007/130] Unit tests for ReactiveWindow and ReactiveUserControl --- .../ReactiveUserControlTest.cs | 34 +++++++++++++++++ .../ReactiveWindowTest.cs | 37 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs create mode 100644 tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs new file mode 100644 index 0000000000..328b749ba1 --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs @@ -0,0 +1,34 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Controls; +using Avalonia.UnitTests; +using ReactiveUI; +using Splat; +using Xunit; + +namespace Avalonia.ReactiveUI.UnitTests +{ + public class ReactiveUserControlTest + { + public class ExampleViewModel : ReactiveObject { } + + public class ExampleView : ReactiveUserControl { } + + [Fact] + public void Data_Context_Should_Stay_In_Sync_With_Reactive_User_Control_View_Model() + { + var view = new ExampleView(); + var viewModel = new ExampleViewModel(); + Assert.Null(view.ViewModel); + + view.DataContext = viewModel; + Assert.Equal(view.ViewModel, viewModel); + Assert.Equal(view.DataContext, viewModel); + + view.DataContext = null; + Assert.Null(view.ViewModel); + Assert.Null(view.DataContext); + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs new file mode 100644 index 0000000000..ff77de8d28 --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs @@ -0,0 +1,37 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Controls; +using Avalonia.UnitTests; +using ReactiveUI; +using Splat; +using Xunit; + +namespace Avalonia.ReactiveUI.UnitTests +{ + public class ReactiveWindowTest + { + public class ExampleViewModel : ReactiveObject { } + + public class ExampleWindow : ReactiveWindow { } + + [Fact] + public void Data_Context_Should_Stay_In_Sync_With_Reactive_Window_View_Model() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var view = new ExampleWindow(); + var viewModel = new ExampleViewModel(); + Assert.Null(view.ViewModel); + + view.DataContext = viewModel; + Assert.Equal(view.ViewModel, viewModel); + Assert.Equal(view.DataContext, viewModel); + + view.DataContext = null; + Assert.Null(view.ViewModel); + Assert.Null(view.DataContext); + } + } + } +} \ No newline at end of file From fe7e7054fb7012b8f901de4d7abc8664bc9f40e9 Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 22 May 2019 20:40:26 +0300 Subject: [PATCH 008/130] Add initial AutoDataTemplateBindingHook implementation --- .../AutoDataTemplateBindingHook.cs | 60 +++++++++++++++++++ .../AutoDataTemplateBindingHookTest.cs | 56 +++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs create mode 100644 tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs diff --git a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs new file mode 100644 index 0000000000..bd156a5884 --- /dev/null +++ b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml.Templates; +using ReactiveUI; + +namespace Avalonia.ReactiveUI +{ + /// + /// AutoDataTemplateBindingHook is a binding hook that checks ItemsControls + /// that don't have DataTemplates, and assigns a default DataTemplate that + /// loads the View associated with each ViewModel. + /// + public class AutoDataTemplateBindingHook : IPropertyBindingHook + { + private static Lazy DefaultItemTemplate { get; } = new Lazy(() => + { + var template = @" + + +"; + + var loader = new AvaloniaXamlLoader(); + return (DataTemplate)loader.Load(template); + }); + + /// + public bool ExecuteHook( + object source, object target, + Func[]> getCurrentViewModelProperties, + Func[]> getCurrentViewProperties, + BindingDirection direction) + { + var viewProperties = getCurrentViewProperties(); + var lastViewProperty = viewProperties.LastOrDefault(); + if (lastViewProperty == null) + return true; + + var itemsControl = lastViewProperty.Sender as ItemsControl; + if (itemsControl == null) + return true; + + var propertyName = viewProperties.Last().GetPropertyName(); + if (propertyName != "Items" && + propertyName != "ItemsSource") + return true; + + if (itemsControl.ItemTemplate != null) + return true; + + itemsControl.ItemTemplate = DefaultItemTemplate.Value; + return true; + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs new file mode 100644 index 0000000000..2391daece2 --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs @@ -0,0 +1,56 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Xunit; +using ReactiveUI; +using Avalonia.ReactiveUI; +using Avalonia.UnitTests; +using Avalonia.Controls; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Splat; + +namespace Avalonia.ReactiveUI.UnitTests +{ + public class AutoDataTemplateBindingHookTest + { + public class NestedViewModel : ReactiveObject { } + + public class NestedView : ReactiveUserControl { } + + public class ExampleViewModel : ReactiveObject + { + public ObservableCollection Items { get; } = new ObservableCollection(); + } + + public class ExampleView : ReactiveUserControl + { + public ListBox List { get; } = new ListBox(); + + public ExampleView() + { + Content = List; + ViewModel = new ExampleViewModel(); + this.OneWayBind(ViewModel, x => x.Items, x => x.List.Items); + } + } + + public AutoDataTemplateBindingHookTest() + { + Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); + Locator.CurrentMutable.Register(() => new ExampleView(), typeof(IViewFor)); + } + + [Fact] + public void Should_Apply_Data_Template_Binding_When_No_Template_Is_Set() + { + var view = new ExampleView(); + var root = new TestRoot + { + Child = view + }; + Assert.NotNull(view.List.ItemTemplate); + } + } +} \ No newline at end of file From b486112d296354f0d9d46f4fea72ea8bdb5c2bef Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 22 May 2019 22:06:31 +0300 Subject: [PATCH 009/130] Use null propagation --- src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs index bd156a5884..bb660fd76a 100644 --- a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs +++ b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs @@ -38,10 +38,7 @@ namespace Avalonia.ReactiveUI { var viewProperties = getCurrentViewProperties(); var lastViewProperty = viewProperties.LastOrDefault(); - if (lastViewProperty == null) - return true; - - var itemsControl = lastViewProperty.Sender as ItemsControl; + var itemsControl = lastViewProperty?.Sender as ItemsControl; if (itemsControl == null) return true; From 72fe95272a09644f72fd34c7d14acf42ef576819 Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 22 May 2019 22:08:52 +0300 Subject: [PATCH 010/130] Register auto data template binding hook --- src/Avalonia.ReactiveUI/AppBuilderExtensions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs index f67cb7f40a..ced26a3004 100644 --- a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs +++ b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs @@ -21,9 +21,8 @@ namespace Avalonia.ReactiveUI return builder.AfterSetup(_ => { RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; - Locator.CurrentMutable.Register( - () => new AvaloniaActivationForViewFetcher(), - typeof(IActivationForViewFetcher)); + Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); + Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); }); } } From d2574b383a1880bdd3aebfc94b9ee26ce653629d Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 22 May 2019 22:47:18 +0300 Subject: [PATCH 011/130] Use FuncDataTemplate --- .../AutoDataTemplateBindingHook.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs index bb660fd76a..d7cfebf1d8 100644 --- a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs +++ b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Avalonia.Layout; using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.Templates; using ReactiveUI; @@ -14,19 +16,18 @@ namespace Avalonia.ReactiveUI /// public class AutoDataTemplateBindingHook : IPropertyBindingHook { - private static Lazy DefaultItemTemplate { get; } = new Lazy(() => + private static Lazy DefaultItemTemplate { get; } = new Lazy(() => { - var template = @" - - -"; - - var loader = new AvaloniaXamlLoader(); - return (DataTemplate)loader.Load(template); + return new FuncDataTemplate(x => + { + var control = new ViewModelViewHost(); + var context = control.GetObservable(Control.DataContextProperty); + control.Bind(ViewModelViewHost.ViewModelProperty, context); + control.HorizontalContentAlignment = HorizontalAlignment.Stretch; + control.VerticalContentAlignment = VerticalAlignment.Stretch; + return control; + }, + true); }); /// From 65f5f58a07f29c5baf4fe80218cc2a83deba5dc6 Mon Sep 17 00:00:00 2001 From: artyom Date: Thu, 23 May 2019 00:36:53 +0300 Subject: [PATCH 012/130] ViewModelViewHost type resolution assertion --- .../AutoDataTemplateBindingHookTest.cs | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs index 2391daece2..4ff3e1fed6 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs @@ -6,9 +6,12 @@ using ReactiveUI; using Avalonia.ReactiveUI; using Avalonia.UnitTests; using Avalonia.Controls; +using Avalonia.Controls.Templates; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Avalonia.VisualTree; +using Avalonia.Controls.Presenters; using Splat; namespace Avalonia.ReactiveUI.UnitTests @@ -26,7 +29,7 @@ namespace Avalonia.ReactiveUI.UnitTests public class ExampleView : ReactiveUserControl { - public ListBox List { get; } = new ListBox(); + public ItemsControl List { get; } = new ItemsControl(); public ExampleView() { @@ -40,17 +43,48 @@ namespace Avalonia.ReactiveUI.UnitTests { Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); Locator.CurrentMutable.Register(() => new ExampleView(), typeof(IViewFor)); + Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); } [Fact] public void Should_Apply_Data_Template_Binding_When_No_Template_Is_Set() { var view = new ExampleView(); - var root = new TestRoot - { - Child = view - }; Assert.NotNull(view.List.ItemTemplate); } + + [Fact] + public void Should_Use_View_Model_View_Host_As_Data_Template() + { + var view = new ExampleView(); + view.ViewModel.Items.Add(new NestedViewModel()); + + view.List.Template = GetTemplate(); + view.List.ApplyTemplate(); + view.List.Presenter.ApplyTemplate(); + + var child = view.List.Presenter.Panel.Children[0]; + var container = (ContentPresenter) child; + container.UpdateChild(); + + Assert.IsType(container.Child); + } + + private FuncControlTemplate GetTemplate() + { + return new FuncControlTemplate(parent => + { + return new Border + { + Background = new Media.SolidColorBrush(0xffffffff), + Child = new ItemsPresenter + { + Name = "PART_ItemsPresenter", + MemberSelector = parent.MemberSelector, + [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + } + }; + }); + } } } \ No newline at end of file From 8a2d1662fdcd92057d39aa39a2e8296e134ab0bd Mon Sep 17 00:00:00 2001 From: artyom Date: Thu, 23 May 2019 01:20:36 +0300 Subject: [PATCH 013/130] Remove LazyT, test new template resolver --- .../AutoDataTemplateBindingHook.cs | 23 +++++++-------- .../AutoDataTemplateBindingHookTest.cs | 28 ++++++++++++++++++- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs index d7cfebf1d8..3f41f54363 100644 --- a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs +++ b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs @@ -16,19 +16,16 @@ namespace Avalonia.ReactiveUI /// public class AutoDataTemplateBindingHook : IPropertyBindingHook { - private static Lazy DefaultItemTemplate { get; } = new Lazy(() => + private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate(x => { - return new FuncDataTemplate(x => - { - var control = new ViewModelViewHost(); - var context = control.GetObservable(Control.DataContextProperty); - control.Bind(ViewModelViewHost.ViewModelProperty, context); - control.HorizontalContentAlignment = HorizontalAlignment.Stretch; - control.VerticalContentAlignment = VerticalAlignment.Stretch; - return control; - }, - true); - }); + var control = new ViewModelViewHost(); + var context = control.GetObservable(Control.DataContextProperty); + control.Bind(ViewModelViewHost.ViewModelProperty, context); + control.HorizontalContentAlignment = HorizontalAlignment.Stretch; + control.VerticalContentAlignment = VerticalAlignment.Stretch; + return control; + }, + true); /// public bool ExecuteHook( @@ -51,7 +48,7 @@ namespace Avalonia.ReactiveUI if (itemsControl.ItemTemplate != null) return true; - itemsControl.ItemTemplate = DefaultItemTemplate.Value; + itemsControl.ItemTemplate = DefaultItemTemplate; return true; } } diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs index 4ff3e1fed6..667462eb91 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs @@ -13,6 +13,8 @@ using System.Linq; using Avalonia.VisualTree; using Avalonia.Controls.Presenters; using Splat; +using System.Threading.Tasks; +using System; namespace Avalonia.ReactiveUI.UnitTests { @@ -42,8 +44,8 @@ namespace Avalonia.ReactiveUI.UnitTests public AutoDataTemplateBindingHookTest() { Locator.CurrentMutable.RegisterConstant(new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); - Locator.CurrentMutable.Register(() => new ExampleView(), typeof(IViewFor)); Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); + Locator.CurrentMutable.Register(() => new NestedView(), typeof(IViewFor)); } [Fact] @@ -70,6 +72,30 @@ namespace Avalonia.ReactiveUI.UnitTests Assert.IsType(container.Child); } + [Fact] + public void Should_Resolve_And_Embedd_Appropriate_View_Model() + { + var view = new ExampleView(); + var root = new TestRoot { Child = view }; + view.ViewModel.Items.Add(new NestedViewModel()); + + view.List.Template = GetTemplate(); + view.List.ApplyTemplate(); + view.List.Presenter.ApplyTemplate(); + + var child = view.List.Presenter.Panel.Children[0]; + var container = (ContentPresenter) child; + container.UpdateChild(); + + var host = (ViewModelViewHost) container.Child; + Assert.IsType(host.ViewModel); + Assert.IsType(host.DataContext); + + host.DataContext = "changed context"; + Assert.IsType(host.ViewModel); + Assert.IsType(host.DataContext); + } + private FuncControlTemplate GetTemplate() { return new FuncControlTemplate(parent => From c4fccd5dd7b606a4b5ae1bf35a3bb70027794320 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 23 May 2019 12:34:38 +0800 Subject: [PATCH 014/130] Import Grid code from WPF.Core --- src/Avalonia.Controls/GridWPF.cs | 4420 ++++++++++++++++++++++++++++++ 1 file changed, 4420 insertions(+) create mode 100644 src/Avalonia.Controls/GridWPF.cs diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs new file mode 100644 index 0000000000..3a4b41314f --- /dev/null +++ b/src/Avalonia.Controls/GridWPF.cs @@ -0,0 +1,4420 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reactive.Linq; +using System.Runtime.CompilerServices; +using Avalonia.Collections; +using Avalonia.Controls.Utils; +using Avalonia.VisualTree; +using JetBrains.Annotations; + + +using MS.Internal; +using MS.Internal.Controls; +using MS.Internal.PresentationFramework; +using MS.Internal.Telemetry.PresentationFramework; +using MS.Utility; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Threading; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Markup; + +#pragma warning disable 1634, 1691 // suppressing PreSharp warnings + +namespace System.Windows.Controls +{ + /// + /// Grid + /// + public class Grid : Panel, IAddChild + { + //------------------------------------------------------ + // + // Constructors + // + //------------------------------------------------------ + + #region Constructors + + static Grid() + { + ControlsTraceLogger.AddControl(TelemetryControls.Grid); + } + + /// + /// Default constructor. + /// + public Grid() + { + SetFlags((bool) ShowGridLinesProperty.GetDefaultValue(DependencyObjectType), Flags.ShowGridLinesPropertyValue); + } + + #endregion Constructors + + //------------------------------------------------------ + // + // Public Methods + // + //------------------------------------------------------ + + #region Public Methods + + /// + /// + /// + void IAddChild.AddChild(object value) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + UIElement cell = value as UIElement; + if (cell != null) + { + Children.Add(cell); + return; + } + + throw (new ArgumentException(SR.Get(SRID.Grid_UnexpectedParameterType, value.GetType(), typeof(UIElement)), "value")); + } + + /// + /// + /// + void IAddChild.AddText(string text) + { + XamlSerializerUtil.ThrowIfNonWhiteSpaceInAddText(text, this); + } + + /// + /// + /// + protected internal override IEnumerator LogicalChildren + { + get + { + // empty panel or a panel being used as the items + // host has *no* logical children; give empty enumerator + bool noChildren = (base.VisualChildrenCount == 0) || IsItemsHost; + + if (noChildren) + { + ExtendedData extData = ExtData; + + if ( extData == null + || ( (extData.ColumnDefinitions == null || extData.ColumnDefinitions.Count == 0) + && (extData.RowDefinitions == null || extData.RowDefinitions.Count == 0) ) + ) + { + // grid is empty + return EmptyEnumerator.Instance; + } + } + + return (new GridChildrenCollectionEnumeratorSimple(this, !noChildren)); + } + } + + /// + /// Helper for setting Column property on a UIElement. + /// + /// UIElement to set Column property on. + /// Column property value. + public static void SetColumn(UIElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(ColumnProperty, value); + } + + /// + /// Helper for reading Column property from a UIElement. + /// + /// UIElement to read Column property from. + /// Column property value. + [AttachedPropertyBrowsableForChildren()] + public static int GetColumn(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((int)element.GetValue(ColumnProperty)); + } + + /// + /// Helper for setting Row property on a UIElement. + /// + /// UIElement to set Row property on. + /// Row property value. + public static void SetRow(UIElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(RowProperty, value); + } + + /// + /// Helper for reading Row property from a UIElement. + /// + /// UIElement to read Row property from. + /// Row property value. + [AttachedPropertyBrowsableForChildren()] + public static int GetRow(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((int)element.GetValue(RowProperty)); + } + + /// + /// Helper for setting ColumnSpan property on a UIElement. + /// + /// UIElement to set ColumnSpan property on. + /// ColumnSpan property value. + public static void SetColumnSpan(UIElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(ColumnSpanProperty, value); + } + + /// + /// Helper for reading ColumnSpan property from a UIElement. + /// + /// UIElement to read ColumnSpan property from. + /// ColumnSpan property value. + [AttachedPropertyBrowsableForChildren()] + public static int GetColumnSpan(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((int)element.GetValue(ColumnSpanProperty)); + } + + /// + /// Helper for setting RowSpan property on a UIElement. + /// + /// UIElement to set RowSpan property on. + /// RowSpan property value. + public static void SetRowSpan(UIElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(RowSpanProperty, value); + } + + /// + /// Helper for reading RowSpan property from a UIElement. + /// + /// UIElement to read RowSpan property from. + /// RowSpan property value. + [AttachedPropertyBrowsableForChildren()] + public static int GetRowSpan(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((int)element.GetValue(RowSpanProperty)); + } + + /// + /// Helper for setting IsSharedSizeScope property on a UIElement. + /// + /// UIElement to set IsSharedSizeScope property on. + /// IsSharedSizeScope property value. + public static void SetIsSharedSizeScope(UIElement element, bool value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(IsSharedSizeScopeProperty, value); + } + + /// + /// Helper for reading IsSharedSizeScope property from a UIElement. + /// + /// UIElement to read IsSharedSizeScope property from. + /// IsSharedSizeScope property value. + public static bool GetIsSharedSizeScope(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((bool)element.GetValue(IsSharedSizeScopeProperty)); + } + + #endregion Public Methods + + //------------------------------------------------------ + // + // Public Properties + // + //------------------------------------------------------ + + #region Public Properties + + /// + /// ShowGridLines property. + /// + public bool ShowGridLines + { + get { return (CheckFlagsAnd(Flags.ShowGridLinesPropertyValue)); } + set { SetValue(ShowGridLinesProperty, value); } + } + + /// + /// Returns a ColumnDefinitionCollection of column definitions. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public ColumnDefinitionCollection ColumnDefinitions + { + get + { + if (_data == null) { _data = new ExtendedData(); } + if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitionCollection(this); } + + return (_data.ColumnDefinitions); + } + } + + /// + /// Returns a RowDefinitionCollection of row definitions. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public RowDefinitionCollection RowDefinitions + { + get + { + if (_data == null) { _data = new ExtendedData(); } + if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitionCollection(this); } + + return (_data.RowDefinitions); + } + } + + #endregion Public Properties + + //------------------------------------------------------ + // + // Protected Methods + // + //------------------------------------------------------ + + #region Protected Methods + + /// + /// Derived class must implement to support Visual children. The method must return + /// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1. + /// + /// By default a Visual does not have any children. + /// + /// Remark: + /// During this virtual call it is not valid to modify the Visual tree. + /// + protected override Visual GetVisualChild(int index) + { + // because "base.Count + 1" for GridLinesRenderer + // argument checking done at the base class + if(index == base.VisualChildrenCount) + { + if (_gridLinesRenderer == null) + { + throw new ArgumentOutOfRangeException("index", index, SR.Get(SRID.Visual_ArgumentOutOfRange)); + } + return _gridLinesRenderer; + } + else return base.GetVisualChild(index); + } + + /// + /// Derived classes override this property to enable the Visual code to enumerate + /// the Visual children. Derived classes need to return the number of children + /// from this method. + /// + /// By default a Visual does not have any children. + /// + /// Remark: During this virtual method the Visual tree must not be modified. + /// + protected override int VisualChildrenCount + { + //since GridLinesRenderer has not been added as a child, so we do not subtract + get { return base.VisualChildrenCount + (_gridLinesRenderer != null ? 1 : 0); } + } + + + /// + /// Content measurement. + /// + /// Constraint + /// Desired size + protected override Size MeasureOverride(Size constraint) + { + Size gridDesiredSize; + ExtendedData extData = ExtData; + + try + { + EnterCounterScope(Counters.MeasureOverride); + + ListenToNotifications = true; + MeasureOverrideInProgress = true; + + if (extData == null) + { + gridDesiredSize = new Size(); + UIElementCollection children = InternalChildren; + + for (int i = 0, count = children.Count; i < count; ++i) + { + UIElement child = children[i]; + if (child != null) + { + child.Measure(constraint); + gridDesiredSize.Width = Math.Max(gridDesiredSize.Width, child.DesiredSize.Width); + gridDesiredSize.Height = Math.Max(gridDesiredSize.Height, child.DesiredSize.Height); + } + } + } + else + { + { + bool sizeToContentU = double.IsPositiveInfinity(constraint.Width); + bool sizeToContentV = double.IsPositiveInfinity(constraint.Height); + + // Clear index information and rounding errors + if (RowDefinitionCollectionDirty || ColumnDefinitionCollectionDirty) + { + if (_definitionIndices != null) + { + Array.Clear(_definitionIndices, 0, _definitionIndices.Length); + _definitionIndices = null; + } + + if (UseLayoutRounding) + { + if (_roundingErrors != null) + { + Array.Clear(_roundingErrors, 0, _roundingErrors.Length); + _roundingErrors = null; + } + } + } + + ValidateDefinitionsUStructure(); + ValidateDefinitionsLayout(DefinitionsU, sizeToContentU); + + ValidateDefinitionsVStructure(); + ValidateDefinitionsLayout(DefinitionsV, sizeToContentV); + + CellsStructureDirty |= (SizeToContentU != sizeToContentU) || (SizeToContentV != sizeToContentV); + + SizeToContentU = sizeToContentU; + SizeToContentV = sizeToContentV; + } + + ValidateCells(); + + Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); + + // Grid classifies cells into four groups depending on + // the column / row type a cell belongs to (number corresponds to + // group number): + // + // Px Auto Star + // +--------+--------+--------+ + // | | | | + // Px | 1 | 1 | 3 | + // | | | | + // +--------+--------+--------+ + // | | | | + // Auto | 1 | 1 | 3 | + // | | | | + // +--------+--------+--------+ + // | | | | + // Star | 4 | 2 | 4 | + // | | | | + // +--------+--------+--------+ + // + // The group number indicates the order in which cells are measured. + // Certain order is necessary to be able to dynamically resolve star + // columns / rows sizes which are used as input for measuring of + // the cells belonging to them. + // + // However, there are cases when topology of a grid causes cyclical + // size dependences. For example: + // + // + // column width="Auto" column width="*" + // +----------------------+----------------------+ + // | | | + // | | | + // | | | + // | | | + // row height="Auto" | | cell 1 2 | + // | | | + // | | | + // | | | + // | | | + // +----------------------+----------------------+ + // | | | + // | | | + // | | | + // | | | + // row height="*" | cell 2 1 | | + // | | | + // | | | + // | | | + // | | | + // +----------------------+----------------------+ + // + // In order to accurately calculate constraint width for "cell 1 2" + // (which is the remaining of grid's available width and calculated + // value of Auto column), "cell 2 1" needs to be calculated first, + // as it contributes to the Auto column's calculated value. + // At the same time in order to accurately calculate constraint + // height for "cell 2 1", "cell 1 2" needs to be calcualted first, + // as it contributes to Auto row height, which is used in the + // computation of Star row resolved height. + // + // to "break" this cyclical dependency we are making (arbitrary) + // decision to treat cells like "cell 2 1" as if they appear in Auto + // rows. And then recalculate them one more time when star row + // heights are resolved. + // + // (Or more strictly) the code below implement the following logic: + // + // +---------+ + // | enter | + // +---------+ + // | + // V + // +----------------+ + // | Measure Group1 | + // +----------------+ + // | + // V + // / - \ + // / \ + // Y / Can \ N + // +--------| Resolve |-----------+ + // | \ StarsV? / | + // | \ / | + // | \ - / | + // V V + // +----------------+ / - \ + // | Resolve StarsV | / \ + // +----------------+ Y / Can \ N + // | +----| Resolve |------+ + // V | \ StarsU? / | + // +----------------+ | \ / | + // | Measure Group2 | | \ - / | + // +----------------+ | V + // | | +-----------------+ + // V | | Measure Group2' | + // +----------------+ | +-----------------+ + // | Resolve StarsU | | | + // +----------------+ V V + // | +----------------+ +----------------+ + // V | Resolve StarsU | | Resolve StarsU | + // +----------------+ +----------------+ +----------------+ + // | Measure Group3 | | | + // +----------------+ V V + // | +----------------+ +----------------+ + // | | Measure Group3 | | Measure Group3 | + // | +----------------+ +----------------+ + // | | | + // | V V + // | +----------------+ +----------------+ + // | | Resolve StarsV | | Resolve StarsV | + // | +----------------+ +----------------+ + // | | | + // | | V + // | | +------------------+ + // | | | Measure Group2'' | + // | | +------------------+ + // | | | + // +----------------------+-------------------------+ + // | + // V + // +----------------+ + // | Measure Group4 | + // +----------------+ + // | + // V + // +--------+ + // | exit | + // +--------+ + // + // where: + // * all [Measure GroupN] - regular children measure process - + // each cell is measured given contraint size as an input + // and each cell's desired size is accumulated on the + // corresponding column / row; + // * [Measure Group2'] - is when each cell is measured with + // infinit height as a constraint and a cell's desired + // height is ignored; + // * [Measure Groups''] - is when each cell is measured (second + // time during single Grid.MeasureOverride) regularly but its + // returned width is ignored; + // + // This algorithm is believed to be as close to ideal as possible. + // It has the following drawbacks: + // * cells belonging to Group2 can be called to measure twice; + // * iff during second measure a cell belonging to Group2 returns + // desired width greater than desired width returned the first + // time, such a cell is going to be clipped, even though it + // appears in Auto column. + // + + MeasureCellsGroup(extData.CellGroup1, constraint, false, false); + + { + // after Group1 is measured, only Group3 may have cells belonging to Auto rows. + bool canResolveStarsV = !HasGroup3CellsInAutoRows; + + if (canResolveStarsV) + { + if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } + MeasureCellsGroup(extData.CellGroup2, constraint, false, false); + if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } + MeasureCellsGroup(extData.CellGroup3, constraint, false, false); + } + else + { + // if at least one cell exists in Group2, it must be measured before + // StarsU can be resolved. + bool canResolveStarsU = extData.CellGroup2 > PrivateCells.Length; + if (canResolveStarsU) + { + if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } + MeasureCellsGroup(extData.CellGroup3, constraint, false, false); + if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } + } + else + { + // This is a revision to the algorithm employed for the cyclic + // dependency case described above. We now repeatedly + // measure Group3 and Group2 until their sizes settle. We + // also use a count heuristic to break a loop in case of one. + + bool hasDesiredSizeUChanged = false; + int cnt=0; + + // Cache Group2MinWidths & Group3MinHeights + double[] group2MinSizes = CacheMinSizes(extData.CellGroup2, false); + double[] group3MinSizes = CacheMinSizes(extData.CellGroup3, true); + + MeasureCellsGroup(extData.CellGroup2, constraint, false, true); + + do + { + if (hasDesiredSizeUChanged) + { + // Reset cached Group3Heights + ApplyCachedMinSizes(group3MinSizes, true); + } + + if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } + MeasureCellsGroup(extData.CellGroup3, constraint, false, false); + + // Reset cached Group2Widths + ApplyCachedMinSizes(group2MinSizes, false); + + if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } + MeasureCellsGroup(extData.CellGroup2, constraint, cnt == c_layoutLoopMaxCount, false, out hasDesiredSizeUChanged); + } + while (hasDesiredSizeUChanged && ++cnt <= c_layoutLoopMaxCount); + } + } + } + + MeasureCellsGroup(extData.CellGroup4, constraint, false, false); + + EnterCounter(Counters._CalculateDesiredSize); + gridDesiredSize = new Size( + CalculateDesiredSize(DefinitionsU), + CalculateDesiredSize(DefinitionsV)); + ExitCounter(Counters._CalculateDesiredSize); + } + } + finally + { + MeasureOverrideInProgress = false; + ExitCounterScope(Counters.MeasureOverride); + } + + return (gridDesiredSize); + } + + /// + /// Content arrangement. + /// + /// Arrange size + protected override Size ArrangeOverride(Size arrangeSize) + { + try + { + EnterCounterScope(Counters.ArrangeOverride); + + ArrangeOverrideInProgress = true; + + if (_data == null) + { + UIElementCollection children = InternalChildren; + + for (int i = 0, count = children.Count; i < count; ++i) + { + UIElement child = children[i]; + if (child != null) + { + child.Arrange(new Rect(arrangeSize)); + } + } + } + else + { + Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); + + EnterCounter(Counters._SetFinalSize); + + SetFinalSize(DefinitionsU, arrangeSize.Width, true); + SetFinalSize(DefinitionsV, arrangeSize.Height, false); + + ExitCounter(Counters._SetFinalSize); + + UIElementCollection children = InternalChildren; + + for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) + { + UIElement cell = children[currentCell]; + if (cell == null) + { + continue; + } + + int columnIndex = PrivateCells[currentCell].ColumnIndex; + int rowIndex = PrivateCells[currentCell].RowIndex; + int columnSpan = PrivateCells[currentCell].ColumnSpan; + int rowSpan = PrivateCells[currentCell].RowSpan; + + Rect cellRect = new Rect( + columnIndex == 0 ? 0.0 : DefinitionsU[columnIndex].FinalOffset, + rowIndex == 0 ? 0.0 : DefinitionsV[rowIndex].FinalOffset, + GetFinalSizeForRange(DefinitionsU, columnIndex, columnSpan), + GetFinalSizeForRange(DefinitionsV, rowIndex, rowSpan) ); + + EnterCounter(Counters._ArrangeChildHelper2); + cell.Arrange(cellRect); + ExitCounter(Counters._ArrangeChildHelper2); + } + + // update render bound on grid lines renderer visual + GridLinesRenderer gridLinesRenderer = EnsureGridLinesRenderer(); + if (gridLinesRenderer != null) + { + gridLinesRenderer.UpdateRenderBounds(arrangeSize); + } + } + } + finally + { + SetValid(); + ArrangeOverrideInProgress = false; + ExitCounterScope(Counters.ArrangeOverride); + } + return (arrangeSize); + } + + /// + /// + /// + protected internal override void OnVisualChildrenChanged( + DependencyObject visualAdded, + DependencyObject visualRemoved) + { + CellsStructureDirty = true; + + base.OnVisualChildrenChanged(visualAdded, visualRemoved); + } + + #endregion Protected Methods + + //------------------------------------------------------ + // + // Internal Methods + // + //------------------------------------------------------ + + #region Internal Methods + + /// + /// Invalidates grid caches and makes the grid dirty for measure. + /// + internal void Invalidate() + { + CellsStructureDirty = true; + InvalidateMeasure(); + } + + /// + /// Returns final width for a column. + /// + /// + /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data. + /// + internal double GetFinalColumnDefinitionWidth(int columnIndex) + { + double value = 0.0; + + Invariant.Assert(_data != null); + + // actual value calculations require structure to be up-to-date + if (!ColumnDefinitionCollectionDirty) + { + DefinitionBase[] definitions = DefinitionsU; + value = definitions[(columnIndex + 1) % definitions.Length].FinalOffset; + if (columnIndex != 0) { value -= definitions[columnIndex].FinalOffset; } + } + return (value); + } + + /// + /// Returns final height for a row. + /// + /// + /// Used from public RowDefinition ActualHeight. Calculates final height using offset data. + /// + internal double GetFinalRowDefinitionHeight(int rowIndex) + { + double value = 0.0; + + Invariant.Assert(_data != null); + + // actual value calculations require structure to be up-to-date + if (!RowDefinitionCollectionDirty) + { + DefinitionBase[] definitions = DefinitionsV; + value = definitions[(rowIndex + 1) % definitions.Length].FinalOffset; + if (rowIndex != 0) { value -= definitions[rowIndex].FinalOffset; } + } + return (value); + } + + #endregion Internal Methods + + //------------------------------------------------------ + // + // Internal Properties + // + //------------------------------------------------------ + + #region Internal Properties + + /// + /// Convenience accessor to MeasureOverrideInProgress bit flag. + /// + internal bool MeasureOverrideInProgress + { + get { return (CheckFlagsAnd(Flags.MeasureOverrideInProgress)); } + set { SetFlags(value, Flags.MeasureOverrideInProgress); } + } + + /// + /// Convenience accessor to ArrangeOverrideInProgress bit flag. + /// + internal bool ArrangeOverrideInProgress + { + get { return (CheckFlagsAnd(Flags.ArrangeOverrideInProgress)); } + set { SetFlags(value, Flags.ArrangeOverrideInProgress); } + } + + /// + /// Convenience accessor to ValidDefinitionsUStructure bit flag. + /// + internal bool ColumnDefinitionCollectionDirty + { + get { return (!CheckFlagsAnd(Flags.ValidDefinitionsUStructure)); } + set { SetFlags(!value, Flags.ValidDefinitionsUStructure); } + } + + /// + /// Convenience accessor to ValidDefinitionsVStructure bit flag. + /// + internal bool RowDefinitionCollectionDirty + { + get { return (!CheckFlagsAnd(Flags.ValidDefinitionsVStructure)); } + set { SetFlags(!value, Flags.ValidDefinitionsVStructure); } + } + + #endregion Internal Properties + + //------------------------------------------------------ + // + // Private Methods + // + //------------------------------------------------------ + + #region Private Methods + + /// + /// Lays out cells according to rows and columns, and creates lookup grids. + /// + private void ValidateCells() + { + EnterCounter(Counters._ValidateCells); + + if (CellsStructureDirty) + { + ValidateCellsCore(); + CellsStructureDirty = false; + } + + ExitCounter(Counters._ValidateCells); + } + + /// + /// ValidateCellsCore + /// + private void ValidateCellsCore() + { + UIElementCollection children = InternalChildren; + ExtendedData extData = ExtData; + + extData.CellCachesCollection = new CellCache[children.Count]; + extData.CellGroup1 = int.MaxValue; + extData.CellGroup2 = int.MaxValue; + extData.CellGroup3 = int.MaxValue; + extData.CellGroup4 = int.MaxValue; + + bool hasStarCellsU = false; + bool hasStarCellsV = false; + bool hasGroup3CellsInAutoRows = false; + + for (int i = PrivateCells.Length - 1; i >= 0; --i) + { + UIElement child = children[i]; + if (child == null) + { + continue; + } + + CellCache cell = new CellCache(); + + // + // read and cache child positioning properties + // + + // read indices from the corresponding properties + // clamp to value < number_of_columns + // column >= 0 is guaranteed by property value validation callback + cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Length - 1); + // clamp to value < number_of_rows + // row >= 0 is guaranteed by property value validation callback + cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Length - 1); + + // read span properties + // clamp to not exceed beyond right side of the grid + // column_span > 0 is guaranteed by property value validation callback + cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Length - cell.ColumnIndex); + + // clamp to not exceed beyond bottom side of the grid + // row_span > 0 is guaranteed by property value validation callback + cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Length - cell.RowIndex); + + Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Length); + Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Length); + + // + // calculate and cache length types for the child + // + + cell.SizeTypeU = GetLengthTypeForRange(DefinitionsU, cell.ColumnIndex, cell.ColumnSpan); + cell.SizeTypeV = GetLengthTypeForRange(DefinitionsV, cell.RowIndex, cell.RowSpan); + + hasStarCellsU |= cell.IsStarU; + hasStarCellsV |= cell.IsStarV; + + // + // distribute cells into four groups. + // + + if (!cell.IsStarV) + { + if (!cell.IsStarU) + { + cell.Next = extData.CellGroup1; + extData.CellGroup1 = i; + } + else + { + cell.Next = extData.CellGroup3; + extData.CellGroup3 = i; + + // remember if this cell belongs to auto row + hasGroup3CellsInAutoRows |= cell.IsAutoV; + } + } + else + { + if ( cell.IsAutoU + // note below: if spans through Star column it is NOT Auto + && !cell.IsStarU ) + { + cell.Next = extData.CellGroup2; + extData.CellGroup2 = i; + } + else + { + cell.Next = extData.CellGroup4; + extData.CellGroup4 = i; + } + } + + PrivateCells[i] = cell; + } + + HasStarCellsU = hasStarCellsU; + HasStarCellsV = hasStarCellsV; + HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows; + } + + /// + /// Initializes DefinitionsU memeber either to user supplied ColumnDefinitions collection + /// or to a default single element collection. DefinitionsU gets trimmed to size. + /// + /// + /// This is one of two methods, where ColumnDefinitions and DefinitionsU are directly accessed. + /// All the rest measure / arrange / render code must use DefinitionsU. + /// + private void ValidateDefinitionsUStructure() + { + EnterCounter(Counters._ValidateColsStructure); + + if (ColumnDefinitionCollectionDirty) + { + ExtendedData extData = ExtData; + + if (extData.ColumnDefinitions == null) + { + if (extData.DefinitionsU == null) + { + extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() }; + } + } + else + { + extData.ColumnDefinitions.InternalTrimToSize(); + + if (extData.ColumnDefinitions.InternalCount == 0) + { + // if column definitions collection is empty + // mockup array with one column + extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() }; + } + else + { + extData.DefinitionsU = extData.ColumnDefinitions.InternalItems; + } + } + + ColumnDefinitionCollectionDirty = false; + } + + Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Length > 0); + + ExitCounter(Counters._ValidateColsStructure); + } + + /// + /// Initializes DefinitionsV memeber either to user supplied RowDefinitions collection + /// or to a default single element collection. DefinitionsV gets trimmed to size. + /// + /// + /// This is one of two methods, where RowDefinitions and DefinitionsV are directly accessed. + /// All the rest measure / arrange / render code must use DefinitionsV. + /// + private void ValidateDefinitionsVStructure() + { + EnterCounter(Counters._ValidateRowsStructure); + + if (RowDefinitionCollectionDirty) + { + ExtendedData extData = ExtData; + + if (extData.RowDefinitions == null) + { + if (extData.DefinitionsV == null) + { + extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() }; + } + } + else + { + extData.RowDefinitions.InternalTrimToSize(); + + if (extData.RowDefinitions.InternalCount == 0) + { + // if row definitions collection is empty + // mockup array with one row + extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() }; + } + else + { + extData.DefinitionsV = extData.RowDefinitions.InternalItems; + } + } + + RowDefinitionCollectionDirty = false; + } + + Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Length > 0); + + ExitCounter(Counters._ValidateRowsStructure); + } + + /// + /// Validates layout time size type information on given array of definitions. + /// Sets MinSize and MeasureSizes. + /// + /// Array of definitions to update. + /// if "true" then star definitions are treated as Auto. + private void ValidateDefinitionsLayout( + DefinitionBase[] definitions, + bool treatStarAsAuto) + { + for (int i = 0; i < definitions.Length; ++i) + { + definitions[i].OnBeforeLayout(this); + + double userMinSize = definitions[i].UserMinSize; + double userMaxSize = definitions[i].UserMaxSize; + double userSize = 0; + + switch (definitions[i].UserSize.GridUnitType) + { + case (GridUnitType.Pixel): + definitions[i].SizeType = LayoutTimeSizeType.Pixel; + userSize = definitions[i].UserSize.Value; + // this was brought with NewLayout and defeats squishy behavior + userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); + break; + case (GridUnitType.Auto): + definitions[i].SizeType = LayoutTimeSizeType.Auto; + userSize = double.PositiveInfinity; + break; + case (GridUnitType.Star): + if (treatStarAsAuto) + { + definitions[i].SizeType = LayoutTimeSizeType.Auto; + userSize = double.PositiveInfinity; + } + else + { + definitions[i].SizeType = LayoutTimeSizeType.Star; + userSize = double.PositiveInfinity; + } + break; + default: + Debug.Assert(false); + break; + } + + definitions[i].UpdateMinSize(userMinSize); + definitions[i].MeasureSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); + } + } + + private double[] CacheMinSizes(int cellsHead, bool isRows) + { + double[] minSizes = isRows ? new double[DefinitionsV.Length] : new double[DefinitionsU.Length]; + + for (int j=0; j + /// Measures one group of cells. + /// + /// Head index of the cells chain. + /// Reference size for spanned cells + /// calculations. + /// When "true" cells' desired + /// width is not registered in columns. + /// Passed through to MeasureCell. + /// When "true" cells' desired height is not registered in rows. + private void MeasureCellsGroup( + int cellsHead, + Size referenceSize, + bool ignoreDesiredSizeU, + bool forceInfinityV, + out bool hasDesiredSizeUChanged) + { + hasDesiredSizeUChanged = false; + + if (cellsHead >= PrivateCells.Length) + { + return; + } + + UIElementCollection children = InternalChildren; + Hashtable spanStore = null; + bool ignoreDesiredSizeV = forceInfinityV; + + int i = cellsHead; + do + { + double oldWidth = children[i].DesiredSize.Width; + + MeasureCell(i, forceInfinityV); + + hasDesiredSizeUChanged |= !DoubleUtil.AreClose(oldWidth, children[i].DesiredSize.Width); + + if (!ignoreDesiredSizeU) + { + if (PrivateCells[i].ColumnSpan == 1) + { + DefinitionsU[PrivateCells[i].ColumnIndex].UpdateMinSize(Math.Min(children[i].DesiredSize.Width, DefinitionsU[PrivateCells[i].ColumnIndex].UserMaxSize)); + } + else + { + RegisterSpan( + ref spanStore, + PrivateCells[i].ColumnIndex, + PrivateCells[i].ColumnSpan, + true, + children[i].DesiredSize.Width); + } + } + + if (!ignoreDesiredSizeV) + { + if (PrivateCells[i].RowSpan == 1) + { + DefinitionsV[PrivateCells[i].RowIndex].UpdateMinSize(Math.Min(children[i].DesiredSize.Height, DefinitionsV[PrivateCells[i].RowIndex].UserMaxSize)); + } + else + { + RegisterSpan( + ref spanStore, + PrivateCells[i].RowIndex, + PrivateCells[i].RowSpan, + false, + children[i].DesiredSize.Height); + } + } + + i = PrivateCells[i].Next; + } while (i < PrivateCells.Length); + + if (spanStore != null) + { + foreach (DictionaryEntry e in spanStore) + { + SpanKey key = (SpanKey)e.Key; + double requestedSize = (double)e.Value; + + EnsureMinSizeInDefinitionRange( + key.U ? DefinitionsU : DefinitionsV, + key.Start, + key.Count, + requestedSize, + key.U ? referenceSize.Width : referenceSize.Height); + } + } + } + + /// + /// Helper method to register a span information for delayed processing. + /// + /// Reference to a hashtable object used as storage. + /// Span starting index. + /// Span count. + /// true if this is a column span. false if this is a row span. + /// Value to store. If an entry already exists the biggest value is stored. + private static void RegisterSpan( + ref Hashtable store, + int start, + int count, + bool u, + double value) + { + if (store == null) + { + store = new Hashtable(); + } + + SpanKey key = new SpanKey(start, count, u); + object o = store[key]; + + if ( o == null + || value > (double)o ) + { + store[key] = value; + } + } + + /// + /// Takes care of measuring a single cell. + /// + /// Index of the cell to measure. + /// If "true" then cell is always + /// calculated to infinite height. + private void MeasureCell( + int cell, + bool forceInfinityV) + { + EnterCounter(Counters._MeasureCell); + + double cellMeasureWidth; + double cellMeasureHeight; + + if ( PrivateCells[cell].IsAutoU + && !PrivateCells[cell].IsStarU ) + { + // if cell belongs to at least one Auto column and not a single Star column + // then it should be calculated "to content", thus it is possible to "shortcut" + // calculations and simply assign PositiveInfinity here. + cellMeasureWidth = double.PositiveInfinity; + } + else + { + // otherwise... + cellMeasureWidth = GetMeasureSizeForRange( + DefinitionsU, + PrivateCells[cell].ColumnIndex, + PrivateCells[cell].ColumnSpan); + } + + if (forceInfinityV) + { + cellMeasureHeight = double.PositiveInfinity; + } + else if ( PrivateCells[cell].IsAutoV + && !PrivateCells[cell].IsStarV ) + { + // if cell belongs to at least one Auto row and not a single Star row + // then it should be calculated "to content", thus it is possible to "shortcut" + // calculations and simply assign PositiveInfinity here. + cellMeasureHeight = double.PositiveInfinity; + } + else + { + cellMeasureHeight = GetMeasureSizeForRange( + DefinitionsV, + PrivateCells[cell].RowIndex, + PrivateCells[cell].RowSpan); + } + + EnterCounter(Counters.__MeasureChild); + UIElement child = InternalChildren[cell]; + if (child != null) + { + Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); + child.Measure(childConstraint); + } + ExitCounter(Counters.__MeasureChild); + + ExitCounter(Counters._MeasureCell); + } + + + /// + /// Calculates one dimensional measure size for given definitions' range. + /// + /// Source array of definitions to read values from. + /// Starting index of the range. + /// Number of definitions included in the range. + /// Calculated measure size. + /// + /// For "Auto" definitions MinWidth is used in place of PreferredSize. + /// + private double GetMeasureSizeForRange( + DefinitionBase[] definitions, + int start, + int count) + { + Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); + + double measureSize = 0; + int i = start + count - 1; + + do + { + measureSize += (definitions[i].SizeType == LayoutTimeSizeType.Auto) + ? definitions[i].MinSize + : definitions[i].MeasureSize; + } while (--i >= start); + + return (measureSize); + } + + /// + /// Accumulates length type information for given definition's range. + /// + /// Source array of definitions to read values from. + /// Starting index of the range. + /// Number of definitions included in the range. + /// Length type for given range. + private LayoutTimeSizeType GetLengthTypeForRange( + DefinitionBase[] definitions, + int start, + int count) + { + Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); + + LayoutTimeSizeType lengthType = LayoutTimeSizeType.None; + int i = start + count - 1; + + do + { + lengthType |= definitions[i].SizeType; + } while (--i >= start); + + return (lengthType); + } + + /// + /// Distributes min size back to definition array's range. + /// + /// Start of the range. + /// Number of items in the range. + /// Minimum size that should "fit" into the definitions range. + /// Definition array receiving distribution. + /// Size used to resolve percentages. + private void EnsureMinSizeInDefinitionRange( + DefinitionBase[] definitions, + int start, + int count, + double requestedSize, + double percentReferenceSize) + { + Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length); + + // avoid processing when asked to distribute "0" + if (!_IsZero(requestedSize)) + { + DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting + int end = start + count; + int autoDefinitionsCount = 0; + double rangeMinSize = 0; + double rangePreferredSize = 0; + double rangeMaxSize = 0; + double maxMaxSize = 0; // maximum of maximum sizes + + // first accumulate the necessary information: + // a) sum up the sizes in the range; + // b) count the number of auto definitions in the range; + // c) initialize temp array + // d) cache the maximum size into SizeCache + // e) accumulate max of max sizes + for (int i = start; i < end; ++i) + { + double minSize = definitions[i].MinSize; + double preferredSize = definitions[i].PreferredSize; + double maxSize = Math.Max(definitions[i].UserMaxSize, minSize); + + rangeMinSize += minSize; + rangePreferredSize += preferredSize; + rangeMaxSize += maxSize; + + definitions[i].SizeCache = maxSize; + + // sanity check: no matter what, but min size must always be the smaller; + // max size must be the biggest; and preferred should be in between + Debug.Assert( minSize <= preferredSize + && preferredSize <= maxSize + && rangeMinSize <= rangePreferredSize + && rangePreferredSize <= rangeMaxSize ); + + if (maxMaxSize < maxSize) maxMaxSize = maxSize; + if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++; + tempDefinitions[i - start] = definitions[i]; + } + + // avoid processing if the range already big enough + if (requestedSize > rangeMinSize) + { + if (requestedSize <= rangePreferredSize) + { + // + // requestedSize fits into preferred size of the range. + // distribute according to the following logic: + // * do not distribute into auto definitions - they should continue to stay "tight"; + // * for all non-auto definitions distribute to equi-size min sizes, without exceeding preferred size. + // + // in order to achieve that, definitions are sorted in a way that all auto definitions + // are first, then definitions follow ascending order with PreferredSize as the key of sorting. + // + double sizeToDistribute; + int i; + + Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer); + for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) + { + // sanity check: only auto definitions allowed in this loop + Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + + // adjust sizeToDistribute value by subtracting auto definition min size + sizeToDistribute -= (tempDefinitions[i].MinSize); + } + + for (; i < count; ++i) + { + // sanity check: no auto definitions allowed in this loop + Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + + double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize); + if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); } + sizeToDistribute -= newMinSize; + } + + // sanity check: requested size must all be distributed + Debug.Assert(_IsZero(sizeToDistribute)); + } + else if (requestedSize <= rangeMaxSize) + { + // + // requestedSize bigger than preferred size, but fit into max size of the range. + // distribute according to the following logic: + // * do not distribute into auto definitions, if possible - they should continue to stay "tight"; + // * for all non-auto definitions distribute to euqi-size min sizes, without exceeding max size. + // + // in order to achieve that, definitions are sorted in a way that all non-auto definitions + // are last, then definitions follow ascending order with MaxSize as the key of sorting. + // + double sizeToDistribute; + int i; + + Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer); + for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) + { + // sanity check: no auto definitions allowed in this loop + Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + + double preferredSize = tempDefinitions[i].PreferredSize; + double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i); + tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); + sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + } + + for (; i < count; ++i) + { + // sanity check: only auto definitions allowed in this loop + Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + + double preferredSize = tempDefinitions[i].MinSize; + double newMinSize = preferredSize + sizeToDistribute / (count - i); + tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); + sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + } + + // sanity check: requested size must all be distributed + Debug.Assert(_IsZero(sizeToDistribute)); + } + else + { + // + // requestedSize bigger than max size of the range. + // distribute according to the following logic: + // * for all definitions distribute to equi-size min sizes. + // + double equalSize = requestedSize / count; + + if ( equalSize < maxMaxSize + && !_AreClose(equalSize, maxMaxSize) ) + { + // equi-size is less than maximum of maxSizes. + // in this case distribute so that smaller definitions grow faster than + // bigger ones. + double totalRemainingSize = maxMaxSize * count - rangeMaxSize; + double sizeToDistribute = requestedSize - rangeMaxSize; + + // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers + Debug.Assert( !double.IsInfinity(totalRemainingSize) + && !DoubleUtil.IsNaN(totalRemainingSize) + && totalRemainingSize > 0 + && !double.IsInfinity(sizeToDistribute) + && !DoubleUtil.IsNaN(sizeToDistribute) + && sizeToDistribute > 0 ); + + for (int i = 0; i < count; ++i) + { + double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize; + tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize); + } + } + else + { + // + // equi-size is greater or equal to maximum of max sizes. + // all definitions receive equalSize as their mim sizes. + // + for (int i = 0; i < count; ++i) + { + tempDefinitions[i].UpdateMinSize(equalSize); + } + } + } + } + } + } + + /// + /// Resolves Star's for given array of definitions. + /// + /// Array of definitions to resolve stars. + /// All available size. + /// + /// Must initialize LayoutSize for all Star entries in given array of definitions. + /// + private void ResolveStar( + DefinitionBase[] definitions, + double availableSize) + { + if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) + { + ResolveStarLegacy(definitions, availableSize); + } + else + { + ResolveStarMaxDiscrepancy(definitions, availableSize); + } + } + + // original implementation, used from 3.0 through 4.6.2 + private void ResolveStarLegacy( + DefinitionBase[] definitions, + double availableSize) + { + DefinitionBase[] tempDefinitions = TempDefinitions; + int starDefinitionsCount = 0; + double takenSize = 0; + + for (int i = 0; i < definitions.Length; ++i) + { + switch (definitions[i].SizeType) + { + case (LayoutTimeSizeType.Auto): + takenSize += definitions[i].MinSize; + break; + case (LayoutTimeSizeType.Pixel): + takenSize += definitions[i].MeasureSize; + break; + case (LayoutTimeSizeType.Star): + { + tempDefinitions[starDefinitionsCount++] = definitions[i]; + + double starValue = definitions[i].UserSize.Value; + + if (_IsZero(starValue)) + { + definitions[i].MeasureSize = 0; + definitions[i].SizeCache = 0; + } + else + { + // clipping by c_starClip guarantees that sum of even a very big number of max'ed out star values + // can be summed up without overflow + starValue = Math.Min(starValue, c_starClip); + + // Note: normalized star value is temporary cached into MeasureSize + definitions[i].MeasureSize = starValue; + double maxSize = Math.Max(definitions[i].MinSize, definitions[i].UserMaxSize); + maxSize = Math.Min(maxSize, c_starClip); + definitions[i].SizeCache = maxSize / starValue; + } + } + break; + } + } + + if (starDefinitionsCount > 0) + { + Array.Sort(tempDefinitions, 0, starDefinitionsCount, s_starDistributionOrderComparer); + + // the 'do {} while' loop below calculates sum of star weights in order to avoid fp overflow... + // partial sum value is stored in each definition's SizeCache member. + // this way the algorithm guarantees (starValue <= definition.SizeCache) and thus + // (starValue / definition.SizeCache) will never overflow due to sum of star weights becoming zero. + // this is an important change from previous implementation where the following was possible: + // ((BigValueStar + SmallValueStar) - BigValueStar) resulting in 0... + double allStarWeights = 0; + int i = starDefinitionsCount - 1; + do + { + allStarWeights += tempDefinitions[i].MeasureSize; + tempDefinitions[i].SizeCache = allStarWeights; + } while (--i >= 0); + + i = 0; + do + { + double resolvedSize; + double starValue = tempDefinitions[i].MeasureSize; + + if (_IsZero(starValue)) + { + resolvedSize = tempDefinitions[i].MinSize; + } + else + { + double userSize = Math.Max(availableSize - takenSize, 0.0) * (starValue / tempDefinitions[i].SizeCache); + resolvedSize = Math.Min(userSize, tempDefinitions[i].UserMaxSize); + resolvedSize = Math.Max(tempDefinitions[i].MinSize, resolvedSize); + } + + tempDefinitions[i].MeasureSize = resolvedSize; + takenSize += resolvedSize; + } while (++i < starDefinitionsCount); + } + } + + // new implementation as of 4.7. Several improvements: + // 1. Allocate to *-defs hitting their min or max constraints, before allocating + // to other *-defs. A def that hits its min uses more space than its + // proportional share, reducing the space available to everyone else. + // The legacy algorithm deducted this space only from defs processed + // after the min; the new algorithm deducts it proportionally from all + // defs. This avoids the "*-defs exceed available space" problem, + // and other related problems where *-defs don't receive proportional + // allocations even though no constraints are preventing it. + // 2. When multiple defs hit min or max, resolve the one with maximum + // discrepancy (defined below). This avoids discontinuities - small + // change in available space resulting in large change to one def's allocation. + // 3. Correct handling of large *-values, including Infinity. + private void ResolveStarMaxDiscrepancy( + DefinitionBase[] definitions, + double availableSize) + { + int defCount = definitions.Length; + DefinitionBase[] tempDefinitions = TempDefinitions; + int minCount = 0, maxCount = 0; + double takenSize = 0; + double totalStarWeight = 0.0; + int starCount = 0; // number of unresolved *-definitions + double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" + + // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights + double maxStar = 0.0; + for (int i=0; i maxStar) + { + maxStar = def.UserSize.Value; + } + } + } + + if (Double.IsPositiveInfinity(maxStar)) + { + // negative scale means one or more of the weights was Infinity + scale = -1.0; + } + else if (starCount > 0) + { + // if maxStar * starCount > Double.Max, summing all the weights could cause + // floating-point overflow. To avoid that, scale the weights by a factor to keep + // the sum within limits. Choose a power of 2, to preserve precision. + double power = Math.Floor(Math.Log(Double.MaxValue / maxStar / starCount, 2.0)); + if (power < 0.0) + { + scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia + } + } + + // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights + // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. + // More explanation below... + for (bool runPhase2and3=true; runPhase2and3; ) + { + // Phase 2. Compute total *-weight W and available space S. + // For *-items that have Min or Max constraints, compute the ratios used to decide + // whether proportional space is too big or too small and add the item to the + // corresponding list. (The "min" list is in the first half of tempDefinitions, + // the "max" list in the second half. TempDefinitions has capacity at least + // 2*defCount, so there's room for both lists.) + totalStarWeight = 0.0; + takenSize = 0.0; + minCount = maxCount = 0; + + for (int i=0; i 0.0) + { + // store ratio w/min in MeasureSize (for now) + tempDefinitions[minCount++] = def; + def.MeasureSize = starWeight / def.MinSize; + } + + double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); + if (!Double.IsPositiveInfinity(effectiveMaxSize)) + { + // store ratio w/max in SizeCache (for now) + tempDefinitions[defCount + maxCount++] = def; + def.SizeCache = starWeight / effectiveMaxSize; + } + } + break; + } + } + + // Phase 3. Resolve *-items whose proportional sizes are too big or too small. + int minCountPhase2 = minCount, maxCountPhase2 = maxCount; + double takenStarWeight = 0.0; + double remainingAvailableSize = availableSize - takenSize; + double remainingStarWeight = totalStarWeight - takenStarWeight; + Array.Sort(tempDefinitions, 0, minCount, s_minRatioComparer); + Array.Sort(tempDefinitions, defCount, maxCount, s_maxRatioComparer); + + while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) + { + // the calculation + // remainingStarWeight = totalStarWeight - takenStarWeight + // is subject to catastrophic cancellation if the two terms are nearly equal, + // which leads to meaningless results. Check for that, and recompute from + // the remaining definitions. [This leads to quadratic behavior in really + // pathological cases - but they'd never arise in practice.] + const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate + if (remainingStarWeight < totalStarWeight * starFactor) + { + takenStarWeight = 0.0; + totalStarWeight = 0.0; + + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + if (def.SizeType == LayoutTimeSizeType.Star && def.MeasureSize > 0.0) + { + totalStarWeight += StarWeight(def, scale); + } + } + + remainingStarWeight = totalStarWeight - takenStarWeight; + } + + double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity; + double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; + + // choose the def with larger ratio to the current proportion ("max discrepancy") + double proportion = remainingStarWeight / remainingAvailableSize; + bool? chooseMin = Choose(minRatio, maxRatio, proportion); + + // if no def was chosen, advance to phase 4; the current proportion doesn't + // conflict with any min or max values. + if (!(chooseMin.HasValue)) + { + break; + } + + // get the chosen definition and its resolved size + DefinitionBase resolvedDef; + double resolvedSize; + if (chooseMin == true) + { + resolvedDef = tempDefinitions[minCount - 1]; + resolvedSize = resolvedDef.MinSize; + --minCount; + } + else + { + resolvedDef = tempDefinitions[defCount + maxCount - 1]; + resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); + --maxCount; + } + + // resolve the chosen def, deduct its contributions from W and S. + // Defs resolved in phase 3 are marked by storing the negative of their resolved + // size in MeasureSize, to distinguish them from a pending def. + takenSize += resolvedSize; + resolvedDef.MeasureSize = -resolvedSize; + takenStarWeight += StarWeight(resolvedDef, scale); + --starCount; + + remainingAvailableSize = availableSize - takenSize; + remainingStarWeight = totalStarWeight - takenStarWeight; + + // advance to the next candidate defs, removing ones that have been resolved. + // Both counts are advanced, as a def might appear in both lists. + while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0) + { + --minCount; + tempDefinitions[minCount] = null; + } + while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0) + { + --maxCount; + tempDefinitions[defCount + maxCount] = null; + } + } + + // decide whether to run Phase2 and Phase3 again. There are 3 cases: + // 1. There is space available, and *-defs remaining. This is the + // normal case - move on to Phase 4 to allocate the remaining + // space proportionally to the remaining *-defs. + // 2. There is space available, but no *-defs. This implies at least one + // def was resolved as 'max', taking less space than its proportion. + // If there are also 'min' defs, reconsider them - we can give + // them more space. If not, all the *-defs are 'max', so there's + // no way to use all the available space. + // 3. We allocated too much space. This implies at least one def was + // resolved as 'min'. If there are also 'max' defs, reconsider + // them, otherwise the over-allocation is an inevitable consequence + // of the given min constraints. + // Note that if we return to Phase2, at least one *-def will have been + // resolved. This guarantees we don't run Phase2+3 infinitely often. + runPhase2and3 = false; + if (starCount == 0 && takenSize < availableSize) + { + // if no *-defs remain and we haven't allocated all the space, reconsider the defs + // resolved as 'min'. Their allocation can be increased to make up the gap. + for (int i = minCount; i < minCountPhase2; ++i) + { + DefinitionBase def = tempDefinitions[i]; + if (def != null) + { + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + + if (takenSize > availableSize) + { + // if we've allocated too much space, reconsider the defs + // resolved as 'max'. Their allocation can be decreased to make up the gap. + for (int i = maxCount; i < maxCountPhase2; ++i) + { + DefinitionBase def = tempDefinitions[defCount + i]; + if (def != null) + { + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + } + + // Phase 4. Resolve the remaining defs proportionally. + starCount = 0; + for (int i=0; i 0) + { + Array.Sort(tempDefinitions, 0, starCount, s_starWeightComparer); + + // compute the partial sums of *-weight, in increasing order of weight + // for minimal loss of precision. + totalStarWeight = 0.0; + for (int i = 0; i < starCount; ++i) + { + DefinitionBase def = tempDefinitions[i]; + totalStarWeight += def.MeasureSize; + def.SizeCache = totalStarWeight; + } + + // resolve the defs, in decreasing order of weight + for (int i = starCount - 1; i >= 0; --i) + { + DefinitionBase def = tempDefinitions[i]; + double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; + + // min and max should have no effect by now, but just in case... + resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); + resolvedSize = Math.Max(def.MinSize, resolvedSize); + + def.MeasureSize = resolvedSize; + takenSize += resolvedSize; + } + } + } + + /// + /// Calculates desired size for given array of definitions. + /// + /// Array of definitions to use for calculations. + /// Desired size. + private double CalculateDesiredSize( + DefinitionBase[] definitions) + { + double desiredSize = 0; + + for (int i = 0; i < definitions.Length; ++i) + { + desiredSize += definitions[i].MinSize; + } + + return (desiredSize); + } + + /// + /// Calculates and sets final size for all definitions in the given array. + /// + /// Array of definitions to process. + /// Final size to lay out to. + /// True if sizing row definitions, false for columns + private void SetFinalSize( + DefinitionBase[] definitions, + double finalSize, + bool columns) + { + if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) + { + SetFinalSizeLegacy(definitions, finalSize, columns); + } + else + { + SetFinalSizeMaxDiscrepancy(definitions, finalSize, columns); + } + } + + // original implementation, used from 3.0 through 4.6.2 + private void SetFinalSizeLegacy( + DefinitionBase[] definitions, + double finalSize, + bool columns) + { + int starDefinitionsCount = 0; // traverses form the first entry up + int nonStarIndex = definitions.Length; // traverses from the last entry down + double allPreferredArrangeSize = 0; + bool useLayoutRounding = this.UseLayoutRounding; + int[] definitionIndices = DefinitionIndices; + double[] roundingErrors = null; + + // If using layout rounding, check whether rounding needs to compensate for high DPI + double dpi = 1.0; + + if (useLayoutRounding) + { + DpiScale dpiScale = GetDpi(); + dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; + roundingErrors = RoundingErrors; + } + + for (int i = 0; i < definitions.Length; ++i) + { + // if definition is shared then is cannot be star + Debug.Assert(!definitions[i].IsShared || !definitions[i].UserSize.IsStar); + + if (definitions[i].UserSize.IsStar) + { + double starValue = definitions[i].UserSize.Value; + + if (_IsZero(starValue)) + { + // cach normilized star value temporary into MeasureSize + definitions[i].MeasureSize = 0; + definitions[i].SizeCache = 0; + } + else + { + // clipping by c_starClip guarantees that sum of even a very big number of max'ed out star values + // can be summed up without overflow + starValue = Math.Min(starValue, c_starClip); + + // Note: normalized star value is temporary cached into MeasureSize + definitions[i].MeasureSize = starValue; + double maxSize = Math.Max(definitions[i].MinSizeForArrange, definitions[i].UserMaxSize); + maxSize = Math.Min(maxSize, c_starClip); + definitions[i].SizeCache = maxSize / starValue; + if (useLayoutRounding) + { + roundingErrors[i] = definitions[i].SizeCache; + definitions[i].SizeCache = UIElement.RoundLayoutValue(definitions[i].SizeCache, dpi); + } + } + definitionIndices[starDefinitionsCount++] = i; + } + else + { + double userSize = 0; + + switch (definitions[i].UserSize.GridUnitType) + { + case (GridUnitType.Pixel): + userSize = definitions[i].UserSize.Value; + break; + + case (GridUnitType.Auto): + userSize = definitions[i].MinSizeForArrange; + break; + } + + double userMaxSize; + + if (definitions[i].IsShared) + { + // overriding userMaxSize effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + userMaxSize = userSize; + } + else + { + userMaxSize = definitions[i].UserMaxSize; + } + + definitions[i].SizeCache = Math.Max(definitions[i].MinSizeForArrange, Math.Min(userSize, userMaxSize)); + if (useLayoutRounding) + { + roundingErrors[i] = definitions[i].SizeCache; + definitions[i].SizeCache = UIElement.RoundLayoutValue(definitions[i].SizeCache, dpi); + } + + allPreferredArrangeSize += definitions[i].SizeCache; + definitionIndices[--nonStarIndex] = i; + } + } + + // indices should meet + Debug.Assert(nonStarIndex == starDefinitionsCount); + + if (starDefinitionsCount > 0) + { + StarDistributionOrderIndexComparer starDistributionOrderIndexComparer = new StarDistributionOrderIndexComparer(definitions); + Array.Sort(definitionIndices, 0, starDefinitionsCount, starDistributionOrderIndexComparer); + + // the 'do {} while' loop below calculates sum of star weights in order to avoid fp overflow... + // partial sum value is stored in each definition's SizeCache member. + // this way the algorithm guarantees (starValue <= definition.SizeCache) and thus + // (starValue / definition.SizeCache) will never overflow due to sum of star weights becoming zero. + // this is an important change from previous implementation where the following was possible: + // ((BigValueStar + SmallValueStar) - BigValueStar) resulting in 0... + double allStarWeights = 0; + int i = starDefinitionsCount - 1; + do + { + allStarWeights += definitions[definitionIndices[i]].MeasureSize; + definitions[definitionIndices[i]].SizeCache = allStarWeights; + } while (--i >= 0); + + i = 0; + do + { + double resolvedSize; + double starValue = definitions[definitionIndices[i]].MeasureSize; + + if (_IsZero(starValue)) + { + resolvedSize = definitions[definitionIndices[i]].MinSizeForArrange; + } + else + { + double userSize = Math.Max(finalSize - allPreferredArrangeSize, 0.0) * (starValue / definitions[definitionIndices[i]].SizeCache); + resolvedSize = Math.Min(userSize, definitions[definitionIndices[i]].UserMaxSize); + resolvedSize = Math.Max(definitions[definitionIndices[i]].MinSizeForArrange, resolvedSize); + } + + definitions[definitionIndices[i]].SizeCache = resolvedSize; + if (useLayoutRounding) + { + roundingErrors[definitionIndices[i]] = definitions[definitionIndices[i]].SizeCache; + definitions[definitionIndices[i]].SizeCache = UIElement.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi); + } + + allPreferredArrangeSize += definitions[definitionIndices[i]].SizeCache; + } while (++i < starDefinitionsCount); + } + + if ( allPreferredArrangeSize > finalSize + && !_AreClose(allPreferredArrangeSize, finalSize) ) + { + DistributionOrderIndexComparer distributionOrderIndexComparer = new DistributionOrderIndexComparer(definitions); + Array.Sort(definitionIndices, 0, definitions.Length, distributionOrderIndexComparer); + double sizeToDistribute = finalSize - allPreferredArrangeSize; + + for (int i = 0; i < definitions.Length; ++i) + { + int definitionIndex = definitionIndices[i]; + double final = definitions[definitionIndex].SizeCache + (sizeToDistribute / (definitions.Length - i)); + double finalOld = final; + final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); + final = Math.Min(final, definitions[definitionIndex].SizeCache); + + if (useLayoutRounding) + { + roundingErrors[definitionIndex] = final; + final = UIElement.RoundLayoutValue(finalOld, dpi); + final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); + final = Math.Min(final, definitions[definitionIndex].SizeCache); + } + + sizeToDistribute -= (final - definitions[definitionIndex].SizeCache); + definitions[definitionIndex].SizeCache = final; + } + + allPreferredArrangeSize = finalSize - sizeToDistribute; + } + + if (useLayoutRounding) + { + if (!_AreClose(allPreferredArrangeSize, finalSize)) + { + // Compute deltas + for (int i = 0; i < definitions.Length; ++i) + { + roundingErrors[i] = roundingErrors[i] - definitions[i].SizeCache; + definitionIndices[i] = i; + } + + // Sort rounding errors + RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); + Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); + double adjustedSize = allPreferredArrangeSize; + double dpiIncrement = UIElement.RoundLayoutValue(1.0, dpi); + + if (allPreferredArrangeSize > finalSize) + { + int i = definitions.Length - 1; + while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache - dpiIncrement; + final = Math.Max(final, definition.MinSizeForArrange); + if (final < definition.SizeCache) + { + adjustedSize -= dpiIncrement; + } + definition.SizeCache = final; + i--; + } + } + else if (allPreferredArrangeSize < finalSize) + { + int i = 0; + while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache + dpiIncrement; + final = Math.Max(final, definition.MinSizeForArrange); + if (final > definition.SizeCache) + { + adjustedSize += dpiIncrement; + } + definition.SizeCache = final; + i++; + } + } + } + } + + definitions[0].FinalOffset = 0.0; + for (int i = 0; i < definitions.Length; ++i) + { + definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; + } + } + + // new implementation, as of 4.7. This incorporates the same algorithm + // as in ResolveStarMaxDiscrepancy. It differs in the same way that SetFinalSizeLegacy + // differs from ResolveStarLegacy, namely (a) leaves results in def.SizeCache + // instead of def.MeasureSize, (b) implements LayoutRounding if requested, + // (c) stores intermediate results differently. + // The LayoutRounding logic is improved: + // 1. Use pre-rounded values during proportional allocation. This avoids the + // same kind of problems arising from interaction with min/max that + // motivated the new algorithm in the first place. + // 2. Use correct "nudge" amount when distributing roundoff space. This + // comes into play at high DPI - greater than 134. + // 3. Applies rounding only to real pixel values (not to ratios) + private void SetFinalSizeMaxDiscrepancy( + DefinitionBase[] definitions, + double finalSize, + bool columns) + { + int defCount = definitions.Length; + int[] definitionIndices = DefinitionIndices; + int minCount = 0, maxCount = 0; + double takenSize = 0.0; + double totalStarWeight = 0.0; + int starCount = 0; // number of unresolved *-definitions + double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" + + // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights + double maxStar = 0.0; + for (int i=0; i maxStar) + { + maxStar = def.UserSize.Value; + } + } + } + + if (Double.IsPositiveInfinity(maxStar)) + { + // negative scale means one or more of the weights was Infinity + scale = -1.0; + } + else if (starCount > 0) + { + // if maxStar * starCount > Double.Max, summing all the weights could cause + // floating-point overflow. To avoid that, scale the weights by a factor to keep + // the sum within limits. Choose a power of 2, to preserve precision. + double power = Math.Floor(Math.Log(Double.MaxValue / maxStar / starCount, 2.0)); + if (power < 0.0) + { + scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia + } + } + + + // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights + // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. + // More explanation below... + for (bool runPhase2and3=true; runPhase2and3; ) + { + // Phase 2. Compute total *-weight W and available space S. + // For *-items that have Min or Max constraints, compute the ratios used to decide + // whether proportional space is too big or too small and add the item to the + // corresponding list. (The "min" list is in the first half of definitionIndices, + // the "max" list in the second half. DefinitionIndices has capacity at least + // 2*defCount, so there's room for both lists.) + totalStarWeight = 0.0; + takenSize = 0.0; + minCount = maxCount = 0; + + for (int i=0; i 0.0) + { + // store ratio w/min in MeasureSize (for now) + definitionIndices[minCount++] = i; + def.MeasureSize = starWeight / def.MinSizeForArrange; + } + + double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize); + if (!Double.IsPositiveInfinity(effectiveMaxSize)) + { + // store ratio w/max in SizeCache (for now) + definitionIndices[defCount + maxCount++] = i; + def.SizeCache = starWeight / effectiveMaxSize; + } + } + } + else + { + double userSize = 0; + + switch (def.UserSize.GridUnitType) + { + case (GridUnitType.Pixel): + userSize = def.UserSize.Value; + break; + + case (GridUnitType.Auto): + userSize = def.MinSizeForArrange; + break; + } + + double userMaxSize; + + if (def.IsShared) + { + // overriding userMaxSize effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + userMaxSize = userSize; + } + else + { + userMaxSize = def.UserMaxSize; + } + + def.SizeCache = Math.Max(def.MinSizeForArrange, Math.Min(userSize, userMaxSize)); + takenSize += def.SizeCache; + } + } + + // Phase 3. Resolve *-items whose proportional sizes are too big or too small. + int minCountPhase2 = minCount, maxCountPhase2 = maxCount; + double takenStarWeight = 0.0; + double remainingAvailableSize = finalSize - takenSize; + double remainingStarWeight = totalStarWeight - takenStarWeight; + + MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions); + Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer); + MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions); + Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer); + + while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) + { + // the calculation + // remainingStarWeight = totalStarWeight - takenStarWeight + // is subject to catastrophic cancellation if the two terms are nearly equal, + // which leads to meaningless results. Check for that, and recompute from + // the remaining definitions. [This leads to quadratic behavior in really + // pathological cases - but they'd never arise in practice.] + const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate + if (remainingStarWeight < totalStarWeight * starFactor) + { + takenStarWeight = 0.0; + totalStarWeight = 0.0; + + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + if (def.UserSize.IsStar && def.MeasureSize > 0.0) + { + totalStarWeight += StarWeight(def, scale); + } + } + + remainingStarWeight = totalStarWeight - takenStarWeight; + } + + double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : Double.PositiveInfinity; + double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0; + + // choose the def with larger ratio to the current proportion ("max discrepancy") + double proportion = remainingStarWeight / remainingAvailableSize; + bool? chooseMin = Choose(minRatio, maxRatio, proportion); + + // if no def was chosen, advance to phase 4; the current proportion doesn't + // conflict with any min or max values. + if (!(chooseMin.HasValue)) + { + break; + } + + // get the chosen definition and its resolved size + int resolvedIndex; + DefinitionBase resolvedDef; + double resolvedSize; + if (chooseMin == true) + { + resolvedIndex = definitionIndices[minCount - 1]; + resolvedDef = definitions[resolvedIndex]; + resolvedSize = resolvedDef.MinSizeForArrange; + --minCount; + } + else + { + resolvedIndex = definitionIndices[defCount + maxCount - 1]; + resolvedDef = definitions[resolvedIndex]; + resolvedSize = Math.Max(resolvedDef.MinSizeForArrange, resolvedDef.UserMaxSize); + --maxCount; + } + + // resolve the chosen def, deduct its contributions from W and S. + // Defs resolved in phase 3 are marked by storing the negative of their resolved + // size in MeasureSize, to distinguish them from a pending def. + takenSize += resolvedSize; + resolvedDef.MeasureSize = -resolvedSize; + takenStarWeight += StarWeight(resolvedDef, scale); + --starCount; + + remainingAvailableSize = finalSize - takenSize; + remainingStarWeight = totalStarWeight - takenStarWeight; + + // advance to the next candidate defs, removing ones that have been resolved. + // Both counts are advanced, as a def might appear in both lists. + while (minCount > 0 && definitions[definitionIndices[minCount - 1]].MeasureSize < 0.0) + { + --minCount; + definitionIndices[minCount] = -1; + } + while (maxCount > 0 && definitions[definitionIndices[defCount + maxCount - 1]].MeasureSize < 0.0) + { + --maxCount; + definitionIndices[defCount + maxCount] = -1; + } + } + + // decide whether to run Phase2 and Phase3 again. There are 3 cases: + // 1. There is space available, and *-defs remaining. This is the + // normal case - move on to Phase 4 to allocate the remaining + // space proportionally to the remaining *-defs. + // 2. There is space available, but no *-defs. This implies at least one + // def was resolved as 'max', taking less space than its proportion. + // If there are also 'min' defs, reconsider them - we can give + // them more space. If not, all the *-defs are 'max', so there's + // no way to use all the available space. + // 3. We allocated too much space. This implies at least one def was + // resolved as 'min'. If there are also 'max' defs, reconsider + // them, otherwise the over-allocation is an inevitable consequence + // of the given min constraints. + // Note that if we return to Phase2, at least one *-def will have been + // resolved. This guarantees we don't run Phase2+3 infinitely often. + runPhase2and3 = false; + if (starCount == 0 && takenSize < finalSize) + { + // if no *-defs remain and we haven't allocated all the space, reconsider the defs + // resolved as 'min'. Their allocation can be increased to make up the gap. + for (int i = minCount; i < minCountPhase2; ++i) + { + if (definitionIndices[i] >= 0) + { + DefinitionBase def = definitions[definitionIndices[i]]; + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + + if (takenSize > finalSize) + { + // if we've allocated too much space, reconsider the defs + // resolved as 'max'. Their allocation can be decreased to make up the gap. + for (int i = maxCount; i < maxCountPhase2; ++i) + { + if (definitionIndices[defCount + i] >= 0) + { + DefinitionBase def = definitions[definitionIndices[defCount + i]]; + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + } + + // Phase 4. Resolve the remaining defs proportionally. + starCount = 0; + for (int i=0; i 0) + { + StarWeightIndexComparer starWeightIndexComparer = new StarWeightIndexComparer(definitions); + Array.Sort(definitionIndices, 0, starCount, starWeightIndexComparer); + + // compute the partial sums of *-weight, in increasing order of weight + // for minimal loss of precision. + totalStarWeight = 0.0; + for (int i = 0; i < starCount; ++i) + { + DefinitionBase def = definitions[definitionIndices[i]]; + totalStarWeight += def.MeasureSize; + def.SizeCache = totalStarWeight; + } + + // resolve the defs, in decreasing order of weight. + for (int i = starCount - 1; i >= 0; --i) + { + DefinitionBase def = definitions[definitionIndices[i]]; + double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(finalSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; + + // min and max should have no effect by now, but just in case... + resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); + resolvedSize = Math.Max(def.MinSizeForArrange, resolvedSize); + + // Use the raw (unrounded) sizes to update takenSize, so that + // proportions are computed in the same terms as in phase 3; + // this avoids errors arising from min/max constraints. + takenSize += resolvedSize; + def.SizeCache = resolvedSize; + } + } + + // Phase 5. Apply layout rounding. We do this after fully allocating + // unrounded sizes, to avoid breaking assumptions in the previous phases + if (UseLayoutRounding) + { + DpiScale dpiScale = GetDpi(); + double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; + double[] roundingErrors = RoundingErrors; + double roundedTakenSize = 0.0; + + // round each of the allocated sizes, keeping track of the deltas + for (int i = 0; i < definitions.Length; ++i) + { + DefinitionBase def = definitions[i]; + double roundedSize = UIElement.RoundLayoutValue(def.SizeCache, dpi); + roundingErrors[i] = (roundedSize - def.SizeCache); + def.SizeCache = roundedSize; + roundedTakenSize += roundedSize; + } + + // The total allocation might differ from finalSize due to rounding + // effects. Tweak the allocations accordingly. + + // Theoretical and historical note. The problem at hand - allocating + // space to columns (or rows) with *-weights, min and max constraints, + // and layout rounding - has a long history. Especially the special + // case of 50 columns with min=1 and available space=435 - allocating + // seats in the U.S. House of Representatives to the 50 states in + // proportion to their population. There are numerous algorithms + // and papers dating back to the 1700's, including the book: + // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982. + // + // One surprising result of all this research is that *any* algorithm + // will suffer from one or more undesirable features such as the + // "population paradox" or the "Alabama paradox", where (to use our terminology) + // increasing the available space by one pixel might actually decrease + // the space allocated to a given column, or increasing the weight of + // a column might decrease its allocation. This is worth knowing + // in case someone complains about this behavior; it's not a bug so + // much as something inherent to the problem. Cite the book mentioned + // above or one of the 100s of references, and resolve as WontFix. + // + // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer) + // each being allocated a large number of pixels (~50 or greater), and + // people don't even notice the kind of 1-pixel anomolies that are + // theoretically inevitable, or don't care if they do. At least they shouldn't + // care - no one should be using the results WPF's grid layout to make + // quantitative decisions; its job is to produce a reasonable display, not + // to allocate seats in Congress. + // + // Our algorithm is more susceptible to paradox than the one currently + // used for Congressional allocation ("Huntington-Hill" algorithm), but + // it is faster to run: O(N log N) vs. O(S * N), where N=number of + // definitions, S = number of available pixels. And it produces + // adequate results in practice, as mentioned above. + // + // To reiterate one point: all this only applies when layout rounding + // is in effect. When fractional sizes are allowed, the algorithm + // behaves as well as possible, subject to the min/max constraints + // and precision of floating-point computation. (However, the resulting + // display is subject to anti-aliasing problems. TANSTAAFL.) + + if (!_AreClose(roundedTakenSize, finalSize)) + { + // Compute deltas + for (int i = 0; i < definitions.Length; ++i) + { + definitionIndices[i] = i; + } + + // Sort rounding errors + RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); + Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); + double adjustedSize = roundedTakenSize; + double dpiIncrement = 1.0/dpi; + + if (roundedTakenSize > finalSize) + { + int i = definitions.Length - 1; + while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache - dpiIncrement; + final = Math.Max(final, definition.MinSizeForArrange); + if (final < definition.SizeCache) + { + adjustedSize -= dpiIncrement; + } + definition.SizeCache = final; + i--; + } + } + else if (roundedTakenSize < finalSize) + { + int i = 0; + while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache + dpiIncrement; + final = Math.Max(final, definition.MinSizeForArrange); + if (final > definition.SizeCache) + { + adjustedSize += dpiIncrement; + } + definition.SizeCache = final; + i++; + } + } + } + } + + // Phase 6. Compute final offsets + definitions[0].FinalOffset = 0.0; + for (int i = 0; i < definitions.Length; ++i) + { + definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; + } + } + + /// + /// Choose the ratio with maximum discrepancy from the current proportion. + /// Returns: + /// true if proportion fails a min constraint but not a max, or + /// if the min constraint has higher discrepancy + /// false if proportion fails a max constraint but not a min, or + /// if the max constraint has higher discrepancy + /// null if proportion doesn't fail a min or max constraint + /// The discrepancy is the ratio of the proportion to the max- or min-ratio. + /// When both ratios hit the constraint, minRatio < proportion < maxRatio, + /// and the minRatio has higher discrepancy if + /// (proportion / minRatio) > (maxRatio / proportion) + /// + private static bool? Choose(double minRatio, double maxRatio, double proportion) + { + if (minRatio < proportion) + { + if (maxRatio > proportion) + { + // compare proportion/minRatio : maxRatio/proportion, but + // do it carefully to avoid floating-point overflow or underflow + // and divide-by-0. + double minPower = Math.Floor(Math.Log(minRatio, 2.0)); + double maxPower = Math.Floor(Math.Log(maxRatio, 2.0)); + double f = Math.Pow(2.0, Math.Floor((minPower + maxPower) / 2.0)); + if ((proportion / f) * (proportion / f) > (minRatio / f) * (maxRatio / f)) + { + return true; + } + else + { + return false; + } + } + else + { + return true; + } + } + else if (maxRatio > proportion) + { + return false; + } + + return null; + } + + /// + /// Sorts row/column indices by rounding error if layout rounding is applied. + /// + /// Index, rounding error pair + /// Index, rounding error pair + /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise + private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y) + { + if (x.Value < y.Value) + { + return -1; + } + else if (x.Value > y.Value) + { + return 1; + } + return 0; + } + + /// + /// Calculates final (aka arrange) size for given range. + /// + /// Array of definitions to process. + /// Start of the range. + /// Number of items in the range. + /// Final size. + private double GetFinalSizeForRange( + DefinitionBase[] definitions, + int start, + int count) + { + double size = 0; + int i = start + count - 1; + + do + { + size += definitions[i].SizeCache; + } while (--i >= start); + + return (size); + } + + /// + /// Clears dirty state for the grid and its columns / rows + /// + private void SetValid() + { + ExtendedData extData = ExtData; + if (extData != null) + { +// for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid (); +// for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid (); + + if (extData.TempDefinitions != null) + { + // TempDefinitions has to be cleared to avoid "memory leaks" + Array.Clear(extData.TempDefinitions, 0, Math.Max(DefinitionsU.Length, DefinitionsV.Length)); + extData.TempDefinitions = null; + } + } + } + + /// + /// Returns true if ColumnDefinitions collection is not empty + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeColumnDefinitions() + { + ExtendedData extData = ExtData; + return ( extData != null + && extData.ColumnDefinitions != null + && extData.ColumnDefinitions.Count > 0 ); + } + + /// + /// Returns true if RowDefinitions collection is not empty + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeRowDefinitions() + { + ExtendedData extData = ExtData; + return ( extData != null + && extData.RowDefinitions != null + && extData.RowDefinitions.Count > 0 ); + } + + /// + /// Synchronized ShowGridLines property with the state of the grid's visual collection + /// by adding / removing GridLinesRenderer visual. + /// Returns a reference to GridLinesRenderer visual or null. + /// + private GridLinesRenderer EnsureGridLinesRenderer() + { + // + // synchronize the state + // + if (ShowGridLines && (_gridLinesRenderer == null)) + { + _gridLinesRenderer = new GridLinesRenderer(); + this.AddVisualChild(_gridLinesRenderer); + } + + if ((!ShowGridLines) && (_gridLinesRenderer != null)) + { + this.RemoveVisualChild(_gridLinesRenderer); + _gridLinesRenderer = null; + } + + return (_gridLinesRenderer); + } + + /// + /// SetFlags is used to set or unset one or multiple + /// flags on the object. + /// + private void SetFlags(bool value, Flags flags) + { + _flags = value ? (_flags | flags) : (_flags & (~flags)); + } + + /// + /// CheckFlagsAnd returns true if all the flags in the + /// given bitmask are set on the object. + /// + private bool CheckFlagsAnd(Flags flags) + { + return ((_flags & flags) == flags); + } + + /// + /// CheckFlagsOr returns true if at least one flag in the + /// given bitmask is set. + /// + /// + /// If no bits are set in the given bitmask, the method returns + /// true. + /// + private bool CheckFlagsOr(Flags flags) + { + return (flags == 0 || (_flags & flags) != 0); + } + + /// + /// + /// + private static void OnShowGridLinesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Grid grid = (Grid)d; + + if ( grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway + && grid.ListenToNotifications) + { + grid.InvalidateVisual(); + } + + grid.SetFlags((bool) e.NewValue, Flags.ShowGridLinesPropertyValue); + } + + /// + /// + /// + private static void OnCellAttachedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Visual child = d as Visual; + + if (child != null) + { + Grid grid = VisualTreeHelper.GetParent(child) as Grid; + if ( grid != null + && grid.ExtData != null + && grid.ListenToNotifications ) + { + grid.CellsStructureDirty = true; + grid.InvalidateMeasure(); + } + } + } + + /// + /// + /// + private static bool IsIntValueNotNegative(object value) + { + return ((int)value >= 0); + } + + /// + /// + /// + private static bool IsIntValueGreaterThanZero(object value) + { + return ((int)value > 0); + } + + /// + /// Helper for Comparer methods. + /// + /// + /// true iff one or both of x and y are null, in which case result holds + /// the relative sort order. + /// + private static bool CompareNullRefs(object x, object y, out int result) + { + result = 2; + + if (x == null) + { + if (y == null) + { + result = 0; + } + else + { + result = -1; + } + } + else + { + if (y == null) + { + result = 1; + } + } + + return (result != 2); + } + + #endregion Private Methods + + //------------------------------------------------------ + // + // Private Properties + // + //------------------------------------------------------ + + #region Private Properties + + /// + /// Private version returning array of column definitions. + /// + private DefinitionBase[] DefinitionsU + { + get { return (ExtData.DefinitionsU); } + } + + /// + /// Private version returning array of row definitions. + /// + private DefinitionBase[] DefinitionsV + { + get { return (ExtData.DefinitionsV); } + } + + /// + /// Helper accessor to layout time array of definitions. + /// + private DefinitionBase[] TempDefinitions + { + get + { + ExtendedData extData = ExtData; + int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length) * 2; + + if ( extData.TempDefinitions == null + || extData.TempDefinitions.Length < requiredLength ) + { + WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(s_tempDefinitionsDataSlot); + if (tempDefinitionsWeakRef == null) + { + extData.TempDefinitions = new DefinitionBase[requiredLength]; + Thread.SetData(s_tempDefinitionsDataSlot, new WeakReference(extData.TempDefinitions)); + } + else + { + extData.TempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; + if ( extData.TempDefinitions == null + || extData.TempDefinitions.Length < requiredLength ) + { + extData.TempDefinitions = new DefinitionBase[requiredLength]; + tempDefinitionsWeakRef.Target = extData.TempDefinitions; + } + } + } + return (extData.TempDefinitions); + } + } + + /// + /// Helper accessor to definition indices. + /// + private int[] DefinitionIndices + { + get + { + int requiredLength = Math.Max(Math.Max(DefinitionsU.Length, DefinitionsV.Length), 1) * 2; + + if (_definitionIndices == null || _definitionIndices.Length < requiredLength) + { + _definitionIndices = new int[requiredLength]; + } + + return _definitionIndices; + } + } + + /// + /// Helper accessor to rounding errors. + /// + private double[] RoundingErrors + { + get + { + int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length); + + if (_roundingErrors == null && requiredLength == 0) + { + _roundingErrors = new double[1]; + } + else if (_roundingErrors == null || _roundingErrors.Length < requiredLength) + { + _roundingErrors = new double[requiredLength]; + } + return _roundingErrors; + } + } + + /// + /// Private version returning array of cells. + /// + private CellCache[] PrivateCells + { + get { return (ExtData.CellCachesCollection); } + } + + /// + /// Convenience accessor to ValidCellsStructure bit flag. + /// + private bool CellsStructureDirty + { + get { return (!CheckFlagsAnd(Flags.ValidCellsStructure)); } + set { SetFlags(!value, Flags.ValidCellsStructure); } + } + + /// + /// Convenience accessor to ListenToNotifications bit flag. + /// + private bool ListenToNotifications + { + get { return (CheckFlagsAnd(Flags.ListenToNotifications)); } + set { SetFlags(value, Flags.ListenToNotifications); } + } + + /// + /// Convenience accessor to SizeToContentU bit flag. + /// + private bool SizeToContentU + { + get { return (CheckFlagsAnd(Flags.SizeToContentU)); } + set { SetFlags(value, Flags.SizeToContentU); } + } + + /// + /// Convenience accessor to SizeToContentV bit flag. + /// + private bool SizeToContentV + { + get { return (CheckFlagsAnd(Flags.SizeToContentV)); } + set { SetFlags(value, Flags.SizeToContentV); } + } + + /// + /// Convenience accessor to HasStarCellsU bit flag. + /// + private bool HasStarCellsU + { + get { return (CheckFlagsAnd(Flags.HasStarCellsU)); } + set { SetFlags(value, Flags.HasStarCellsU); } + } + + /// + /// Convenience accessor to HasStarCellsV bit flag. + /// + private bool HasStarCellsV + { + get { return (CheckFlagsAnd(Flags.HasStarCellsV)); } + set { SetFlags(value, Flags.HasStarCellsV); } + } + + /// + /// Convenience accessor to HasGroup3CellsInAutoRows bit flag. + /// + private bool HasGroup3CellsInAutoRows + { + get { return (CheckFlagsAnd(Flags.HasGroup3CellsInAutoRows)); } + set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } + } + + /// + /// fp version of d == 0. + /// + /// Value to check. + /// true if d == 0. + private static bool _IsZero(double d) + { + return (Math.Abs(d) < c_epsilon); + } + + /// + /// fp version of d1 == d2 + /// + /// First value to compare + /// Second value to compare + /// true if d1 == d2 + private static bool _AreClose(double d1, double d2) + { + return (Math.Abs(d1 - d2) < c_epsilon); + } + + /// + /// Returns reference to extended data bag. + /// + private ExtendedData ExtData + { + get { return (_data); } + } + + /// + /// Returns *-weight, adjusted for scale computed during Phase 1 + /// + static double StarWeight(DefinitionBase def, double scale) + { + if (scale < 0.0) + { + // if one of the *-weights is Infinity, adjust the weights by mapping + // Infinty to 1.0 and everything else to 0.0: the infinite items share the + // available space equally, everyone else gets nothing. + return (Double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0; + } + else + { + return def.UserSize.Value * scale; + } + } + + #endregion Private Properties + + //------------------------------------------------------ + // + // Private Fields + // + //------------------------------------------------------ + + #region Private Fields + private ExtendedData _data; // extended data instantiated on demand, for non-trivial case handling only + private Flags _flags; // grid validity / property caches dirtiness flags + private GridLinesRenderer _gridLinesRenderer; + + // Keeps track of definition indices. + int[] _definitionIndices; + + // Stores unrounded values and rounding errors during layout rounding. + double[] _roundingErrors; + + #endregion Private Fields + + //------------------------------------------------------ + // + // Static Fields + // + //------------------------------------------------------ + + #region Static Fields + private const double c_epsilon = 1e-5; // used in fp calculations + private const double c_starClip = 1e298; // used as maximum for clipping star values during normalization + private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop + private static readonly LocalDataStoreSlot s_tempDefinitionsDataSlot = Thread.AllocateDataSlot(); + private static readonly IComparer s_spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); + private static readonly IComparer s_spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); + private static readonly IComparer s_starDistributionOrderComparer = new StarDistributionOrderComparer(); + private static readonly IComparer s_distributionOrderComparer = new DistributionOrderComparer(); + private static readonly IComparer s_minRatioComparer = new MinRatioComparer(); + private static readonly IComparer s_maxRatioComparer = new MaxRatioComparer(); + private static readonly IComparer s_starWeightComparer = new StarWeightComparer(); + + #endregion Static Fields + + //------------------------------------------------------ + // + // Private Structures / Classes + // + //------------------------------------------------------ + + #region Private Structures Classes + + /// + /// Extended data instantiated on demand, when grid handles non-trivial case. + /// + private class ExtendedData + { + internal ColumnDefinitionCollection ColumnDefinitions; // collection of column definitions (logical tree support) + internal RowDefinitionCollection RowDefinitions; // collection of row definitions (logical tree support) + internal DefinitionBase[] DefinitionsU; // collection of column definitions used during calc + internal DefinitionBase[] DefinitionsV; // collection of row definitions used during calc + internal CellCache[] CellCachesCollection; // backing store for logical children + internal int CellGroup1; // index of the first cell in first cell group + internal int CellGroup2; // index of the first cell in second cell group + internal int CellGroup3; // index of the first cell in third cell group + internal int CellGroup4; // index of the first cell in forth cell group + internal DefinitionBase[] TempDefinitions; // temporary array used during layout for various purposes + // TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length) + } + + /// + /// Grid validity / property caches dirtiness flags + /// + [System.Flags] + private enum Flags + { + // + // the foolowing flags let grid tracking dirtiness in more granular manner: + // * Valid???Structure flags indicate that elements were added or removed. + // * Valid???Layout flags indicate that layout time portion of the information + // stored on the objects should be updated. + // + ValidDefinitionsUStructure = 0x00000001, + ValidDefinitionsVStructure = 0x00000002, + ValidCellsStructure = 0x00000004, + + // + // boolean properties state + // + ShowGridLinesPropertyValue = 0x00000100, // show grid lines ? + + // + // boolean flags + // + ListenToNotifications = 0x00001000, // "0" when all notifications are ignored + SizeToContentU = 0x00002000, // "1" if calculating to content in U direction + SizeToContentV = 0x00004000, // "1" if calculating to content in V direction + HasStarCellsU = 0x00008000, // "1" if at least one cell belongs to a Star column + HasStarCellsV = 0x00010000, // "1" if at least one cell belongs to a Star row + HasGroup3CellsInAutoRows = 0x00020000, // "1" if at least one cell of group 3 belongs to an Auto row + MeasureOverrideInProgress = 0x00040000, // "1" while in the context of Grid.MeasureOverride + ArrangeOverrideInProgress = 0x00080000, // "1" while in the context of Grid.ArrangeOverride + } + + #endregion Private Structures Classes + + //------------------------------------------------------ + // + // Properties + // + //------------------------------------------------------ + + #region Properties + + /// + /// ShowGridLines property. This property is used mostly + /// for simplification of visual debuggig. When it is set + /// to true grid lines are drawn to visualize location + /// of grid lines. + /// + public static readonly DependencyProperty ShowGridLinesProperty = + DependencyProperty.Register( + "ShowGridLines", + typeof(bool), + typeof(Grid), + new FrameworkPropertyMetadata( + false, + new PropertyChangedCallback(OnShowGridLinesPropertyChanged))); + + /// + /// Column property. This is an attached property. + /// Grid defines Column property, so that it can be set + /// on any element treated as a cell. Column property + /// specifies child's position with respect to columns. + /// + /// + /// Columns are 0 - based. In order to appear in first column, element + /// should have Column property set to 0. + /// Default value for the property is 0. + /// + [CommonDependencyProperty] + public static readonly DependencyProperty ColumnProperty = + DependencyProperty.RegisterAttached( + "Column", + typeof(int), + typeof(Grid), + new FrameworkPropertyMetadata( + 0, + new PropertyChangedCallback(OnCellAttachedPropertyChanged)), + new ValidateValueCallback(IsIntValueNotNegative)); + + /// + /// Row property. This is an attached property. + /// Grid defines Row, so that it can be set + /// on any element treated as a cell. Row property + /// specifies child's position with respect to rows. + /// + /// Rows are 0 - based. In order to appear in first row, element + /// should have Row property set to 0. + /// Default value for the property is 0. + /// + /// + [CommonDependencyProperty] + public static readonly DependencyProperty RowProperty = + DependencyProperty.RegisterAttached( + "Row", + typeof(int), + typeof(Grid), + new FrameworkPropertyMetadata( + 0, + new PropertyChangedCallback(OnCellAttachedPropertyChanged)), + new ValidateValueCallback(IsIntValueNotNegative)); + + /// + /// ColumnSpan property. This is an attached property. + /// Grid defines ColumnSpan, so that it can be set + /// on any element treated as a cell. ColumnSpan property + /// specifies child's width with respect to columns. + /// Example, ColumnSpan == 2 means that child will span across two columns. + /// + /// + /// Default value for the property is 1. + /// + [CommonDependencyProperty] + public static readonly DependencyProperty ColumnSpanProperty = + DependencyProperty.RegisterAttached( + "ColumnSpan", + typeof(int), + typeof(Grid), + new FrameworkPropertyMetadata( + 1, + new PropertyChangedCallback(OnCellAttachedPropertyChanged)), + new ValidateValueCallback(IsIntValueGreaterThanZero)); + + /// + /// RowSpan property. This is an attached property. + /// Grid defines RowSpan, so that it can be set + /// on any element treated as a cell. RowSpan property + /// specifies child's height with respect to row grid lines. + /// Example, RowSpan == 3 means that child will span across three rows. + /// + /// + /// Default value for the property is 1. + /// + [CommonDependencyProperty] + public static readonly DependencyProperty RowSpanProperty = + DependencyProperty.RegisterAttached( + "RowSpan", + typeof(int), + typeof(Grid), + new FrameworkPropertyMetadata( + 1, + new PropertyChangedCallback(OnCellAttachedPropertyChanged)), + new ValidateValueCallback(IsIntValueGreaterThanZero)); + + + /// + /// IsSharedSizeScope property marks scoping element for shared size. + /// + public static readonly DependencyProperty IsSharedSizeScopeProperty = + DependencyProperty.RegisterAttached( + "IsSharedSizeScope", + typeof(bool), + typeof(Grid), + new FrameworkPropertyMetadata( + false, + new PropertyChangedCallback(DefinitionBase.OnIsSharedSizeScopePropertyChanged))); + + #endregion Properties + + //------------------------------------------------------ + // + // Internal Structures / Classes + // + //------------------------------------------------------ + + #region Internal Structures Classes + + /// + /// LayoutTimeSizeType is used internally and reflects layout-time size type. + /// + [System.Flags] + internal enum LayoutTimeSizeType : byte + { + None = 0x00, + Pixel = 0x01, + Auto = 0x02, + Star = 0x04, + } + + #endregion Internal Structures Classes + + //------------------------------------------------------ + // + // Private Structures / Classes + // + //------------------------------------------------------ + + #region Private Structures Classes + + /// + /// CellCache stored calculated values of + /// 1. attached cell positioning properties; + /// 2. size type; + /// 3. index of a next cell in the group; + /// + private struct CellCache + { + internal int ColumnIndex; + internal int RowIndex; + internal int ColumnSpan; + internal int RowSpan; + internal LayoutTimeSizeType SizeTypeU; + internal LayoutTimeSizeType SizeTypeV; + internal int Next; + internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } } + internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } } + internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } } + internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } } + } + + /// + /// Helper class for representing a key for a span in hashtable. + /// + private class SpanKey + { + /// + /// Constructor. + /// + /// Starting index of the span. + /// Span count. + /// true for columns; false for rows. + internal SpanKey(int start, int count, bool u) + { + _start = start; + _count = count; + _u = u; + } + + /// + /// + /// + public override int GetHashCode() + { + int hash = (_start ^ (_count << 2)); + + if (_u) hash &= 0x7ffffff; + else hash |= 0x8000000; + + return (hash); + } + + /// + /// + /// + public override bool Equals(object obj) + { + SpanKey sk = obj as SpanKey; + return ( sk != null + && sk._start == _start + && sk._count == _count + && sk._u == _u ); + } + + /// + /// Returns start index of the span. + /// + internal int Start { get { return (_start); } } + + /// + /// Returns span count. + /// + internal int Count { get { return (_count); } } + + /// + /// Returns true if this is a column span. + /// false if this is a row span. + /// + internal bool U { get { return (_u); } } + + private int _start; + private int _count; + private bool _u; + } + + /// + /// SpanPreferredDistributionOrderComparer. + /// + private class SpanPreferredDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + if (definitionX.UserSize.IsAuto) + { + if (definitionY.UserSize.IsAuto) + { + result = definitionX.MinSize.CompareTo(definitionY.MinSize); + } + else + { + result = -1; + } + } + else + { + if (definitionY.UserSize.IsAuto) + { + result = +1; + } + else + { + result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize); + } + } + } + + return result; + } + } + + /// + /// SpanMaxDistributionOrderComparer. + /// + private class SpanMaxDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + if (definitionX.UserSize.IsAuto) + { + if (definitionY.UserSize.IsAuto) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + else + { + result = +1; + } + } + else + { + if (definitionY.UserSize.IsAuto) + { + result = -1; + } + else + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + } + } + + return result; + } + } + + /// + /// StarDistributionOrderComparer. + /// + private class StarDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// DistributionOrderComparer. + /// + private class DistributionOrderComparer: IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + double xprime = definitionX.SizeCache - definitionX.MinSizeForArrange; + double yprime = definitionY.SizeCache - definitionY.MinSizeForArrange; + result = xprime.CompareTo(yprime); + } + + return result; + } + } + + + /// + /// StarDistributionOrderIndexComparer. + /// + private class StarDistributionOrderIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal StarDistributionOrderIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// DistributionOrderComparer. + /// + private class DistributionOrderIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal DistributionOrderIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + double xprime = definitionX.SizeCache - definitionX.MinSizeForArrange; + double yprime = definitionY.SizeCache - definitionY.MinSizeForArrange; + result = xprime.CompareTo(yprime); + } + + return result; + } + } + + /// + /// RoundingErrorIndexComparer. + /// + private class RoundingErrorIndexComparer : IComparer + { + private readonly double[] errors; + + internal RoundingErrorIndexComparer(double[] errors) + { + Invariant.Assert(errors != null); + this.errors = errors; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + int result; + + if (!CompareNullRefs(indexX, indexY, out result)) + { + double errorX = errors[indexX.Value]; + double errorY = errors[indexY.Value]; + result = errorX.CompareTo(errorY); + } + + return result; + } + } + + /// + /// MinRatioComparer. + /// Sort by w/min (stored in MeasureSize), descending. + /// We query the list from the back, i.e. in ascending order of w/min. + /// + private class MinRatioComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionY, definitionX, out result)) + { + result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); + } + + return result; + } + } + + /// + /// MaxRatioComparer. + /// Sort by w/max (stored in SizeCache), ascending. + /// We query the list from the back, i.e. in descending order of w/max. + /// + private class MaxRatioComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// StarWeightComparer. + /// Sort by *-weight (stored in MeasureSize), ascending. + /// + private class StarWeightComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); + } + + return result; + } + } + + /// + /// MinRatioIndexComparer. + /// + private class MinRatioIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal MinRatioIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionY, definitionX, out result)) + { + result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); + } + + return result; + } + } + + /// + /// MaxRatioIndexComparer. + /// + private class MaxRatioIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal MaxRatioIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// MaxRatioIndexComparer. + /// + private class StarWeightIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal StarWeightIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); + } + + return result; + } + } + + /// + /// Implementation of a simple enumerator of grid's logical children + /// + private class GridChildrenCollectionEnumeratorSimple : IEnumerator + { + internal GridChildrenCollectionEnumeratorSimple(Grid grid, bool includeChildren) + { + Debug.Assert(grid != null); + _currentEnumerator = -1; + _enumerator0 = new ColumnDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.ColumnDefinitions : null); + _enumerator1 = new RowDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.RowDefinitions : null); + // GridLineRenderer is NOT included into this enumerator. + _enumerator2Index = 0; + if (includeChildren) + { + _enumerator2Collection = grid.Children; + _enumerator2Count = _enumerator2Collection.Count; + } + else + { + _enumerator2Collection = null; + _enumerator2Count = 0; + } + } + + public bool MoveNext() + { + while (_currentEnumerator < 3) + { + if (_currentEnumerator >= 0) + { + switch (_currentEnumerator) + { + case (0): if (_enumerator0.MoveNext()) { _currentChild = _enumerator0.Current; return (true); } break; + case (1): if (_enumerator1.MoveNext()) { _currentChild = _enumerator1.Current; return (true); } break; + case (2): if (_enumerator2Index < _enumerator2Count) + { + _currentChild = _enumerator2Collection[_enumerator2Index]; + _enumerator2Index++; + return (true); + } + break; + } + } + _currentEnumerator++; + } + return (false); + } + + public Object Current + { + get + { + if (_currentEnumerator == -1) + { + #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception + throw new InvalidOperationException(SR.Get(SRID.EnumeratorNotStarted)); + } + if (_currentEnumerator >= 3) + { + #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception + throw new InvalidOperationException(SR.Get(SRID.EnumeratorReachedEnd)); + } + + // assert below is not true anymore since UIElementCollection allowes for null children + //Debug.Assert(_currentChild != null); + return (_currentChild); + } + } + + public void Reset() + { + _currentEnumerator = -1; + _currentChild = null; + _enumerator0.Reset(); + _enumerator1.Reset(); + _enumerator2Index = 0; + } + + private int _currentEnumerator; + private Object _currentChild; + private ColumnDefinitionCollection.Enumerator _enumerator0; + private RowDefinitionCollection.Enumerator _enumerator1; + private UIElementCollection _enumerator2Collection; + private int _enumerator2Index; + private int _enumerator2Count; + } + + /// + /// Helper to render grid lines. + /// + internal class GridLinesRenderer : DrawingVisual + { + /// + /// Static initialization + /// + static GridLinesRenderer() + { + s_oddDashPen = new Pen(Brushes.Blue, c_penWidth); + DoubleCollection oddDashArray = new DoubleCollection(); + oddDashArray.Add(c_dashLength); + oddDashArray.Add(c_dashLength); + s_oddDashPen.DashStyle = new DashStyle(oddDashArray, 0); + s_oddDashPen.DashCap = PenLineCap.Flat; + s_oddDashPen.Freeze(); + + s_evenDashPen = new Pen(Brushes.Yellow, c_penWidth); + DoubleCollection evenDashArray = new DoubleCollection(); + evenDashArray.Add(c_dashLength); + evenDashArray.Add(c_dashLength); + s_evenDashPen.DashStyle = new DashStyle(evenDashArray, c_dashLength); + s_evenDashPen.DashCap = PenLineCap.Flat; + s_evenDashPen.Freeze(); + } + + /// + /// UpdateRenderBounds. + /// + /// Size of render bounds + internal void UpdateRenderBounds(Size boundsSize) + { + using (DrawingContext drawingContext = RenderOpen()) + { + Grid grid = VisualTreeHelper.GetParent(this) as Grid; + if ( grid == null + || grid.ShowGridLines == false ) + { + return; + } + + for (int i = 1; i < grid.DefinitionsU.Length; ++i) + { + DrawGridLine( + drawingContext, + grid.DefinitionsU[i].FinalOffset, 0.0, + grid.DefinitionsU[i].FinalOffset, boundsSize.Height); + } + + for (int i = 1; i < grid.DefinitionsV.Length; ++i) + { + DrawGridLine( + drawingContext, + 0.0, grid.DefinitionsV[i].FinalOffset, + boundsSize.Width, grid.DefinitionsV[i].FinalOffset); + } + } + } + + /// + /// Draw single hi-contrast line. + /// + private static void DrawGridLine( + DrawingContext drawingContext, + double startX, + double startY, + double endX, + double endY) + { + Point start = new Point(startX, startY); + Point end = new Point(endX, endY); + drawingContext.DrawLine(s_oddDashPen, start, end); + drawingContext.DrawLine(s_evenDashPen, start, end); + } + + private const double c_dashLength = 4.0; // + private const double c_penWidth = 1.0; // + private static readonly Pen s_oddDashPen; // first pen to draw dash + private static readonly Pen s_evenDashPen; // second pen to draw dash + private static readonly Point c_zeroPoint = new Point(0, 0); + } + + #endregion Private Structures Classes + + //------------------------------------------------------ + // + // Extended debugging for grid + // + //------------------------------------------------------ + +#if GRIDPARANOIA + private static double _performanceFrequency; + private static readonly bool _performanceFrequencyInitialized = InitializePerformanceFrequency(); + + //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")] + private static extern bool QueryPerformanceCounter(out long lpPerformanceCount); + + //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")] + private static extern bool QueryPerformanceFrequency(out long lpFrequency); + + private static double CostInMilliseconds(long count) + { + return ((double)count / _performanceFrequency); + } + + private static long Cost(long startCount, long endCount) + { + long l = endCount - startCount; + if (l < 0) { l += long.MaxValue; } + return (l); + } + + private static bool InitializePerformanceFrequency() + { + long l; + QueryPerformanceFrequency(out l); + _performanceFrequency = (double)l * 0.001; + return (true); + } + + private struct Counter + { + internal long Start; + internal long Total; + internal int Calls; + } + + private Counter[] _counters; + private bool _hasNewCounterInfo; +#endif // GRIDPARANOIA + + // + // This property + // 1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject + // 2. This is a performance optimization + // + internal override int EffectiveValuesInitialSize + { + get { return 9; } + } + + [Conditional("GRIDPARANOIA")] + internal void EnterCounterScope(Counters scopeCounter) + { + #if GRIDPARANOIA + if (ID == "CountThis") + { + if (_counters == null) + { + _counters = new Counter[(int)Counters.Count]; + } + ExitCounterScope(Counters.Default); + EnterCounter(scopeCounter); + } + else + { + _counters = null; + } + #endif // GRIDPARANOIA + } + + [Conditional("GRIDPARANOIA")] + internal void ExitCounterScope(Counters scopeCounter) + { + #if GRIDPARANOIA + if (_counters != null) + { + if (scopeCounter != Counters.Default) + { + ExitCounter(scopeCounter); + } + + if (_hasNewCounterInfo) + { + string NFormat = "F6"; + Console.WriteLine( + "\ncounter name | total t (ms) | # of calls | per call t (ms)" + + "\n----------------------+---------------+---------------+----------------------" ); + + for (int i = 0; i < _counters.Length; ++i) + { + if (_counters[i].Calls > 0) + { + Counters counter = (Counters)i; + double total = CostInMilliseconds(_counters[i].Total); + double single = total / _counters[i].Calls; + string counterName = counter.ToString(); + string separator; + + if (counterName.Length < 8) { separator = "\t\t\t"; } + else if (counterName.Length < 16) { separator = "\t\t"; } + else { separator = "\t"; } + + Console.WriteLine( + counter.ToString() + separator + + total.ToString(NFormat) + "\t" + + _counters[i].Calls + "\t\t" + + single.ToString(NFormat)); + + _counters[i] = new Counter(); + } + } + } + _hasNewCounterInfo = false; + } + #endif // GRIDPARANOIA + } + + [Conditional("GRIDPARANOIA")] + internal void EnterCounter(Counters counter) + { + #if GRIDPARANOIA + if (_counters != null) + { + Debug.Assert((int)counter < _counters.Length); + + int i = (int)counter; + QueryPerformanceCounter(out _counters[i].Start); + } + #endif // GRIDPARANOIA + } + + [Conditional("GRIDPARANOIA")] + internal void ExitCounter(Counters counter) + { + #if GRIDPARANOIA + if (_counters != null) + { + Debug.Assert((int)counter < _counters.Length); + + int i = (int)counter; + long l; + QueryPerformanceCounter(out l); + l = Cost(_counters[i].Start, l); + _counters[i].Total += l; + _counters[i].Calls++; + _hasNewCounterInfo = true; + } + #endif // GRIDPARANOIA + } + + internal enum Counters : int + { + Default = -1, + + MeasureOverride, + _ValidateColsStructure, + _ValidateRowsStructure, + _ValidateCells, + _MeasureCell, + __MeasureChild, + _CalculateDesiredSize, + + ArrangeOverride, + _SetFinalSize, + _ArrangeChildHelper2, + _PositionCell, + + Count, + } + } +} \ No newline at end of file From 7767319ce4c77178ef02cd30bcd2d376652979f9 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 23 May 2019 13:13:41 +0800 Subject: [PATCH 015/130] Porting part 1 of n --- src/Avalonia.Controls/GridWPF.cs | 593 +++++++++---------------------- 1 file changed, 168 insertions(+), 425 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 3a4b41314f..ff006a5c1b 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -10,96 +10,20 @@ using System.Runtime.CompilerServices; using Avalonia.Collections; using Avalonia.Controls.Utils; using Avalonia.VisualTree; +using System.Threading; using JetBrains.Annotations; - - -using MS.Internal; -using MS.Internal.Controls; -using MS.Internal.PresentationFramework; -using MS.Internal.Telemetry.PresentationFramework; -using MS.Utility; - -using System; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia; using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Windows.Threading; -using System.Threading; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; -using System.Windows.Media; -using System.Windows.Markup; - -#pragma warning disable 1634, 1691 // suppressing PreSharp warnings namespace System.Windows.Controls { /// /// Grid /// - public class Grid : Panel, IAddChild + public class Grid : Panel { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - static Grid() - { - ControlsTraceLogger.AddControl(TelemetryControls.Grid); - } - - /// - /// Default constructor. - /// - public Grid() - { - SetFlags((bool) ShowGridLinesProperty.GetDefaultValue(DependencyObjectType), Flags.ShowGridLinesPropertyValue); - } - - #endregion Constructors - - //------------------------------------------------------ - // - // Public Methods - // - //------------------------------------------------------ - - #region Public Methods - - /// - /// - /// - void IAddChild.AddChild(object value) - { - if (value == null) - { - throw new ArgumentNullException("value"); - } - - UIElement cell = value as UIElement; - if (cell != null) - { - Children.Add(cell); - return; - } - - throw (new ArgumentException(SR.Get(SRID.Grid_UnexpectedParameterType, value.GetType(), typeof(UIElement)), "value")); - } - - /// - /// - /// - void IAddChild.AddText(string text) - { - XamlSerializerUtil.ThrowIfNonWhiteSpaceInAddText(text, this); - } - /// /// /// @@ -108,16 +32,16 @@ namespace System.Windows.Controls get { // empty panel or a panel being used as the items - // host has *no* logical children; give empty enumerator + // host has *no* logical Children; give empty enumerator bool noChildren = (base.VisualChildrenCount == 0) || IsItemsHost; if (noChildren) { ExtendedData extData = ExtData; - if ( extData == null - || ( (extData.ColumnDefinitions == null || extData.ColumnDefinitions.Count == 0) - && (extData.RowDefinitions == null || extData.RowDefinitions.Count == 0) ) + if (extData == null + || ((extData.ColumnDefinitions == null || extData.ColumnDefinitions.Count == 0) + && (extData.RowDefinitions == null || extData.RowDefinitions.Count == 0)) ) { // grid is empty @@ -130,11 +54,11 @@ namespace System.Windows.Controls } /// - /// Helper for setting Column property on a UIElement. + /// Helper for setting Column property on a Control. /// - /// UIElement to set Column property on. + /// Control to set Column property on. /// Column property value. - public static void SetColumn(UIElement element, int value) + public static void SetColumn(Control element, int value) { if (element == null) { @@ -145,12 +69,11 @@ namespace System.Windows.Controls } /// - /// Helper for reading Column property from a UIElement. + /// Helper for reading Column property from a Control. /// - /// UIElement to read Column property from. + /// Control to read Column property from. /// Column property value. - [AttachedPropertyBrowsableForChildren()] - public static int GetColumn(UIElement element) + public static int GetColumn(Control element) { if (element == null) { @@ -161,11 +84,11 @@ namespace System.Windows.Controls } /// - /// Helper for setting Row property on a UIElement. + /// Helper for setting Row property on a Control. /// - /// UIElement to set Row property on. + /// Control to set Row property on. /// Row property value. - public static void SetRow(UIElement element, int value) + public static void SetRow(Control element, int value) { if (element == null) { @@ -176,12 +99,12 @@ namespace System.Windows.Controls } /// - /// Helper for reading Row property from a UIElement. + /// Helper for reading Row property from a Control. /// - /// UIElement to read Row property from. + /// Control to read Row property from. /// Row property value. [AttachedPropertyBrowsableForChildren()] - public static int GetRow(UIElement element) + public static int GetRow(Control element) { if (element == null) { @@ -192,11 +115,11 @@ namespace System.Windows.Controls } /// - /// Helper for setting ColumnSpan property on a UIElement. + /// Helper for setting ColumnSpan property on a Control. /// - /// UIElement to set ColumnSpan property on. + /// Control to set ColumnSpan property on. /// ColumnSpan property value. - public static void SetColumnSpan(UIElement element, int value) + public static void SetColumnSpan(Control element, int value) { if (element == null) { @@ -207,12 +130,12 @@ namespace System.Windows.Controls } /// - /// Helper for reading ColumnSpan property from a UIElement. + /// Helper for reading ColumnSpan property from a Control. /// - /// UIElement to read ColumnSpan property from. + /// Control to read ColumnSpan property from. /// ColumnSpan property value. [AttachedPropertyBrowsableForChildren()] - public static int GetColumnSpan(UIElement element) + public static int GetColumnSpan(Control element) { if (element == null) { @@ -223,11 +146,11 @@ namespace System.Windows.Controls } /// - /// Helper for setting RowSpan property on a UIElement. + /// Helper for setting RowSpan property on a Control. /// - /// UIElement to set RowSpan property on. + /// Control to set RowSpan property on. /// RowSpan property value. - public static void SetRowSpan(UIElement element, int value) + public static void SetRowSpan(Control element, int value) { if (element == null) { @@ -238,12 +161,11 @@ namespace System.Windows.Controls } /// - /// Helper for reading RowSpan property from a UIElement. + /// Helper for reading RowSpan property from a Control. /// - /// UIElement to read RowSpan property from. + /// Control to read RowSpan property from. /// RowSpan property value. - [AttachedPropertyBrowsableForChildren()] - public static int GetRowSpan(UIElement element) + public static int GetRowSpan(Control element) { if (element == null) { @@ -254,11 +176,11 @@ namespace System.Windows.Controls } /// - /// Helper for setting IsSharedSizeScope property on a UIElement. + /// Helper for setting IsSharedSizeScope property on a Control. /// - /// UIElement to set IsSharedSizeScope property on. + /// Control to set IsSharedSizeScope property on. /// IsSharedSizeScope property value. - public static void SetIsSharedSizeScope(UIElement element, bool value) + public static void SetIsSharedSizeScope(Control element, bool value) { if (element == null) { @@ -269,11 +191,11 @@ namespace System.Windows.Controls } /// - /// Helper for reading IsSharedSizeScope property from a UIElement. + /// Helper for reading IsSharedSizeScope property from a Control. /// - /// UIElement to read IsSharedSizeScope property from. + /// Control to read IsSharedSizeScope property from. /// IsSharedSizeScope property value. - public static bool GetIsSharedSizeScope(UIElement element) + public static bool GetIsSharedSizeScope(Control element) { if (element == null) { @@ -283,7 +205,6 @@ namespace System.Windows.Controls return ((bool)element.GetValue(IsSharedSizeScopeProperty)); } - #endregion Public Methods //------------------------------------------------------ // @@ -343,10 +264,10 @@ namespace System.Windows.Controls #region Protected Methods /// - /// Derived class must implement to support Visual children. The method must return + /// Derived class must implement to support Visual Children. The method must return /// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1. /// - /// By default a Visual does not have any children. + /// By default a Visual does not have any Children. /// /// Remark: /// During this virtual call it is not valid to modify the Visual tree. @@ -355,7 +276,7 @@ namespace System.Windows.Controls { // because "base.Count + 1" for GridLinesRenderer // argument checking done at the base class - if(index == base.VisualChildrenCount) + if (index == base.VisualChildrenCount) { if (_gridLinesRenderer == null) { @@ -368,10 +289,10 @@ namespace System.Windows.Controls /// /// Derived classes override this property to enable the Visual code to enumerate - /// the Visual children. Derived classes need to return the number of children + /// the Visual Children. Derived classes need to return the number of Children /// from this method. /// - /// By default a Visual does not have any children. + /// By default a Visual does not have any Children. /// /// Remark: During this virtual method the Visual tree must not be modified. /// @@ -402,11 +323,10 @@ namespace System.Windows.Controls if (extData == null) { gridDesiredSize = new Size(); - UIElementCollection children = InternalChildren; - for (int i = 0, count = children.Count; i < count; ++i) + for (int i = 0, count = Children.Count; i < count; ++i) { - UIElement child = children[i]; + var child = Children[i]; if (child != null) { child.Measure(constraint); @@ -586,7 +506,7 @@ namespace System.Windows.Controls // +--------+ // // where: - // * all [Measure GroupN] - regular children measure process - + // * all [Measure GroupN] - regular Children measure process - // each cell is measured given contraint size as an input // and each cell's desired size is accumulated on the // corresponding column / row; @@ -638,7 +558,7 @@ namespace System.Windows.Controls // also use a count heuristic to break a loop in case of one. bool hasDesiredSizeUChanged = false; - int cnt=0; + int cnt = 0; // Cache Group2MinWidths & Group3MinHeights double[] group2MinSizes = CacheMinSizes(extData.CellGroup2, false); @@ -700,11 +620,11 @@ namespace System.Windows.Controls if (_data == null) { - UIElementCollection children = InternalChildren; + ControlCollection Children = InternalChildren; - for (int i = 0, count = children.Count; i < count; ++i) + for (int i = 0, count = Children.Count; i < count; ++i) { - UIElement child = children[i]; + Control child = Children[i]; if (child != null) { child.Arrange(new Rect(arrangeSize)); @@ -722,11 +642,11 @@ namespace System.Windows.Controls ExitCounter(Counters._SetFinalSize); - UIElementCollection children = InternalChildren; + ControlCollection Children = InternalChildren; for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) { - UIElement cell = children[currentCell]; + Control cell = Children[currentCell]; if (cell == null) { continue; @@ -741,7 +661,7 @@ namespace System.Windows.Controls columnIndex == 0 ? 0.0 : DefinitionsU[columnIndex].FinalOffset, rowIndex == 0 ? 0.0 : DefinitionsV[rowIndex].FinalOffset, GetFinalSizeForRange(DefinitionsU, columnIndex, columnSpan), - GetFinalSizeForRange(DefinitionsV, rowIndex, rowSpan) ); + GetFinalSizeForRange(DefinitionsV, rowIndex, rowSpan)); EnterCounter(Counters._ArrangeChildHelper2); cell.Arrange(cellRect); @@ -917,10 +837,10 @@ namespace System.Windows.Controls /// private void ValidateCellsCore() { - UIElementCollection children = InternalChildren; + ControlCollection Children = InternalChildren; ExtendedData extData = ExtData; - extData.CellCachesCollection = new CellCache[children.Count]; + extData.CellCachesCollection = new CellCache[Children.Count]; extData.CellGroup1 = int.MaxValue; extData.CellGroup2 = int.MaxValue; extData.CellGroup3 = int.MaxValue; @@ -932,7 +852,7 @@ namespace System.Windows.Controls for (int i = PrivateCells.Length - 1; i >= 0; --i) { - UIElement child = children[i]; + Control child = Children[i]; if (child == null) { continue; @@ -996,9 +916,9 @@ namespace System.Windows.Controls } else { - if ( cell.IsAutoU - // note below: if spans through Star column it is NOT Auto - && !cell.IsStarU ) + if (cell.IsAutoU + // note below: if spans through Star column it is NOT Auto + && !cell.IsStarU) { cell.Next = extData.CellGroup2; extData.CellGroup2 = i; @@ -1168,7 +1088,7 @@ namespace System.Windows.Controls { double[] minSizes = isRows ? new double[DefinitionsV.Length] : new double[DefinitionsU.Length]; - for (int j=0; j (double)o ) + if (o == null + || value > (double)o) { store[key] = value; } @@ -1355,8 +1274,8 @@ namespace System.Windows.Controls double cellMeasureWidth; double cellMeasureHeight; - if ( PrivateCells[cell].IsAutoU - && !PrivateCells[cell].IsStarU ) + if (PrivateCells[cell].IsAutoU + && !PrivateCells[cell].IsStarU) { // if cell belongs to at least one Auto column and not a single Star column // then it should be calculated "to content", thus it is possible to "shortcut" @@ -1376,8 +1295,8 @@ namespace System.Windows.Controls { cellMeasureHeight = double.PositiveInfinity; } - else if ( PrivateCells[cell].IsAutoV - && !PrivateCells[cell].IsStarV ) + else if (PrivateCells[cell].IsAutoV + && !PrivateCells[cell].IsStarV) { // if cell belongs to at least one Auto row and not a single Star row // then it should be calculated "to content", thus it is possible to "shortcut" @@ -1393,7 +1312,7 @@ namespace System.Windows.Controls } EnterCounter(Counters.__MeasureChild); - UIElement child = InternalChildren[cell]; + Control child = InternalChildren[cell]; if (child != null) { Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); @@ -1508,12 +1427,12 @@ namespace System.Windows.Controls // sanity check: no matter what, but min size must always be the smaller; // max size must be the biggest; and preferred should be in between - Debug.Assert( minSize <= preferredSize - && preferredSize <= maxSize - && rangeMinSize <= rangePreferredSize - && rangePreferredSize <= rangeMaxSize ); + Debug.Assert(minSize <= preferredSize + && preferredSize <= maxSize + && rangeMinSize <= rangePreferredSize + && rangePreferredSize <= rangeMaxSize); - if (maxMaxSize < maxSize) maxMaxSize = maxSize; + if (maxMaxSize < maxSize) maxMaxSize = maxSize; if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++; tempDefinitions[i - start] = definitions[i]; } @@ -1607,8 +1526,8 @@ namespace System.Windows.Controls // double equalSize = requestedSize / count; - if ( equalSize < maxMaxSize - && !_AreClose(equalSize, maxMaxSize) ) + if (equalSize < maxMaxSize + && !_AreClose(equalSize, maxMaxSize)) { // equi-size is less than maximum of maxSizes. // in this case distribute so that smaller definitions grow faster than @@ -1617,12 +1536,12 @@ namespace System.Windows.Controls double sizeToDistribute = requestedSize - rangeMaxSize; // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers - Debug.Assert( !double.IsInfinity(totalRemainingSize) - && !DoubleUtil.IsNaN(totalRemainingSize) - && totalRemainingSize > 0 - && !double.IsInfinity(sizeToDistribute) - && !DoubleUtil.IsNaN(sizeToDistribute) - && sizeToDistribute > 0 ); + Debug.Assert(!double.IsInfinity(totalRemainingSize) + && !DoubleUtil.IsNaN(totalRemainingSize) + && totalRemainingSize > 0 + && !double.IsInfinity(sizeToDistribute) + && !DoubleUtil.IsNaN(sizeToDistribute) + && sizeToDistribute > 0); for (int i = 0; i < count; ++i) { @@ -1706,9 +1625,9 @@ namespace System.Windows.Controls // Note: normalized star value is temporary cached into MeasureSize definitions[i].MeasureSize = starValue; - double maxSize = Math.Max(definitions[i].MinSize, definitions[i].UserMaxSize); - maxSize = Math.Min(maxSize, c_starClip); - definitions[i].SizeCache = maxSize / starValue; + double maxSize = Math.Max(definitions[i].MinSize, definitions[i].UserMaxSize); + maxSize = Math.Min(maxSize, c_starClip); + definitions[i].SizeCache = maxSize / starValue; } } break; @@ -1746,12 +1665,12 @@ namespace System.Windows.Controls else { double userSize = Math.Max(availableSize - takenSize, 0.0) * (starValue / tempDefinitions[i].SizeCache); - resolvedSize = Math.Min(userSize, tempDefinitions[i].UserMaxSize); - resolvedSize = Math.Max(tempDefinitions[i].MinSize, resolvedSize); + resolvedSize = Math.Min(userSize, tempDefinitions[i].UserMaxSize); + resolvedSize = Math.Max(tempDefinitions[i].MinSize, resolvedSize); } tempDefinitions[i].MeasureSize = resolvedSize; - takenSize += resolvedSize; + takenSize += resolvedSize; } while (++i < starDefinitionsCount); } } @@ -1783,7 +1702,7 @@ namespace System.Windows.Controls // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights double maxStar = 0.0; - for (int i=0; i finalSize - && !_AreClose(allPreferredArrangeSize, finalSize) ) + if (allPreferredArrangeSize > finalSize + && !_AreClose(allPreferredArrangeSize, finalSize)) { DistributionOrderIndexComparer distributionOrderIndexComparer = new DistributionOrderIndexComparer(definitions); Array.Sort(definitionIndices, 0, definitions.Length, distributionOrderIndexComparer); @@ -2267,7 +2186,7 @@ namespace System.Windows.Controls if (useLayoutRounding) { roundingErrors[definitionIndex] = final; - final = UIElement.RoundLayoutValue(finalOld, dpi); + final = Control.RoundLayoutValue(finalOld, dpi); final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); final = Math.Min(final, definitions[definitionIndex].SizeCache); } @@ -2294,7 +2213,7 @@ namespace System.Windows.Controls RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); double adjustedSize = allPreferredArrangeSize; - double dpiIncrement = UIElement.RoundLayoutValue(1.0, dpi); + double dpiIncrement = Control.RoundLayoutValue(1.0, dpi); if (allPreferredArrangeSize > finalSize) { @@ -2365,7 +2284,7 @@ namespace System.Windows.Controls // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights double maxStar = 0.0; - for (int i=0; i finalSize) { @@ -2895,8 +2814,8 @@ namespace System.Windows.Controls ExtendedData extData = ExtData; if (extData != null) { -// for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid (); -// for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid (); + // for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid (); + // for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid (); if (extData.TempDefinitions != null) { @@ -2914,9 +2833,9 @@ namespace System.Windows.Controls public bool ShouldSerializeColumnDefinitions() { ExtendedData extData = ExtData; - return ( extData != null - && extData.ColumnDefinitions != null - && extData.ColumnDefinitions.Count > 0 ); + return (extData != null + && extData.ColumnDefinitions != null + && extData.ColumnDefinitions.Count > 0); } /// @@ -2926,9 +2845,9 @@ namespace System.Windows.Controls public bool ShouldSerializeRowDefinitions() { ExtendedData extData = ExtData; - return ( extData != null - && extData.RowDefinitions != null - && extData.RowDefinitions.Count > 0 ); + return (extData != null + && extData.RowDefinitions != null + && extData.RowDefinitions.Count > 0); } /// @@ -2994,13 +2913,13 @@ namespace System.Windows.Controls { Grid grid = (Grid)d; - if ( grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway - && grid.ListenToNotifications) + if (grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway + && grid.ListenToNotifications) { grid.InvalidateVisual(); } - grid.SetFlags((bool) e.NewValue, Flags.ShowGridLinesPropertyValue); + grid.SetFlags((bool)e.NewValue, Flags.ShowGridLinesPropertyValue); } /// @@ -3013,9 +2932,9 @@ namespace System.Windows.Controls if (child != null) { Grid grid = VisualTreeHelper.GetParent(child) as Grid; - if ( grid != null - && grid.ExtData != null - && grid.ListenToNotifications ) + if (grid != null + && grid.ExtData != null + && grid.ListenToNotifications) { grid.CellsStructureDirty = true; grid.InvalidateMeasure(); @@ -3108,8 +3027,8 @@ namespace System.Windows.Controls ExtendedData extData = ExtData; int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length) * 2; - if ( extData.TempDefinitions == null - || extData.TempDefinitions.Length < requiredLength ) + if (extData.TempDefinitions == null + || extData.TempDefinitions.Length < requiredLength) { WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(s_tempDefinitionsDataSlot); if (tempDefinitionsWeakRef == null) @@ -3120,8 +3039,8 @@ namespace System.Windows.Controls else { extData.TempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; - if ( extData.TempDefinitions == null - || extData.TempDefinitions.Length < requiredLength ) + if (extData.TempDefinitions == null + || extData.TempDefinitions.Length < requiredLength) { extData.TempDefinitions = new DefinitionBase[requiredLength]; tempDefinitionsWeakRef.Target = extData.TempDefinitions; @@ -3348,7 +3267,7 @@ namespace System.Windows.Controls internal RowDefinitionCollection RowDefinitions; // collection of row definitions (logical tree support) internal DefinitionBase[] DefinitionsU; // collection of column definitions used during calc internal DefinitionBase[] DefinitionsV; // collection of row definitions used during calc - internal CellCache[] CellCachesCollection; // backing store for logical children + internal CellCache[] CellCachesCollection; // backing store for logical Children internal int CellGroup1; // index of the first cell in first cell group internal int CellGroup2; // index of the first cell in second cell group internal int CellGroup3; // index of the first cell in third cell group @@ -3369,26 +3288,26 @@ namespace System.Windows.Controls // * Valid???Layout flags indicate that layout time portion of the information // stored on the objects should be updated. // - ValidDefinitionsUStructure = 0x00000001, - ValidDefinitionsVStructure = 0x00000002, - ValidCellsStructure = 0x00000004, + ValidDefinitionsUStructure = 0x00000001, + ValidDefinitionsVStructure = 0x00000002, + ValidCellsStructure = 0x00000004, // // boolean properties state // - ShowGridLinesPropertyValue = 0x00000100, // show grid lines ? + ShowGridLinesPropertyValue = 0x00000100, // show grid lines ? // // boolean flags // - ListenToNotifications = 0x00001000, // "0" when all notifications are ignored - SizeToContentU = 0x00002000, // "1" if calculating to content in U direction - SizeToContentV = 0x00004000, // "1" if calculating to content in V direction - HasStarCellsU = 0x00008000, // "1" if at least one cell belongs to a Star column - HasStarCellsV = 0x00010000, // "1" if at least one cell belongs to a Star row - HasGroup3CellsInAutoRows = 0x00020000, // "1" if at least one cell of group 3 belongs to an Auto row - MeasureOverrideInProgress = 0x00040000, // "1" while in the context of Grid.MeasureOverride - ArrangeOverrideInProgress = 0x00080000, // "1" while in the context of Grid.ArrangeOverride + ListenToNotifications = 0x00001000, // "0" when all notifications are ignored + SizeToContentU = 0x00002000, // "1" if calculating to content in U direction + SizeToContentV = 0x00004000, // "1" if calculating to content in V direction + HasStarCellsU = 0x00008000, // "1" if at least one cell belongs to a Star column + HasStarCellsV = 0x00010000, // "1" if at least one cell belongs to a Star row + HasGroup3CellsInAutoRows = 0x00020000, // "1" if at least one cell of group 3 belongs to an Auto row + MeasureOverrideInProgress = 0x00040000, // "1" while in the context of Grid.MeasureOverride + ArrangeOverrideInProgress = 0x00080000, // "1" while in the context of Grid.ArrangeOverride } #endregion Private Structures Classes @@ -3506,7 +3425,7 @@ namespace System.Windows.Controls /// /// IsSharedSizeScope property marks scoping element for shared size. /// - public static readonly DependencyProperty IsSharedSizeScopeProperty = + public static readonly DependencyProperty IsSharedSizeScopeProperty = DependencyProperty.RegisterAttached( "IsSharedSizeScope", typeof(bool), @@ -3531,10 +3450,10 @@ namespace System.Windows.Controls [System.Flags] internal enum LayoutTimeSizeType : byte { - None = 0x00, - Pixel = 0x01, - Auto = 0x02, - Star = 0x04, + None = 0x00, + Pixel = 0x01, + Auto = 0x02, + Star = 0x04, } #endregion Internal Structures Classes @@ -3594,7 +3513,7 @@ namespace System.Windows.Controls int hash = (_start ^ (_count << 2)); if (_u) hash &= 0x7ffffff; - else hash |= 0x8000000; + else hash |= 0x8000000; return (hash); } @@ -3605,10 +3524,10 @@ namespace System.Windows.Controls public override bool Equals(object obj) { SpanKey sk = obj as SpanKey; - return ( sk != null - && sk._start == _start - && sk._count == _count - && sk._u == _u ); + return (sk != null + && sk._start == _start + && sk._count == _count + && sk._u == _u); } /// @@ -3740,7 +3659,7 @@ namespace System.Windows.Controls /// /// DistributionOrderComparer. /// - private class DistributionOrderComparer: IComparer + private class DistributionOrderComparer : IComparer { public int Compare(object x, object y) { @@ -4068,7 +3987,7 @@ namespace System.Windows.Controls } /// - /// Implementation of a simple enumerator of grid's logical children + /// Implementation of a simple enumerator of grid's logical Children /// private class GridChildrenCollectionEnumeratorSimple : IEnumerator { @@ -4102,13 +4021,14 @@ namespace System.Windows.Controls { case (0): if (_enumerator0.MoveNext()) { _currentChild = _enumerator0.Current; return (true); } break; case (1): if (_enumerator1.MoveNext()) { _currentChild = _enumerator1.Current; return (true); } break; - case (2): if (_enumerator2Index < _enumerator2Count) - { - _currentChild = _enumerator2Collection[_enumerator2Index]; - _enumerator2Index++; - return (true); - } - break; + case (2): + if (_enumerator2Index < _enumerator2Count) + { + _currentChild = _enumerator2Collection[_enumerator2Index]; + _enumerator2Index++; + return (true); + } + break; } } _currentEnumerator++; @@ -4122,16 +4042,16 @@ namespace System.Windows.Controls { if (_currentEnumerator == -1) { - #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception +#pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception throw new InvalidOperationException(SR.Get(SRID.EnumeratorNotStarted)); } if (_currentEnumerator >= 3) { - #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception +#pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception throw new InvalidOperationException(SR.Get(SRID.EnumeratorReachedEnd)); } - // assert below is not true anymore since UIElementCollection allowes for null children + // assert below is not true anymore since ControlCollection allowes for null Children //Debug.Assert(_currentChild != null); return (_currentChild); } @@ -4150,7 +4070,7 @@ namespace System.Windows.Controls private Object _currentChild; private ColumnDefinitionCollection.Enumerator _enumerator0; private RowDefinitionCollection.Enumerator _enumerator1; - private UIElementCollection _enumerator2Collection; + private ControlCollection _enumerator2Collection; private int _enumerator2Index; private int _enumerator2Count; } @@ -4191,8 +4111,8 @@ namespace System.Windows.Controls using (DrawingContext drawingContext = RenderOpen()) { Grid grid = VisualTreeHelper.GetParent(this) as Grid; - if ( grid == null - || grid.ShowGridLines == false ) + if (grid == null + || grid.ShowGridLines == false) { return; } @@ -4239,182 +4159,5 @@ namespace System.Windows.Controls } #endregion Private Structures Classes - - //------------------------------------------------------ - // - // Extended debugging for grid - // - //------------------------------------------------------ - -#if GRIDPARANOIA - private static double _performanceFrequency; - private static readonly bool _performanceFrequencyInitialized = InitializePerformanceFrequency(); - - //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")] - private static extern bool QueryPerformanceCounter(out long lpPerformanceCount); - - //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")] - private static extern bool QueryPerformanceFrequency(out long lpFrequency); - - private static double CostInMilliseconds(long count) - { - return ((double)count / _performanceFrequency); - } - - private static long Cost(long startCount, long endCount) - { - long l = endCount - startCount; - if (l < 0) { l += long.MaxValue; } - return (l); - } - - private static bool InitializePerformanceFrequency() - { - long l; - QueryPerformanceFrequency(out l); - _performanceFrequency = (double)l * 0.001; - return (true); - } - - private struct Counter - { - internal long Start; - internal long Total; - internal int Calls; - } - - private Counter[] _counters; - private bool _hasNewCounterInfo; -#endif // GRIDPARANOIA - - // - // This property - // 1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject - // 2. This is a performance optimization - // - internal override int EffectiveValuesInitialSize - { - get { return 9; } - } - - [Conditional("GRIDPARANOIA")] - internal void EnterCounterScope(Counters scopeCounter) - { - #if GRIDPARANOIA - if (ID == "CountThis") - { - if (_counters == null) - { - _counters = new Counter[(int)Counters.Count]; - } - ExitCounterScope(Counters.Default); - EnterCounter(scopeCounter); - } - else - { - _counters = null; - } - #endif // GRIDPARANOIA - } - - [Conditional("GRIDPARANOIA")] - internal void ExitCounterScope(Counters scopeCounter) - { - #if GRIDPARANOIA - if (_counters != null) - { - if (scopeCounter != Counters.Default) - { - ExitCounter(scopeCounter); - } - - if (_hasNewCounterInfo) - { - string NFormat = "F6"; - Console.WriteLine( - "\ncounter name | total t (ms) | # of calls | per call t (ms)" - + "\n----------------------+---------------+---------------+----------------------" ); - - for (int i = 0; i < _counters.Length; ++i) - { - if (_counters[i].Calls > 0) - { - Counters counter = (Counters)i; - double total = CostInMilliseconds(_counters[i].Total); - double single = total / _counters[i].Calls; - string counterName = counter.ToString(); - string separator; - - if (counterName.Length < 8) { separator = "\t\t\t"; } - else if (counterName.Length < 16) { separator = "\t\t"; } - else { separator = "\t"; } - - Console.WriteLine( - counter.ToString() + separator - + total.ToString(NFormat) + "\t" - + _counters[i].Calls + "\t\t" - + single.ToString(NFormat)); - - _counters[i] = new Counter(); - } - } - } - _hasNewCounterInfo = false; - } - #endif // GRIDPARANOIA - } - - [Conditional("GRIDPARANOIA")] - internal void EnterCounter(Counters counter) - { - #if GRIDPARANOIA - if (_counters != null) - { - Debug.Assert((int)counter < _counters.Length); - - int i = (int)counter; - QueryPerformanceCounter(out _counters[i].Start); - } - #endif // GRIDPARANOIA - } - - [Conditional("GRIDPARANOIA")] - internal void ExitCounter(Counters counter) - { - #if GRIDPARANOIA - if (_counters != null) - { - Debug.Assert((int)counter < _counters.Length); - - int i = (int)counter; - long l; - QueryPerformanceCounter(out l); - l = Cost(_counters[i].Start, l); - _counters[i].Total += l; - _counters[i].Calls++; - _hasNewCounterInfo = true; - } - #endif // GRIDPARANOIA - } - - internal enum Counters : int - { - Default = -1, - - MeasureOverride, - _ValidateColsStructure, - _ValidateRowsStructure, - _ValidateCells, - _MeasureCell, - __MeasureChild, - _CalculateDesiredSize, - - ArrangeOverride, - _SetFinalSize, - _ArrangeChildHelper2, - _PositionCell, - - Count, - } } } \ No newline at end of file From 90e6db45aaf8c4c4dc7333d1ba1ee8a2d924957f Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 23 May 2019 13:38:13 +0800 Subject: [PATCH 016/130] part 2 of n --- src/Avalonia.Controls/Grid.cs | 1200 +++++++++++++++--------------- src/Avalonia.Controls/GridWPF.cs | 394 ++-------- 2 files changed, 670 insertions(+), 924 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 90a27d0b31..98cd5aad02 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1,601 +1,601 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using Avalonia.Collections; -using Avalonia.Controls.Utils; -using Avalonia.VisualTree; -using JetBrains.Annotations; - -namespace Avalonia.Controls -{ - /// - /// Lays out child controls according to a grid. - /// - public class Grid : Panel - { - /// - /// Defines the Column attached property. - /// - public static readonly AttachedProperty ColumnProperty = - AvaloniaProperty.RegisterAttached( - "Column", - validate: ValidateColumn); - - /// - /// Defines the ColumnSpan attached property. - /// - public static readonly AttachedProperty ColumnSpanProperty = - AvaloniaProperty.RegisterAttached("ColumnSpan", 1); - - /// - /// Defines the Row attached property. - /// - public static readonly AttachedProperty RowProperty = - AvaloniaProperty.RegisterAttached( - "Row", - validate: ValidateRow); - - /// - /// Defines the RowSpan attached property. - /// - public static readonly AttachedProperty RowSpanProperty = - AvaloniaProperty.RegisterAttached("RowSpan", 1); - - public static readonly AttachedProperty IsSharedSizeScopeProperty = - AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); - - protected override void OnMeasureInvalidated() - { - base.OnMeasureInvalidated(); - _sharedSizeHost?.InvalidateMeasure(this); - } - - private SharedSizeScopeHost _sharedSizeHost; - - /// - /// Defines the SharedSizeScopeHost private property. - /// The ampersands are used to make accessing the property via xaml inconvenient. - /// - internal static readonly AttachedProperty s_sharedSizeScopeHostProperty = - AvaloniaProperty.RegisterAttached("&&SharedSizeScopeHost"); - - private ColumnDefinitions _columnDefinitions; - - private RowDefinitions _rowDefinitions; - - static Grid() - { - AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); - IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopeChanged); - } - - public Grid() - { - this.AttachedToVisualTree += Grid_AttachedToVisualTree; - this.DetachedFromVisualTree += Grid_DetachedFromVisualTree; - } - - /// - /// Gets or sets the columns definitions for the grid. - /// - public ColumnDefinitions ColumnDefinitions - { - get - { - if (_columnDefinitions == null) - { - ColumnDefinitions = new ColumnDefinitions(); - } - - return _columnDefinitions; - } - - set - { - if (_columnDefinitions != null) - { - throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented."); - } - - _columnDefinitions = value; - _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); - _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); - } - } - - /// - /// Gets or sets the row definitions for the grid. - /// - public RowDefinitions RowDefinitions - { - get - { - if (_rowDefinitions == null) - { - RowDefinitions = new RowDefinitions(); - } - - return _rowDefinitions; - } - - set - { - if (_rowDefinitions != null) - { - throw new NotSupportedException("Reassigning RowDefinitions not yet implemented."); - } - - _rowDefinitions = value; - _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); - _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); - } - } - - /// - /// Gets the value of the Column attached property for a control. - /// - /// The control. - /// The control's column. - public static int GetColumn(AvaloniaObject element) - { - return element.GetValue(ColumnProperty); - } - - /// - /// Gets the value of the ColumnSpan attached property for a control. - /// - /// The control. - /// The control's column span. - public static int GetColumnSpan(AvaloniaObject element) - { - return element.GetValue(ColumnSpanProperty); - } - - /// - /// Gets the value of the Row attached property for a control. - /// - /// The control. - /// The control's row. - public static int GetRow(AvaloniaObject element) - { - return element.GetValue(RowProperty); - } - - /// - /// Gets the value of the RowSpan attached property for a control. - /// - /// The control. - /// The control's row span. - public static int GetRowSpan(AvaloniaObject element) - { - return element.GetValue(RowSpanProperty); - } - - - /// - /// Gets the value of the IsSharedSizeScope attached property for a control. - /// - /// The control. - /// The control's IsSharedSizeScope value. - public static bool GetIsSharedSizeScope(AvaloniaObject element) - { - return element.GetValue(IsSharedSizeScopeProperty); - } - - /// - /// Sets the value of the Column attached property for a control. - /// - /// The control. - /// The column value. - public static void SetColumn(AvaloniaObject element, int value) - { - element.SetValue(ColumnProperty, value); - } - - /// - /// Sets the value of the ColumnSpan attached property for a control. - /// - /// The control. - /// The column span value. - public static void SetColumnSpan(AvaloniaObject element, int value) - { - element.SetValue(ColumnSpanProperty, value); - } - - /// - /// Sets the value of the Row attached property for a control. - /// - /// The control. - /// The row value. - public static void SetRow(AvaloniaObject element, int value) - { - element.SetValue(RowProperty, value); - } - - /// - /// Sets the value of the RowSpan attached property for a control. - /// - /// The control. - /// The row span value. - public static void SetRowSpan(AvaloniaObject element, int value) - { - element.SetValue(RowSpanProperty, value); - } - - /// - /// Sets the value of IsSharedSizeScope property for a control. - /// - /// The control. - /// The IsSharedSizeScope value. - public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) - { - element.SetValue(IsSharedSizeScopeProperty, value); - } - - /// - /// Gets the result of the last column measurement. - /// Use this result to reduce the arrange calculation. - /// - private GridLayout.MeasureResult _columnMeasureCache; - - /// - /// Gets the result of the last row measurement. - /// Use this result to reduce the arrange calculation. - /// - private GridLayout.MeasureResult _rowMeasureCache; - - /// - /// Gets the row layout as of the last measure. - /// - private GridLayout _rowLayoutCache; - - /// - /// Gets the column layout as of the last measure. - /// - private GridLayout _columnLayoutCache; - - /// - /// Measures the grid. - /// - /// The available size. - /// The desired size of the control. - protected override Size MeasureOverride(Size constraint) - { - // Situation 1/2: - // If the grid doesn't have any column/row definitions, it behaves like a normal panel. - // GridLayout supports this situation but we handle this separately for performance. - - if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) - { - var maxWidth = 0.0; - var maxHeight = 0.0; - foreach (var child in Children.OfType()) - { - child.Measure(constraint); - maxWidth = Math.Max(maxWidth, child.DesiredSize.Width); - maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); - } - - maxWidth = Math.Min(maxWidth, constraint.Width); - maxHeight = Math.Min(maxHeight, constraint.Height); - return new Size(maxWidth, maxHeight); - } - - // Situation 2/2: - // If the grid defines some columns or rows. - // Debug Tip: - // - GridLayout doesn't hold any state, so you can drag the debugger execution - // arrow back to any statements and re-run them without any side-effect. - - var measureCache = new Dictionary(); - var (safeColumns, safeRows) = GetSafeColumnRows(); - var columnLayout = new GridLayout(ColumnDefinitions); - var rowLayout = new GridLayout(RowDefinitions); - // Note: If a child stays in a * or Auto column/row, use constraint to measure it. - columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width); - rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height); - - // Calculate measurement. - var columnResult = columnLayout.Measure(constraint.Width); - var rowResult = rowLayout.Measure(constraint.Height); - - // Use the results of the measurement to measure the rest of the children. - foreach (var child in Children.OfType()) - { - var (column, columnSpan) = safeColumns[child]; - var (row, rowSpan) = safeRows[child]; - var width = Enumerable.Range(column, columnSpan).Select(x => columnResult.LengthList[x]).Sum(); - var height = Enumerable.Range(row, rowSpan).Select(x => rowResult.LengthList[x]).Sum(); - - MeasureOnce(child, new Size(width, height)); - } - - // Cache the measure result and return the desired size. - _columnMeasureCache = columnResult; - _rowMeasureCache = rowResult; - _rowLayoutCache = rowLayout; - _columnLayoutCache = columnLayout; - - if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) - { - _sharedSizeHost.UpdateMeasureStatus(this, rowResult, columnResult); - } - - return new Size(columnResult.DesiredLength, rowResult.DesiredLength); - - // Measure each child only once. - // If a child has been measured, it will just return the desired size. - Size MeasureOnce(Control child, Size size) - { - if (measureCache.TryGetValue(child, out var desiredSize)) - { - return desiredSize; - } - - child.Measure(size); - desiredSize = child.DesiredSize; - measureCache[child] = desiredSize; - return desiredSize; - } - } - - /// - /// Arranges the grid's children. - /// - /// The size allocated to the control. - /// The space taken. - protected override Size ArrangeOverride(Size finalSize) - { - // Situation 1/2: - // If the grid doesn't have any column/row definitions, it behaves like a normal panel. - // GridLayout supports this situation but we handle this separately for performance. - - if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) - { - foreach (var child in Children.OfType()) - { - child.Arrange(new Rect(finalSize)); - } - - return finalSize; - } - - // Situation 2/2: - // If the grid defines some columns or rows. - // Debug Tip: - // - GridLayout doesn't hold any state, so you can drag the debugger execution - // arrow back to any statements and re-run them without any side-effect. - - var (safeColumns, safeRows) = GetSafeColumnRows(); - var columnLayout = _columnLayoutCache; - var rowLayout = _rowLayoutCache; - - var rowCache = _rowMeasureCache; - var columnCache = _columnMeasureCache; - - if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) - { - (rowCache, columnCache) = _sharedSizeHost.HandleArrange(this, _rowMeasureCache, _columnMeasureCache); +// // Copyright (c) The Avalonia Project. All rights reserved. +// // Licensed under the MIT license. See licence.md file in the project root for full license information. + +// using System; +// using System.Collections.Generic; +// using System.Diagnostics; +// using System.Linq; +// using System.Reactive.Linq; +// using System.Runtime.CompilerServices; +// using Avalonia.Collections; +// using Avalonia.Controls.Utils; +// using Avalonia.VisualTree; +// using JetBrains.Annotations; + +// namespace Avalonia.Controls +// { +// /// +// /// Lays out child controls according to a grid. +// /// +// public class Grid : Panel +// { +// /// +// /// Defines the Column attached property. +// /// +// public static readonly AttachedProperty ColumnProperty = +// AvaloniaProperty.RegisterAttached( +// "Column", +// validate: ValidateColumn); + +// /// +// /// Defines the ColumnSpan attached property. +// /// +// public static readonly AttachedProperty ColumnSpanProperty = +// AvaloniaProperty.RegisterAttached("ColumnSpan", 1); + +// /// +// /// Defines the Row attached property. +// /// +// public static readonly AttachedProperty RowProperty = +// AvaloniaProperty.RegisterAttached( +// "Row", +// validate: ValidateRow); + +// /// +// /// Defines the RowSpan attached property. +// /// +// public static readonly AttachedProperty RowSpanProperty = +// AvaloniaProperty.RegisterAttached("RowSpan", 1); + +// public static readonly AttachedProperty IsSharedSizeScopeProperty = +// AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); + +// protected override void OnMeasureInvalidated() +// { +// base.OnMeasureInvalidated(); +// _sharedSizeHost?.InvalidateMeasure(this); +// } + +// private SharedSizeScopeHost _sharedSizeHost; + +// /// +// /// Defines the SharedSizeScopeHost private property. +// /// The ampersands are used to make accessing the property via xaml inconvenient. +// /// +// internal static readonly AttachedProperty s_sharedSizeScopeHostProperty = +// AvaloniaProperty.RegisterAttached("&&SharedSizeScopeHost"); + +// private ColumnDefinitions _columnDefinitions; + +// private RowDefinitions _rowDefinitions; + +// static Grid() +// { +// AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); +// IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopeChanged); +// } + +// public Grid() +// { +// this.AttachedToVisualTree += Grid_AttachedToVisualTree; +// this.DetachedFromVisualTree += Grid_DetachedFromVisualTree; +// } + +// /// +// /// Gets or sets the columns definitions for the grid. +// /// +// public ColumnDefinitions ColumnDefinitions +// { +// get +// { +// if (_columnDefinitions == null) +// { +// ColumnDefinitions = new ColumnDefinitions(); +// } + +// return _columnDefinitions; +// } + +// set +// { +// if (_columnDefinitions != null) +// { +// throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented."); +// } + +// _columnDefinitions = value; +// _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); +// _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); +// } +// } + +// /// +// /// Gets or sets the row definitions for the grid. +// /// +// public RowDefinitions RowDefinitions +// { +// get +// { +// if (_rowDefinitions == null) +// { +// RowDefinitions = new RowDefinitions(); +// } + +// return _rowDefinitions; +// } + +// set +// { +// if (_rowDefinitions != null) +// { +// throw new NotSupportedException("Reassigning RowDefinitions not yet implemented."); +// } + +// _rowDefinitions = value; +// _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); +// _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); +// } +// } + +// /// +// /// Gets the value of the Column attached property for a control. +// /// +// /// The control. +// /// The control's column. +// public static int GetColumn(AvaloniaObject element) +// { +// return element.GetValue(ColumnProperty); +// } + +// /// +// /// Gets the value of the ColumnSpan attached property for a control. +// /// +// /// The control. +// /// The control's column span. +// public static int GetColumnSpan(AvaloniaObject element) +// { +// return element.GetValue(ColumnSpanProperty); +// } + +// /// +// /// Gets the value of the Row attached property for a control. +// /// +// /// The control. +// /// The control's row. +// public static int GetRow(AvaloniaObject element) +// { +// return element.GetValue(RowProperty); +// } + +// /// +// /// Gets the value of the RowSpan attached property for a control. +// /// +// /// The control. +// /// The control's row span. +// public static int GetRowSpan(AvaloniaObject element) +// { +// return element.GetValue(RowSpanProperty); +// } + + +// /// +// /// Gets the value of the IsSharedSizeScope attached property for a control. +// /// +// /// The control. +// /// The control's IsSharedSizeScope value. +// public static bool GetIsSharedSizeScope(AvaloniaObject element) +// { +// return element.GetValue(IsSharedSizeScopeProperty); +// } + +// /// +// /// Sets the value of the Column attached property for a control. +// /// +// /// The control. +// /// The column value. +// public static void SetColumn(AvaloniaObject element, int value) +// { +// element.SetValue(ColumnProperty, value); +// } + +// /// +// /// Sets the value of the ColumnSpan attached property for a control. +// /// +// /// The control. +// /// The column span value. +// public static void SetColumnSpan(AvaloniaObject element, int value) +// { +// element.SetValue(ColumnSpanProperty, value); +// } + +// /// +// /// Sets the value of the Row attached property for a control. +// /// +// /// The control. +// /// The row value. +// public static void SetRow(AvaloniaObject element, int value) +// { +// element.SetValue(RowProperty, value); +// } + +// /// +// /// Sets the value of the RowSpan attached property for a control. +// /// +// /// The control. +// /// The row span value. +// public static void SetRowSpan(AvaloniaObject element, int value) +// { +// element.SetValue(RowSpanProperty, value); +// } + +// /// +// /// Sets the value of IsSharedSizeScope property for a control. +// /// +// /// The control. +// /// The IsSharedSizeScope value. +// public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) +// { +// element.SetValue(IsSharedSizeScopeProperty, value); +// } + +// /// +// /// Gets the result of the last column measurement. +// /// Use this result to reduce the arrange calculation. +// /// +// private GridLayout.MeasureResult _columnMeasureCache; + +// /// +// /// Gets the result of the last row measurement. +// /// Use this result to reduce the arrange calculation. +// /// +// private GridLayout.MeasureResult _rowMeasureCache; + +// /// +// /// Gets the row layout as of the last measure. +// /// +// private GridLayout _rowLayoutCache; + +// /// +// /// Gets the column layout as of the last measure. +// /// +// private GridLayout _columnLayoutCache; + +// /// +// /// Measures the grid. +// /// +// /// The available size. +// /// The desired size of the control. +// protected override Size MeasureOverride(Size constraint) +// { +// // Situation 1/2: +// // If the grid doesn't have any column/row definitions, it behaves like a normal panel. +// // GridLayout supports this situation but we handle this separately for performance. + +// if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) +// { +// var maxWidth = 0.0; +// var maxHeight = 0.0; +// foreach (var child in Children.OfType()) +// { +// child.Measure(constraint); +// maxWidth = Math.Max(maxWidth, child.DesiredSize.Width); +// maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); +// } + +// maxWidth = Math.Min(maxWidth, constraint.Width); +// maxHeight = Math.Min(maxHeight, constraint.Height); +// return new Size(maxWidth, maxHeight); +// } + +// // Situation 2/2: +// // If the grid defines some columns or rows. +// // Debug Tip: +// // - GridLayout doesn't hold any state, so you can drag the debugger execution +// // arrow back to any statements and re-run them without any side-effect. + +// var measureCache = new Dictionary(); +// var (safeColumns, safeRows) = GetSafeColumnRows(); +// var columnLayout = new GridLayout(ColumnDefinitions); +// var rowLayout = new GridLayout(RowDefinitions); +// // Note: If a child stays in a * or Auto column/row, use constraint to measure it. +// columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width); +// rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height); + +// // Calculate measurement. +// var columnResult = columnLayout.Measure(constraint.Width); +// var rowResult = rowLayout.Measure(constraint.Height); + +// // Use the results of the measurement to measure the rest of the children. +// foreach (var child in Children.OfType()) +// { +// var (column, columnSpan) = safeColumns[child]; +// var (row, rowSpan) = safeRows[child]; +// var width = Enumerable.Range(column, columnSpan).Select(x => columnResult.LengthList[x]).Sum(); +// var height = Enumerable.Range(row, rowSpan).Select(x => rowResult.LengthList[x]).Sum(); + +// MeasureOnce(child, new Size(width, height)); +// } + +// // Cache the measure result and return the desired size. +// _columnMeasureCache = columnResult; +// _rowMeasureCache = rowResult; +// _rowLayoutCache = rowLayout; +// _columnLayoutCache = columnLayout; + +// if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) +// { +// _sharedSizeHost.UpdateMeasureStatus(this, rowResult, columnResult); +// } + +// return new Size(columnResult.DesiredLength, rowResult.DesiredLength); + +// // Measure each child only once. +// // If a child has been measured, it will just return the desired size. +// Size MeasureOnce(Control child, Size size) +// { +// if (measureCache.TryGetValue(child, out var desiredSize)) +// { +// return desiredSize; +// } + +// child.Measure(size); +// desiredSize = child.DesiredSize; +// measureCache[child] = desiredSize; +// return desiredSize; +// } +// } + +// /// +// /// Arranges the grid's children. +// /// +// /// The size allocated to the control. +// /// The space taken. +// protected override Size ArrangeOverride(Size finalSize) +// { +// // Situation 1/2: +// // If the grid doesn't have any column/row definitions, it behaves like a normal panel. +// // GridLayout supports this situation but we handle this separately for performance. + +// if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) +// { +// foreach (var child in Children.OfType()) +// { +// child.Arrange(new Rect(finalSize)); +// } + +// return finalSize; +// } + +// // Situation 2/2: +// // If the grid defines some columns or rows. +// // Debug Tip: +// // - GridLayout doesn't hold any state, so you can drag the debugger execution +// // arrow back to any statements and re-run them without any side-effect. + +// var (safeColumns, safeRows) = GetSafeColumnRows(); +// var columnLayout = _columnLayoutCache; +// var rowLayout = _rowLayoutCache; + +// var rowCache = _rowMeasureCache; +// var columnCache = _columnMeasureCache; + +// if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) +// { +// (rowCache, columnCache) = _sharedSizeHost.HandleArrange(this, _rowMeasureCache, _columnMeasureCache); - rowCache = rowLayout.Measure(finalSize.Height, rowCache.LeanLengthList); - columnCache = columnLayout.Measure(finalSize.Width, columnCache.LeanLengthList); - } - - // Calculate for arrange result. - var columnResult = columnLayout.Arrange(finalSize.Width, columnCache); - var rowResult = rowLayout.Arrange(finalSize.Height, rowCache); - // Arrange the children. - foreach (var child in Children.OfType()) - { - var (column, columnSpan) = safeColumns[child]; - var (row, rowSpan) = safeRows[child]; - var x = Enumerable.Range(0, column).Sum(c => columnResult.LengthList[c]); - var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]); - var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]); - var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]); - child.Arrange(new Rect(x, y, width, height)); - } - - // Assign the actual width. - for (var i = 0; i < ColumnDefinitions.Count; i++) - { - ColumnDefinitions[i].ActualWidth = columnResult.LengthList[i]; - } - - // Assign the actual height. - for (var i = 0; i < RowDefinitions.Count; i++) - { - RowDefinitions[i].ActualHeight = rowResult.LengthList[i]; - } - - // Return the render size. - return finalSize; - } - - /// - /// Tests whether this grid belongs to a shared size scope. - /// - /// True if the grid is registered in a shared size scope. - internal bool HasSharedSizeScope() - { - return _sharedSizeHost != null; - } - - /// - /// Called when the SharedSizeScope for a given grid has changed. - /// Unregisters the grid from it's current scope and finds a new one (if any) - /// - /// - /// This method, while not efficient, correctly handles nested scopes, with any order of scope changes. - /// - internal void SharedScopeChanged() - { - _sharedSizeHost?.UnegisterGrid(this); - - _sharedSizeHost = null; - var scope = this.GetVisualAncestors().OfType() - .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); - - if (scope != null) - { - _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); - _sharedSizeHost.RegisterGrid(this); - } - - InvalidateMeasure(); - } - - /// - /// Callback when a grid is attached to the visual tree. Finds the innermost SharedSizeScope and registers the grid - /// in it. - /// - /// The source of the event. - /// The event arguments. - private void Grid_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) - { - var scope = - new Control[] { this }.Concat(this.GetVisualAncestors().OfType()) - .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); - - if (_sharedSizeHost != null) - throw new AvaloniaInternalException("Shared size scope already present when attaching to visual tree!"); - - if (scope != null) - { - _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); - _sharedSizeHost.RegisterGrid(this); - } - } - - /// - /// Callback when a grid is detached from the visual tree. Unregisters the grid from its SharedSizeScope if any. - /// - /// The source of the event. - /// The event arguments. - private void Grid_DetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) - { - _sharedSizeHost?.UnegisterGrid(this); - _sharedSizeHost = null; - } - - - /// - /// Get the safe column/columnspan and safe row/rowspan. - /// This method ensures that none of the children has a column/row outside the bounds of the definitions. - /// - [Pure] - private (Dictionary safeColumns, - Dictionary safeRows) GetSafeColumnRows() - { - var columnCount = ColumnDefinitions.Count; - var rowCount = RowDefinitions.Count; - columnCount = columnCount == 0 ? 1 : columnCount; - rowCount = rowCount == 0 ? 1 : rowCount; - var safeColumns = Children.OfType().ToDictionary(child => child, - child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child))); - var safeRows = Children.OfType().ToDictionary(child => child, - child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child))); - return (safeColumns, safeRows); - } - - /// - /// Gets the safe row/column and rowspan/columnspan for a specified range. - /// The user may assign row/column properties outside the bounds of the row/column count, this method coerces them inside. - /// - /// The row or column count. - /// The row or column that the user assigned. - /// The rowspan or columnspan that the user assigned. - /// The safe row/column and rowspan/columnspan. - [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] - private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan) - { - var index = userIndex; - var span = userSpan; - - if (index < 0) - { - span = index + span; - index = 0; - } - - if (span <= 0) - { - span = 1; - } - - if (userIndex >= length) - { - index = length - 1; - span = 1; - } - else if (userIndex + userSpan > length) - { - span = length - userIndex; - } - - return (index, span); - } - - private static int ValidateColumn(AvaloniaObject o, int value) - { - if (value < 0) - { - throw new ArgumentException("Invalid Grid.Column value."); - } - - return value; - } - - private static int ValidateRow(AvaloniaObject o, int value) - { - if (value < 0) - { - throw new ArgumentException("Invalid Grid.Row value."); - } - - return value; - } - - /// - /// Called when the value of changes for a control. - /// - /// The control that triggered the change. - /// Change arguments. - private static void IsSharedSizeScopeChanged(Control source, AvaloniaPropertyChangedEventArgs arg2) - { - var shouldDispose = (arg2.OldValue is bool d) && d; - if (shouldDispose) - { - var host = source.GetValue(s_sharedSizeScopeHostProperty) as SharedSizeScopeHost; - if (host == null) - throw new AvaloniaInternalException("SharedScopeHost wasn't set when IsSharedSizeScope was true!"); - host.Dispose(); - source.ClearValue(s_sharedSizeScopeHostProperty); - } - - var shouldAssign = (arg2.NewValue is bool a) && a; - if (shouldAssign) - { - if (source.GetValue(s_sharedSizeScopeHostProperty) != null) - throw new AvaloniaInternalException("SharedScopeHost was already set when IsSharedSizeScope is only now being set to true!"); - source.SetValue(s_sharedSizeScopeHostProperty, new SharedSizeScopeHost()); - } - - // if the scope has changed, notify the descendant grids that they need to update. - if (source.GetVisualRoot() != null && shouldAssign || shouldDispose) - { - var participatingGrids = new[] { source }.Concat(source.GetVisualDescendants()).OfType(); - - foreach (var grid in participatingGrids) - grid.SharedScopeChanged(); - - } - } - } -} +// rowCache = rowLayout.Measure(finalSize.Height, rowCache.LeanLengthList); +// columnCache = columnLayout.Measure(finalSize.Width, columnCache.LeanLengthList); +// } + +// // Calculate for arrange result. +// var columnResult = columnLayout.Arrange(finalSize.Width, columnCache); +// var rowResult = rowLayout.Arrange(finalSize.Height, rowCache); +// // Arrange the children. +// foreach (var child in Children.OfType()) +// { +// var (column, columnSpan) = safeColumns[child]; +// var (row, rowSpan) = safeRows[child]; +// var x = Enumerable.Range(0, column).Sum(c => columnResult.LengthList[c]); +// var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]); +// var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]); +// var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]); +// child.Arrange(new Rect(x, y, width, height)); +// } + +// // Assign the actual width. +// for (var i = 0; i < ColumnDefinitions.Count; i++) +// { +// ColumnDefinitions[i].ActualWidth = columnResult.LengthList[i]; +// } + +// // Assign the actual height. +// for (var i = 0; i < RowDefinitions.Count; i++) +// { +// RowDefinitions[i].ActualHeight = rowResult.LengthList[i]; +// } + +// // Return the render size. +// return finalSize; +// } + +// /// +// /// Tests whether this grid belongs to a shared size scope. +// /// +// /// True if the grid is registered in a shared size scope. +// internal bool HasSharedSizeScope() +// { +// return _sharedSizeHost != null; +// } + +// /// +// /// Called when the SharedSizeScope for a given grid has changed. +// /// Unregisters the grid from it's current scope and finds a new one (if any) +// /// +// /// +// /// This method, while not efficient, correctly handles nested scopes, with any order of scope changes. +// /// +// internal void SharedScopeChanged() +// { +// _sharedSizeHost?.UnegisterGrid(this); + +// _sharedSizeHost = null; +// var scope = this.GetVisualAncestors().OfType() +// .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); + +// if (scope != null) +// { +// _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); +// _sharedSizeHost.RegisterGrid(this); +// } + +// InvalidateMeasure(); +// } + +// /// +// /// Callback when a grid is attached to the visual tree. Finds the innermost SharedSizeScope and registers the grid +// /// in it. +// /// +// /// The source of the event. +// /// The event arguments. +// private void Grid_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) +// { +// var scope = +// new Control[] { this }.Concat(this.GetVisualAncestors().OfType()) +// .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); + +// if (_sharedSizeHost != null) +// throw new AvaloniaInternalException("Shared size scope already present when attaching to visual tree!"); + +// if (scope != null) +// { +// _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); +// _sharedSizeHost.RegisterGrid(this); +// } +// } + +// /// +// /// Callback when a grid is detached from the visual tree. Unregisters the grid from its SharedSizeScope if any. +// /// +// /// The source of the event. +// /// The event arguments. +// private void Grid_DetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) +// { +// _sharedSizeHost?.UnegisterGrid(this); +// _sharedSizeHost = null; +// } + + +// /// +// /// Get the safe column/columnspan and safe row/rowspan. +// /// This method ensures that none of the children has a column/row outside the bounds of the definitions. +// /// +// [Pure] +// private (Dictionary safeColumns, +// Dictionary safeRows) GetSafeColumnRows() +// { +// var columnCount = ColumnDefinitions.Count; +// var rowCount = RowDefinitions.Count; +// columnCount = columnCount == 0 ? 1 : columnCount; +// rowCount = rowCount == 0 ? 1 : rowCount; +// var safeColumns = Children.OfType().ToDictionary(child => child, +// child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child))); +// var safeRows = Children.OfType().ToDictionary(child => child, +// child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child))); +// return (safeColumns, safeRows); +// } + +// /// +// /// Gets the safe row/column and rowspan/columnspan for a specified range. +// /// The user may assign row/column properties outside the bounds of the row/column count, this method coerces them inside. +// /// +// /// The row or column count. +// /// The row or column that the user assigned. +// /// The rowspan or columnspan that the user assigned. +// /// The safe row/column and rowspan/columnspan. +// [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] +// private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan) +// { +// var index = userIndex; +// var span = userSpan; + +// if (index < 0) +// { +// span = index + span; +// index = 0; +// } + +// if (span <= 0) +// { +// span = 1; +// } + +// if (userIndex >= length) +// { +// index = length - 1; +// span = 1; +// } +// else if (userIndex + userSpan > length) +// { +// span = length - userIndex; +// } + +// return (index, span); +// } + +// private static int ValidateColumn(AvaloniaObject o, int value) +// { +// if (value < 0) +// { +// throw new ArgumentException("Invalid Grid.Column value."); +// } + +// return value; +// } + +// private static int ValidateRow(AvaloniaObject o, int value) +// { +// if (value < 0) +// { +// throw new ArgumentException("Invalid Grid.Row value."); +// } + +// return value; +// } + +// /// +// /// Called when the value of changes for a control. +// /// +// /// The control that triggered the change. +// /// Change arguments. +// private static void IsSharedSizeScopeChanged(Control source, AvaloniaPropertyChangedEventArgs arg2) +// { +// var shouldDispose = (arg2.OldValue is bool d) && d; +// if (shouldDispose) +// { +// var host = source.GetValue(s_sharedSizeScopeHostProperty) as SharedSizeScopeHost; +// if (host == null) +// throw new AvaloniaInternalException("SharedScopeHost wasn't set when IsSharedSizeScope was true!"); +// host.Dispose(); +// source.ClearValue(s_sharedSizeScopeHostProperty); +// } + +// var shouldAssign = (arg2.NewValue is bool a) && a; +// if (shouldAssign) +// { +// if (source.GetValue(s_sharedSizeScopeHostProperty) != null) +// throw new AvaloniaInternalException("SharedScopeHost was already set when IsSharedSizeScope is only now being set to true!"); +// source.SetValue(s_sharedSizeScopeHostProperty, new SharedSizeScopeHost()); +// } + +// // if the scope has changed, notify the descendant grids that they need to update. +// if (source.GetVisualRoot() != null && shouldAssign || shouldDispose) +// { +// var participatingGrids = new[] { source }.Concat(source.GetVisualDescendants()).OfType(); + +// foreach (var grid in participatingGrids) +// grid.SharedScopeChanged(); + +// } +// } +// } +// } diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index ff006a5c1b..fc498608e3 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -17,7 +17,7 @@ using Avalonia.Media; using Avalonia; using System.Collections; -namespace System.Windows.Controls +namespace Avalonia.Controls { /// /// Grid @@ -25,284 +25,108 @@ namespace System.Windows.Controls public class Grid : Panel { /// - /// + /// Defines the Column attached property. /// - protected internal override IEnumerator LogicalChildren - { - get - { - // empty panel or a panel being used as the items - // host has *no* logical Children; give empty enumerator - bool noChildren = (base.VisualChildrenCount == 0) || IsItemsHost; - - if (noChildren) - { - ExtendedData extData = ExtData; - - if (extData == null - || ((extData.ColumnDefinitions == null || extData.ColumnDefinitions.Count == 0) - && (extData.RowDefinitions == null || extData.RowDefinitions.Count == 0)) - ) - { - // grid is empty - return EmptyEnumerator.Instance; - } - } - - return (new GridChildrenCollectionEnumeratorSimple(this, !noChildren)); - } - } + public static readonly AttachedProperty ColumnProperty = + AvaloniaProperty.RegisterAttached( + "Column", + validate: ValidateColumn); /// - /// Helper for setting Column property on a Control. + /// Defines the ColumnSpan attached property. /// - /// Control to set Column property on. - /// Column property value. - public static void SetColumn(Control element, int value) - { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - element.SetValue(ColumnProperty, value); - } + public static readonly AttachedProperty ColumnSpanProperty = + AvaloniaProperty.RegisterAttached("ColumnSpan", 1); /// - /// Helper for reading Column property from a Control. + /// Defines the Row attached property. /// - /// Control to read Column property from. - /// Column property value. - public static int GetColumn(Control element) - { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - return ((int)element.GetValue(ColumnProperty)); - } - - /// - /// Helper for setting Row property on a Control. - /// - /// Control to set Row property on. - /// Row property value. - public static void SetRow(Control element, int value) - { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - element.SetValue(RowProperty, value); - } + public static readonly AttachedProperty RowProperty = + AvaloniaProperty.RegisterAttached( + "Row", + validate: ValidateRow); /// - /// Helper for reading Row property from a Control. + /// Defines the RowSpan attached property. /// - /// Control to read Row property from. - /// Row property value. - [AttachedPropertyBrowsableForChildren()] - public static int GetRow(Control element) - { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - return ((int)element.GetValue(RowProperty)); - } - - /// - /// Helper for setting ColumnSpan property on a Control. - /// - /// Control to set ColumnSpan property on. - /// ColumnSpan property value. - public static void SetColumnSpan(Control element, int value) - { - if (element == null) - { - throw new ArgumentNullException("element"); - } + public static readonly AttachedProperty RowSpanProperty = + AvaloniaProperty.RegisterAttached("RowSpan", 1); - element.SetValue(ColumnSpanProperty, value); - } + public static readonly AttachedProperty IsSharedSizeScopeProperty = + AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); /// - /// Helper for reading ColumnSpan property from a Control. + /// ShowGridLines property. /// - /// Control to read ColumnSpan property from. - /// ColumnSpan property value. - [AttachedPropertyBrowsableForChildren()] - public static int GetColumnSpan(Control element) + public bool ShowGridLines { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - return ((int)element.GetValue(ColumnSpanProperty)); + get { return (CheckFlagsAnd(Flags.ShowGridLinesPropertyValue)); } + set { SetValue(ShowGridLinesProperty, value); } } + private ColumnDefinitions _columnDefinitions; + private RowDefinitions _rowDefinitions; /// - /// Helper for setting RowSpan property on a Control. + /// Gets or sets the columns definitions for the grid. /// - /// Control to set RowSpan property on. - /// RowSpan property value. - public static void SetRowSpan(Control element, int value) + public ColumnDefinitions ColumnDefinitions { - if (element == null) + get { - throw new ArgumentNullException("element"); - } + if (_data == null) { _data = new ExtendedData(); } - element.SetValue(RowSpanProperty, value); - } + if (_columnDefinitions == null) + { + ColumnDefinitions = new ColumnDefinitions(); + } - /// - /// Helper for reading RowSpan property from a Control. - /// - /// Control to read RowSpan property from. - /// RowSpan property value. - public static int GetRowSpan(Control element) - { - if (element == null) - { - throw new ArgumentNullException("element"); + return _columnDefinitions; } - return ((int)element.GetValue(RowSpanProperty)); - } - - /// - /// Helper for setting IsSharedSizeScope property on a Control. - /// - /// Control to set IsSharedSizeScope property on. - /// IsSharedSizeScope property value. - public static void SetIsSharedSizeScope(Control element, bool value) - { - if (element == null) + set { - throw new ArgumentNullException("element"); - } - element.SetValue(IsSharedSizeScopeProperty, value); - } + if (_columnDefinitions != null) + { + throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented."); + } - /// - /// Helper for reading IsSharedSizeScope property from a Control. - /// - /// Control to read IsSharedSizeScope property from. - /// IsSharedSizeScope property value. - public static bool GetIsSharedSizeScope(Control element) - { - if (element == null) - { - throw new ArgumentNullException("element"); + _columnDefinitions = value; + _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); + _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); } - - return ((bool)element.GetValue(IsSharedSizeScopeProperty)); - } - - - //------------------------------------------------------ - // - // Public Properties - // - //------------------------------------------------------ - - #region Public Properties - - /// - /// ShowGridLines property. - /// - public bool ShowGridLines - { - get { return (CheckFlagsAnd(Flags.ShowGridLinesPropertyValue)); } - set { SetValue(ShowGridLinesProperty, value); } } /// - /// Returns a ColumnDefinitionCollection of column definitions. + /// Gets or sets the row definitions for the grid. /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public ColumnDefinitionCollection ColumnDefinitions + public RowDefinitions RowDefinitions { get { if (_data == null) { _data = new ExtendedData(); } - if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitionCollection(this); } - return (_data.ColumnDefinitions); - } - } - - /// - /// Returns a RowDefinitionCollection of row definitions. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public RowDefinitionCollection RowDefinitions - { - get - { - if (_data == null) { _data = new ExtendedData(); } - if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitionCollection(this); } + if (_rowDefinitions == null) + { + RowDefinitions = new RowDefinitions(); + } - return (_data.RowDefinitions); + return _rowDefinitions; } - } - - #endregion Public Properties - - //------------------------------------------------------ - // - // Protected Methods - // - //------------------------------------------------------ - - #region Protected Methods - /// - /// Derived class must implement to support Visual Children. The method must return - /// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1. - /// - /// By default a Visual does not have any Children. - /// - /// Remark: - /// During this virtual call it is not valid to modify the Visual tree. - /// - protected override Visual GetVisualChild(int index) - { - // because "base.Count + 1" for GridLinesRenderer - // argument checking done at the base class - if (index == base.VisualChildrenCount) + set { - if (_gridLinesRenderer == null) + if (_rowDefinitions != null) { - throw new ArgumentOutOfRangeException("index", index, SR.Get(SRID.Visual_ArgumentOutOfRange)); + throw new NotSupportedException("Reassigning RowDefinitions not yet implemented."); } - return _gridLinesRenderer; - } - else return base.GetVisualChild(index); - } - /// - /// Derived classes override this property to enable the Visual code to enumerate - /// the Visual Children. Derived classes need to return the number of Children - /// from this method. - /// - /// By default a Visual does not have any Children. - /// - /// Remark: During this virtual method the Visual tree must not be modified. - /// - protected override int VisualChildrenCount - { - //since GridLinesRenderer has not been added as a child, so we do not subtract - get { return base.VisualChildrenCount + (_gridLinesRenderer != null ? 1 : 0); } + _rowDefinitions = value; + _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); + _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); + } } - /// /// Content measurement. /// @@ -315,8 +139,6 @@ namespace System.Windows.Controls try { - EnterCounterScope(Counters.MeasureOverride); - ListenToNotifications = true; MeasureOverrideInProgress = true; @@ -330,8 +152,9 @@ namespace System.Windows.Controls if (child != null) { child.Measure(constraint); - gridDesiredSize.Width = Math.Max(gridDesiredSize.Width, child.DesiredSize.Width); - gridDesiredSize.Height = Math.Max(gridDesiredSize.Height, child.DesiredSize.Height); + gridDesiredSize = new Size( + Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), + Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); } } } @@ -590,17 +413,14 @@ namespace System.Windows.Controls MeasureCellsGroup(extData.CellGroup4, constraint, false, false); - EnterCounter(Counters._CalculateDesiredSize); gridDesiredSize = new Size( CalculateDesiredSize(DefinitionsU), CalculateDesiredSize(DefinitionsV)); - ExitCounter(Counters._CalculateDesiredSize); } } finally { MeasureOverrideInProgress = false; - ExitCounterScope(Counters.MeasureOverride); } return (gridDesiredSize); @@ -614,17 +434,14 @@ namespace System.Windows.Controls { try { - EnterCounterScope(Counters.ArrangeOverride); ArrangeOverrideInProgress = true; if (_data == null) { - ControlCollection Children = InternalChildren; - for (int i = 0, count = Children.Count; i < count; ++i) { - Control child = Children[i]; + var child = Children[i]; if (child != null) { child.Arrange(new Rect(arrangeSize)); @@ -635,18 +452,12 @@ namespace System.Windows.Controls { Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); - EnterCounter(Counters._SetFinalSize); - SetFinalSize(DefinitionsU, arrangeSize.Width, true); SetFinalSize(DefinitionsV, arrangeSize.Height, false); - ExitCounter(Counters._SetFinalSize); - - ControlCollection Children = InternalChildren; - for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) { - Control cell = Children[currentCell]; + IControl cell = Children[currentCell]; if (cell == null) { continue; @@ -663,9 +474,7 @@ namespace System.Windows.Controls GetFinalSizeForRange(DefinitionsU, columnIndex, columnSpan), GetFinalSizeForRange(DefinitionsV, rowIndex, rowSpan)); - EnterCounter(Counters._ArrangeChildHelper2); cell.Arrange(cellRect); - ExitCounter(Counters._ArrangeChildHelper2); } // update render bound on grid lines renderer visual @@ -680,7 +489,6 @@ namespace System.Windows.Controls { SetValid(); ArrangeOverrideInProgress = false; - ExitCounterScope(Counters.ArrangeOverride); } return (arrangeSize); } @@ -697,7 +505,6 @@ namespace System.Windows.Controls base.OnVisualChildrenChanged(visualAdded, visualRemoved); } - #endregion Protected Methods //------------------------------------------------------ // @@ -821,15 +628,11 @@ namespace System.Windows.Controls /// private void ValidateCells() { - EnterCounter(Counters._ValidateCells); - if (CellsStructureDirty) { ValidateCellsCore(); CellsStructureDirty = false; } - - ExitCounter(Counters._ValidateCells); } /// @@ -837,7 +640,6 @@ namespace System.Windows.Controls /// private void ValidateCellsCore() { - ControlCollection Children = InternalChildren; ExtendedData extData = ExtData; extData.CellCachesCollection = new CellCache[Children.Count]; @@ -852,7 +654,7 @@ namespace System.Windows.Controls for (int i = PrivateCells.Length - 1; i >= 0; --i) { - Control child = Children[i]; + var child = Children[i]; if (child == null) { continue; @@ -1312,7 +1114,7 @@ namespace System.Windows.Controls } EnterCounter(Counters.__MeasureChild); - Control child = InternalChildren[cell]; + IControl child = InternalChildren[cell]; if (child != null) { Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); @@ -2071,7 +1873,7 @@ namespace System.Windows.Controls if (useLayoutRounding) { roundingErrors[i] = definitions[i].SizeCache; - definitions[i].SizeCache = Control.RoundLayoutValue(definitions[i].SizeCache, dpi); + definitions[i].SizeCache = IControl.RoundLayoutValue(definitions[i].SizeCache, dpi); } } definitionIndices[starDefinitionsCount++] = i; @@ -2110,7 +1912,7 @@ namespace System.Windows.Controls if (useLayoutRounding) { roundingErrors[i] = definitions[i].SizeCache; - definitions[i].SizeCache = Control.RoundLayoutValue(definitions[i].SizeCache, dpi); + definitions[i].SizeCache = IControl.RoundLayoutValue(definitions[i].SizeCache, dpi); } allPreferredArrangeSize += definitions[i].SizeCache; @@ -2161,7 +1963,7 @@ namespace System.Windows.Controls if (useLayoutRounding) { roundingErrors[definitionIndices[i]] = definitions[definitionIndices[i]].SizeCache; - definitions[definitionIndices[i]].SizeCache = Control.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi); + definitions[definitionIndices[i]].SizeCache = IControl.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi); } allPreferredArrangeSize += definitions[definitionIndices[i]].SizeCache; @@ -2186,7 +1988,7 @@ namespace System.Windows.Controls if (useLayoutRounding) { roundingErrors[definitionIndex] = final; - final = Control.RoundLayoutValue(finalOld, dpi); + final = IControl.RoundLayoutValue(finalOld, dpi); final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); final = Math.Min(final, definitions[definitionIndex].SizeCache); } @@ -2213,7 +2015,7 @@ namespace System.Windows.Controls RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); double adjustedSize = allPreferredArrangeSize; - double dpiIncrement = Control.RoundLayoutValue(1.0, dpi); + double dpiIncrement = IControl.RoundLayoutValue(1.0, dpi); if (allPreferredArrangeSize > finalSize) { @@ -2612,7 +2414,7 @@ namespace System.Windows.Controls for (int i = 0; i < definitions.Length; ++i) { DefinitionBase def = definitions[i]; - double roundedSize = Control.RoundLayoutValue(def.SizeCache, dpi); + double roundedSize = IControl.RoundLayoutValue(def.SizeCache, dpi); roundingErrors[i] = (roundedSize - def.SizeCache); def.SizeCache = roundedSize; roundedTakenSize += roundedSize; @@ -2826,29 +2628,6 @@ namespace System.Windows.Controls } } - /// - /// Returns true if ColumnDefinitions collection is not empty - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool ShouldSerializeColumnDefinitions() - { - ExtendedData extData = ExtData; - return (extData != null - && extData.ColumnDefinitions != null - && extData.ColumnDefinitions.Count > 0); - } - - /// - /// Returns true if RowDefinitions collection is not empty - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool ShouldSerializeRowDefinitions() - { - ExtendedData extData = ExtData; - return (extData != null - && extData.RowDefinitions != null - && extData.RowDefinitions.Count > 0); - } /// /// Synchronized ShowGridLines property with the state of the grid's visual collection @@ -2991,16 +2770,6 @@ namespace System.Windows.Controls return (result != 2); } - #endregion Private Methods - - //------------------------------------------------------ - // - // Private Properties - // - //------------------------------------------------------ - - #region Private Properties - /// /// Private version returning array of column definitions. /// @@ -4036,27 +3805,6 @@ namespace System.Windows.Controls return (false); } - public Object Current - { - get - { - if (_currentEnumerator == -1) - { -#pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception - throw new InvalidOperationException(SR.Get(SRID.EnumeratorNotStarted)); - } - if (_currentEnumerator >= 3) - { -#pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception - throw new InvalidOperationException(SR.Get(SRID.EnumeratorReachedEnd)); - } - - // assert below is not true anymore since ControlCollection allowes for null Children - //Debug.Assert(_currentChild != null); - return (_currentChild); - } - } - public void Reset() { _currentEnumerator = -1; @@ -4070,7 +3818,7 @@ namespace System.Windows.Controls private Object _currentChild; private ColumnDefinitionCollection.Enumerator _enumerator0; private RowDefinitionCollection.Enumerator _enumerator1; - private ControlCollection _enumerator2Collection; + private Controls _enumerator2Collection; private int _enumerator2Index; private int _enumerator2Count; } @@ -4157,7 +3905,5 @@ namespace System.Windows.Controls private static readonly Pen s_evenDashPen; // second pen to draw dash private static readonly Point c_zeroPoint = new Point(0, 0); } - - #endregion Private Structures Classes } } \ No newline at end of file From f9ebfc232d685a9fe8e06c305c1cd52a0058c287 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 23 May 2019 14:00:08 +0800 Subject: [PATCH 017/130] Uncomment Avalonia's Grid and exclude it from build temporarily. --- .../Avalonia.Controls.csproj | 1 + src/Avalonia.Controls/Grid.cs | 1199 ++++++++--------- 2 files changed, 600 insertions(+), 600 deletions(-) diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 32331d29ab..eabe136791 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -14,4 +14,5 @@ + diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 98cd5aad02..ad5e96376d 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1,601 +1,600 @@ -// // Copyright (c) The Avalonia Project. All rights reserved. -// // Licensed under the MIT license. See licence.md file in the project root for full license information. - -// using System; -// using System.Collections.Generic; -// using System.Diagnostics; -// using System.Linq; -// using System.Reactive.Linq; -// using System.Runtime.CompilerServices; -// using Avalonia.Collections; -// using Avalonia.Controls.Utils; -// using Avalonia.VisualTree; -// using JetBrains.Annotations; - -// namespace Avalonia.Controls -// { -// /// -// /// Lays out child controls according to a grid. -// /// -// public class Grid : Panel -// { -// /// -// /// Defines the Column attached property. -// /// -// public static readonly AttachedProperty ColumnProperty = -// AvaloniaProperty.RegisterAttached( -// "Column", -// validate: ValidateColumn); - -// /// -// /// Defines the ColumnSpan attached property. -// /// -// public static readonly AttachedProperty ColumnSpanProperty = -// AvaloniaProperty.RegisterAttached("ColumnSpan", 1); - -// /// -// /// Defines the Row attached property. -// /// -// public static readonly AttachedProperty RowProperty = -// AvaloniaProperty.RegisterAttached( -// "Row", -// validate: ValidateRow); - -// /// -// /// Defines the RowSpan attached property. -// /// -// public static readonly AttachedProperty RowSpanProperty = -// AvaloniaProperty.RegisterAttached("RowSpan", 1); - -// public static readonly AttachedProperty IsSharedSizeScopeProperty = -// AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); - -// protected override void OnMeasureInvalidated() -// { -// base.OnMeasureInvalidated(); -// _sharedSizeHost?.InvalidateMeasure(this); -// } - -// private SharedSizeScopeHost _sharedSizeHost; - -// /// -// /// Defines the SharedSizeScopeHost private property. -// /// The ampersands are used to make accessing the property via xaml inconvenient. -// /// -// internal static readonly AttachedProperty s_sharedSizeScopeHostProperty = -// AvaloniaProperty.RegisterAttached("&&SharedSizeScopeHost"); - -// private ColumnDefinitions _columnDefinitions; - -// private RowDefinitions _rowDefinitions; - -// static Grid() -// { -// AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); -// IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopeChanged); -// } - -// public Grid() -// { -// this.AttachedToVisualTree += Grid_AttachedToVisualTree; -// this.DetachedFromVisualTree += Grid_DetachedFromVisualTree; -// } - -// /// -// /// Gets or sets the columns definitions for the grid. -// /// -// public ColumnDefinitions ColumnDefinitions -// { -// get -// { -// if (_columnDefinitions == null) -// { -// ColumnDefinitions = new ColumnDefinitions(); -// } - -// return _columnDefinitions; -// } - -// set -// { -// if (_columnDefinitions != null) -// { -// throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented."); -// } - -// _columnDefinitions = value; -// _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); -// _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); -// } -// } - -// /// -// /// Gets or sets the row definitions for the grid. -// /// -// public RowDefinitions RowDefinitions -// { -// get -// { -// if (_rowDefinitions == null) -// { -// RowDefinitions = new RowDefinitions(); -// } - -// return _rowDefinitions; -// } - -// set -// { -// if (_rowDefinitions != null) -// { -// throw new NotSupportedException("Reassigning RowDefinitions not yet implemented."); -// } - -// _rowDefinitions = value; -// _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); -// _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); -// } -// } - -// /// -// /// Gets the value of the Column attached property for a control. -// /// -// /// The control. -// /// The control's column. -// public static int GetColumn(AvaloniaObject element) -// { -// return element.GetValue(ColumnProperty); -// } - -// /// -// /// Gets the value of the ColumnSpan attached property for a control. -// /// -// /// The control. -// /// The control's column span. -// public static int GetColumnSpan(AvaloniaObject element) -// { -// return element.GetValue(ColumnSpanProperty); -// } - -// /// -// /// Gets the value of the Row attached property for a control. -// /// -// /// The control. -// /// The control's row. -// public static int GetRow(AvaloniaObject element) -// { -// return element.GetValue(RowProperty); -// } - -// /// -// /// Gets the value of the RowSpan attached property for a control. -// /// -// /// The control. -// /// The control's row span. -// public static int GetRowSpan(AvaloniaObject element) -// { -// return element.GetValue(RowSpanProperty); -// } - - -// /// -// /// Gets the value of the IsSharedSizeScope attached property for a control. -// /// -// /// The control. -// /// The control's IsSharedSizeScope value. -// public static bool GetIsSharedSizeScope(AvaloniaObject element) -// { -// return element.GetValue(IsSharedSizeScopeProperty); -// } - -// /// -// /// Sets the value of the Column attached property for a control. -// /// -// /// The control. -// /// The column value. -// public static void SetColumn(AvaloniaObject element, int value) -// { -// element.SetValue(ColumnProperty, value); -// } - -// /// -// /// Sets the value of the ColumnSpan attached property for a control. -// /// -// /// The control. -// /// The column span value. -// public static void SetColumnSpan(AvaloniaObject element, int value) -// { -// element.SetValue(ColumnSpanProperty, value); -// } - -// /// -// /// Sets the value of the Row attached property for a control. -// /// -// /// The control. -// /// The row value. -// public static void SetRow(AvaloniaObject element, int value) -// { -// element.SetValue(RowProperty, value); -// } - -// /// -// /// Sets the value of the RowSpan attached property for a control. -// /// -// /// The control. -// /// The row span value. -// public static void SetRowSpan(AvaloniaObject element, int value) -// { -// element.SetValue(RowSpanProperty, value); -// } - -// /// -// /// Sets the value of IsSharedSizeScope property for a control. -// /// -// /// The control. -// /// The IsSharedSizeScope value. -// public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) -// { -// element.SetValue(IsSharedSizeScopeProperty, value); -// } - -// /// -// /// Gets the result of the last column measurement. -// /// Use this result to reduce the arrange calculation. -// /// -// private GridLayout.MeasureResult _columnMeasureCache; - -// /// -// /// Gets the result of the last row measurement. -// /// Use this result to reduce the arrange calculation. -// /// -// private GridLayout.MeasureResult _rowMeasureCache; - -// /// -// /// Gets the row layout as of the last measure. -// /// -// private GridLayout _rowLayoutCache; - -// /// -// /// Gets the column layout as of the last measure. -// /// -// private GridLayout _columnLayoutCache; - -// /// -// /// Measures the grid. -// /// -// /// The available size. -// /// The desired size of the control. -// protected override Size MeasureOverride(Size constraint) -// { -// // Situation 1/2: -// // If the grid doesn't have any column/row definitions, it behaves like a normal panel. -// // GridLayout supports this situation but we handle this separately for performance. - -// if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) -// { -// var maxWidth = 0.0; -// var maxHeight = 0.0; -// foreach (var child in Children.OfType()) -// { -// child.Measure(constraint); -// maxWidth = Math.Max(maxWidth, child.DesiredSize.Width); -// maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); -// } - -// maxWidth = Math.Min(maxWidth, constraint.Width); -// maxHeight = Math.Min(maxHeight, constraint.Height); -// return new Size(maxWidth, maxHeight); -// } - -// // Situation 2/2: -// // If the grid defines some columns or rows. -// // Debug Tip: -// // - GridLayout doesn't hold any state, so you can drag the debugger execution -// // arrow back to any statements and re-run them without any side-effect. - -// var measureCache = new Dictionary(); -// var (safeColumns, safeRows) = GetSafeColumnRows(); -// var columnLayout = new GridLayout(ColumnDefinitions); -// var rowLayout = new GridLayout(RowDefinitions); -// // Note: If a child stays in a * or Auto column/row, use constraint to measure it. -// columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width); -// rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height); - -// // Calculate measurement. -// var columnResult = columnLayout.Measure(constraint.Width); -// var rowResult = rowLayout.Measure(constraint.Height); - -// // Use the results of the measurement to measure the rest of the children. -// foreach (var child in Children.OfType()) -// { -// var (column, columnSpan) = safeColumns[child]; -// var (row, rowSpan) = safeRows[child]; -// var width = Enumerable.Range(column, columnSpan).Select(x => columnResult.LengthList[x]).Sum(); -// var height = Enumerable.Range(row, rowSpan).Select(x => rowResult.LengthList[x]).Sum(); - -// MeasureOnce(child, new Size(width, height)); -// } - -// // Cache the measure result and return the desired size. -// _columnMeasureCache = columnResult; -// _rowMeasureCache = rowResult; -// _rowLayoutCache = rowLayout; -// _columnLayoutCache = columnLayout; - -// if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) -// { -// _sharedSizeHost.UpdateMeasureStatus(this, rowResult, columnResult); -// } - -// return new Size(columnResult.DesiredLength, rowResult.DesiredLength); - -// // Measure each child only once. -// // If a child has been measured, it will just return the desired size. -// Size MeasureOnce(Control child, Size size) -// { -// if (measureCache.TryGetValue(child, out var desiredSize)) -// { -// return desiredSize; -// } - -// child.Measure(size); -// desiredSize = child.DesiredSize; -// measureCache[child] = desiredSize; -// return desiredSize; -// } -// } - -// /// -// /// Arranges the grid's children. -// /// -// /// The size allocated to the control. -// /// The space taken. -// protected override Size ArrangeOverride(Size finalSize) -// { -// // Situation 1/2: -// // If the grid doesn't have any column/row definitions, it behaves like a normal panel. -// // GridLayout supports this situation but we handle this separately for performance. - -// if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) -// { -// foreach (var child in Children.OfType()) -// { -// child.Arrange(new Rect(finalSize)); -// } - -// return finalSize; -// } - -// // Situation 2/2: -// // If the grid defines some columns or rows. -// // Debug Tip: -// // - GridLayout doesn't hold any state, so you can drag the debugger execution -// // arrow back to any statements and re-run them without any side-effect. - -// var (safeColumns, safeRows) = GetSafeColumnRows(); -// var columnLayout = _columnLayoutCache; -// var rowLayout = _rowLayoutCache; - -// var rowCache = _rowMeasureCache; -// var columnCache = _columnMeasureCache; - -// if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) -// { -// (rowCache, columnCache) = _sharedSizeHost.HandleArrange(this, _rowMeasureCache, _columnMeasureCache); +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reactive.Linq; +using System.Runtime.CompilerServices; +using Avalonia.Collections; +using Avalonia.Controls.Utils; +using Avalonia.VisualTree; +using JetBrains.Annotations; + +namespace Avalonia.Controls +{ + /// + /// Lays out child controls according to a grid. + /// + public class Grid : Panel + { + /// + /// Defines the Column attached property. + /// + public static readonly AttachedProperty ColumnProperty = + AvaloniaProperty.RegisterAttached( + "Column", + validate: ValidateColumn); + + /// + /// Defines the ColumnSpan attached property. + /// + public static readonly AttachedProperty ColumnSpanProperty = + AvaloniaProperty.RegisterAttached("ColumnSpan", 1); + + /// + /// Defines the Row attached property. + /// + public static readonly AttachedProperty RowProperty = + AvaloniaProperty.RegisterAttached( + "Row", + validate: ValidateRow); + + /// + /// Defines the RowSpan attached property. + /// + public static readonly AttachedProperty RowSpanProperty = + AvaloniaProperty.RegisterAttached("RowSpan", 1); + + public static readonly AttachedProperty IsSharedSizeScopeProperty = + AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); + + protected override void OnMeasureInvalidated() + { + base.OnMeasureInvalidated(); + _sharedSizeHost?.InvalidateMeasure(this); + } + + private SharedSizeScopeHost _sharedSizeHost; + + /// + /// Defines the SharedSizeScopeHost private property. + /// The ampersands are used to make accessing the property via xaml inconvenient. + /// + internal static readonly AttachedProperty s_sharedSizeScopeHostProperty = + AvaloniaProperty.RegisterAttached("&&SharedSizeScopeHost"); + + private ColumnDefinitions _columnDefinitions; + + private RowDefinitions _rowDefinitions; + + static Grid() + { + AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); + IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopeChanged); + } + + public Grid() + { + this.AttachedToVisualTree += Grid_AttachedToVisualTree; + this.DetachedFromVisualTree += Grid_DetachedFromVisualTree; + } + + /// + /// Gets or sets the columns definitions for the grid. + /// + public ColumnDefinitions ColumnDefinitions + { + get + { + if (_columnDefinitions == null) + { + ColumnDefinitions = new ColumnDefinitions(); + } + + return _columnDefinitions; + } + + set + { + if (_columnDefinitions != null) + { + throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented."); + } + + _columnDefinitions = value; + _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); + _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); + } + } + + /// + /// Gets or sets the row definitions for the grid. + /// + public RowDefinitions RowDefinitions + { + get + { + if (_rowDefinitions == null) + { + RowDefinitions = new RowDefinitions(); + } + + return _rowDefinitions; + } + + set + { + if (_rowDefinitions != null) + { + throw new NotSupportedException("Reassigning RowDefinitions not yet implemented."); + } + + _rowDefinitions = value; + _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); + _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); + } + } + + /// + /// Gets the value of the Column attached property for a control. + /// + /// The control. + /// The control's column. + public static int GetColumn(AvaloniaObject element) + { + return element.GetValue(ColumnProperty); + } + + /// + /// Gets the value of the ColumnSpan attached property for a control. + /// + /// The control. + /// The control's column span. + public static int GetColumnSpan(AvaloniaObject element) + { + return element.GetValue(ColumnSpanProperty); + } + + /// + /// Gets the value of the Row attached property for a control. + /// + /// The control. + /// The control's row. + public static int GetRow(AvaloniaObject element) + { + return element.GetValue(RowProperty); + } + + /// + /// Gets the value of the RowSpan attached property for a control. + /// + /// The control. + /// The control's row span. + public static int GetRowSpan(AvaloniaObject element) + { + return element.GetValue(RowSpanProperty); + } + + + /// + /// Gets the value of the IsSharedSizeScope attached property for a control. + /// + /// The control. + /// The control's IsSharedSizeScope value. + public static bool GetIsSharedSizeScope(AvaloniaObject element) + { + return element.GetValue(IsSharedSizeScopeProperty); + } + + /// + /// Sets the value of the Column attached property for a control. + /// + /// The control. + /// The column value. + public static void SetColumn(AvaloniaObject element, int value) + { + element.SetValue(ColumnProperty, value); + } + + /// + /// Sets the value of the ColumnSpan attached property for a control. + /// + /// The control. + /// The column span value. + public static void SetColumnSpan(AvaloniaObject element, int value) + { + element.SetValue(ColumnSpanProperty, value); + } + + /// + /// Sets the value of the Row attached property for a control. + /// + /// The control. + /// The row value. + public static void SetRow(AvaloniaObject element, int value) + { + element.SetValue(RowProperty, value); + } + + /// + /// Sets the value of the RowSpan attached property for a control. + /// + /// The control. + /// The row span value. + public static void SetRowSpan(AvaloniaObject element, int value) + { + element.SetValue(RowSpanProperty, value); + } + + /// + /// Sets the value of IsSharedSizeScope property for a control. + /// + /// The control. + /// The IsSharedSizeScope value. + public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) + { + element.SetValue(IsSharedSizeScopeProperty, value); + } + + /// + /// Gets the result of the last column measurement. + /// Use this result to reduce the arrange calculation. + /// + private GridLayout.MeasureResult _columnMeasureCache; + + /// + /// Gets the result of the last row measurement. + /// Use this result to reduce the arrange calculation. + /// + private GridLayout.MeasureResult _rowMeasureCache; + + /// + /// Gets the row layout as of the last measure. + /// + private GridLayout _rowLayoutCache; + + /// + /// Gets the column layout as of the last measure. + /// + private GridLayout _columnLayoutCache; + + /// + /// Measures the grid. + /// + /// The available size. + /// The desired size of the control. + protected override Size MeasureOverride(Size constraint) + { + // Situation 1/2: + // If the grid doesn't have any column/row definitions, it behaves like a normal panel. + // GridLayout supports this situation but we handle this separately for performance. + + if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) + { + var maxWidth = 0.0; + var maxHeight = 0.0; + foreach (var child in Children.OfType()) + { + child.Measure(constraint); + maxWidth = Math.Max(maxWidth, child.DesiredSize.Width); + maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); + } + + maxWidth = Math.Min(maxWidth, constraint.Width); + maxHeight = Math.Min(maxHeight, constraint.Height); + return new Size(maxWidth, maxHeight); + } + + // Situation 2/2: + // If the grid defines some columns or rows. + // Debug Tip: + // - GridLayout doesn't hold any state, so you can drag the debugger execution + // arrow back to any statements and re-run them without any side-effect. + + var measureCache = new Dictionary(); + var (safeColumns, safeRows) = GetSafeColumnRows(); + var columnLayout = new GridLayout(ColumnDefinitions); + var rowLayout = new GridLayout(RowDefinitions); + // Note: If a child stays in a * or Auto column/row, use constraint to measure it. + columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width); + rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height); + + // Calculate measurement. + var columnResult = columnLayout.Measure(constraint.Width); + var rowResult = rowLayout.Measure(constraint.Height); + + // Use the results of the measurement to measure the rest of the children. + foreach (var child in Children.OfType()) + { + var (column, columnSpan) = safeColumns[child]; + var (row, rowSpan) = safeRows[child]; + var width = Enumerable.Range(column, columnSpan).Select(x => columnResult.LengthList[x]).Sum(); + var height = Enumerable.Range(row, rowSpan).Select(x => rowResult.LengthList[x]).Sum(); + + MeasureOnce(child, new Size(width, height)); + } + + // Cache the measure result and return the desired size. + _columnMeasureCache = columnResult; + _rowMeasureCache = rowResult; + _rowLayoutCache = rowLayout; + _columnLayoutCache = columnLayout; + + if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) + { + _sharedSizeHost.UpdateMeasureStatus(this, rowResult, columnResult); + } + + return new Size(columnResult.DesiredLength, rowResult.DesiredLength); + + // Measure each child only once. + // If a child has been measured, it will just return the desired size. + Size MeasureOnce(Control child, Size size) + { + if (measureCache.TryGetValue(child, out var desiredSize)) + { + return desiredSize; + } + + child.Measure(size); + desiredSize = child.DesiredSize; + measureCache[child] = desiredSize; + return desiredSize; + } + } + + /// + /// Arranges the grid's children. + /// + /// The size allocated to the control. + /// The space taken. + protected override Size ArrangeOverride(Size finalSize) + { + // Situation 1/2: + // If the grid doesn't have any column/row definitions, it behaves like a normal panel. + // GridLayout supports this situation but we handle this separately for performance. + + if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) + { + foreach (var child in Children.OfType()) + { + child.Arrange(new Rect(finalSize)); + } + + return finalSize; + } + + // Situation 2/2: + // If the grid defines some columns or rows. + // Debug Tip: + // - GridLayout doesn't hold any state, so you can drag the debugger execution + // arrow back to any statements and re-run them without any side-effect. + + var (safeColumns, safeRows) = GetSafeColumnRows(); + var columnLayout = _columnLayoutCache; + var rowLayout = _rowLayoutCache; + + var rowCache = _rowMeasureCache; + var columnCache = _columnMeasureCache; + + if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) + { + (rowCache, columnCache) = _sharedSizeHost.HandleArrange(this, _rowMeasureCache, _columnMeasureCache); -// rowCache = rowLayout.Measure(finalSize.Height, rowCache.LeanLengthList); -// columnCache = columnLayout.Measure(finalSize.Width, columnCache.LeanLengthList); -// } - -// // Calculate for arrange result. -// var columnResult = columnLayout.Arrange(finalSize.Width, columnCache); -// var rowResult = rowLayout.Arrange(finalSize.Height, rowCache); -// // Arrange the children. -// foreach (var child in Children.OfType()) -// { -// var (column, columnSpan) = safeColumns[child]; -// var (row, rowSpan) = safeRows[child]; -// var x = Enumerable.Range(0, column).Sum(c => columnResult.LengthList[c]); -// var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]); -// var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]); -// var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]); -// child.Arrange(new Rect(x, y, width, height)); -// } - -// // Assign the actual width. -// for (var i = 0; i < ColumnDefinitions.Count; i++) -// { -// ColumnDefinitions[i].ActualWidth = columnResult.LengthList[i]; -// } - -// // Assign the actual height. -// for (var i = 0; i < RowDefinitions.Count; i++) -// { -// RowDefinitions[i].ActualHeight = rowResult.LengthList[i]; -// } - -// // Return the render size. -// return finalSize; -// } - -// /// -// /// Tests whether this grid belongs to a shared size scope. -// /// -// /// True if the grid is registered in a shared size scope. -// internal bool HasSharedSizeScope() -// { -// return _sharedSizeHost != null; -// } - -// /// -// /// Called when the SharedSizeScope for a given grid has changed. -// /// Unregisters the grid from it's current scope and finds a new one (if any) -// /// -// /// -// /// This method, while not efficient, correctly handles nested scopes, with any order of scope changes. -// /// -// internal void SharedScopeChanged() -// { -// _sharedSizeHost?.UnegisterGrid(this); - -// _sharedSizeHost = null; -// var scope = this.GetVisualAncestors().OfType() -// .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); - -// if (scope != null) -// { -// _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); -// _sharedSizeHost.RegisterGrid(this); -// } - -// InvalidateMeasure(); -// } - -// /// -// /// Callback when a grid is attached to the visual tree. Finds the innermost SharedSizeScope and registers the grid -// /// in it. -// /// -// /// The source of the event. -// /// The event arguments. -// private void Grid_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) -// { -// var scope = -// new Control[] { this }.Concat(this.GetVisualAncestors().OfType()) -// .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); - -// if (_sharedSizeHost != null) -// throw new AvaloniaInternalException("Shared size scope already present when attaching to visual tree!"); - -// if (scope != null) -// { -// _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); -// _sharedSizeHost.RegisterGrid(this); -// } -// } - -// /// -// /// Callback when a grid is detached from the visual tree. Unregisters the grid from its SharedSizeScope if any. -// /// -// /// The source of the event. -// /// The event arguments. -// private void Grid_DetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) -// { -// _sharedSizeHost?.UnegisterGrid(this); -// _sharedSizeHost = null; -// } - - -// /// -// /// Get the safe column/columnspan and safe row/rowspan. -// /// This method ensures that none of the children has a column/row outside the bounds of the definitions. -// /// -// [Pure] -// private (Dictionary safeColumns, -// Dictionary safeRows) GetSafeColumnRows() -// { -// var columnCount = ColumnDefinitions.Count; -// var rowCount = RowDefinitions.Count; -// columnCount = columnCount == 0 ? 1 : columnCount; -// rowCount = rowCount == 0 ? 1 : rowCount; -// var safeColumns = Children.OfType().ToDictionary(child => child, -// child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child))); -// var safeRows = Children.OfType().ToDictionary(child => child, -// child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child))); -// return (safeColumns, safeRows); -// } - -// /// -// /// Gets the safe row/column and rowspan/columnspan for a specified range. -// /// The user may assign row/column properties outside the bounds of the row/column count, this method coerces them inside. -// /// -// /// The row or column count. -// /// The row or column that the user assigned. -// /// The rowspan or columnspan that the user assigned. -// /// The safe row/column and rowspan/columnspan. -// [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] -// private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan) -// { -// var index = userIndex; -// var span = userSpan; - -// if (index < 0) -// { -// span = index + span; -// index = 0; -// } - -// if (span <= 0) -// { -// span = 1; -// } - -// if (userIndex >= length) -// { -// index = length - 1; -// span = 1; -// } -// else if (userIndex + userSpan > length) -// { -// span = length - userIndex; -// } - -// return (index, span); -// } - -// private static int ValidateColumn(AvaloniaObject o, int value) -// { -// if (value < 0) -// { -// throw new ArgumentException("Invalid Grid.Column value."); -// } - -// return value; -// } - -// private static int ValidateRow(AvaloniaObject o, int value) -// { -// if (value < 0) -// { -// throw new ArgumentException("Invalid Grid.Row value."); -// } - -// return value; -// } - -// /// -// /// Called when the value of changes for a control. -// /// -// /// The control that triggered the change. -// /// Change arguments. -// private static void IsSharedSizeScopeChanged(Control source, AvaloniaPropertyChangedEventArgs arg2) -// { -// var shouldDispose = (arg2.OldValue is bool d) && d; -// if (shouldDispose) -// { -// var host = source.GetValue(s_sharedSizeScopeHostProperty) as SharedSizeScopeHost; -// if (host == null) -// throw new AvaloniaInternalException("SharedScopeHost wasn't set when IsSharedSizeScope was true!"); -// host.Dispose(); -// source.ClearValue(s_sharedSizeScopeHostProperty); -// } - -// var shouldAssign = (arg2.NewValue is bool a) && a; -// if (shouldAssign) -// { -// if (source.GetValue(s_sharedSizeScopeHostProperty) != null) -// throw new AvaloniaInternalException("SharedScopeHost was already set when IsSharedSizeScope is only now being set to true!"); -// source.SetValue(s_sharedSizeScopeHostProperty, new SharedSizeScopeHost()); -// } - -// // if the scope has changed, notify the descendant grids that they need to update. -// if (source.GetVisualRoot() != null && shouldAssign || shouldDispose) -// { -// var participatingGrids = new[] { source }.Concat(source.GetVisualDescendants()).OfType(); - -// foreach (var grid in participatingGrids) -// grid.SharedScopeChanged(); - -// } -// } -// } -// } + rowCache = rowLayout.Measure(finalSize.Height, rowCache.LeanLengthList); + columnCache = columnLayout.Measure(finalSize.Width, columnCache.LeanLengthList); + } + + // Calculate for arrange result. + var columnResult = columnLayout.Arrange(finalSize.Width, columnCache); + var rowResult = rowLayout.Arrange(finalSize.Height, rowCache); + // Arrange the children. + foreach (var child in Children.OfType()) + { + var (column, columnSpan) = safeColumns[child]; + var (row, rowSpan) = safeRows[child]; + var x = Enumerable.Range(0, column).Sum(c => columnResult.LengthList[c]); + var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]); + var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]); + var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]); + child.Arrange(new Rect(x, y, width, height)); + } + + // Assign the actual width. + for (var i = 0; i < ColumnDefinitions.Count; i++) + { + ColumnDefinitions[i].ActualWidth = columnResult.LengthList[i]; + } + + // Assign the actual height. + for (var i = 0; i < RowDefinitions.Count; i++) + { + RowDefinitions[i].ActualHeight = rowResult.LengthList[i]; + } + + // Return the render size. + return finalSize; + } + + /// + /// Tests whether this grid belongs to a shared size scope. + /// + /// True if the grid is registered in a shared size scope. + internal bool HasSharedSizeScope() + { + return _sharedSizeHost != null; + } + + /// + /// Called when the SharedSizeScope for a given grid has changed. + /// Unregisters the grid from it's current scope and finds a new one (if any) + /// + /// + /// This method, while not efficient, correctly handles nested scopes, with any order of scope changes. + /// + internal void SharedScopeChanged() + { + _sharedSizeHost?.UnegisterGrid(this); + + _sharedSizeHost = null; + var scope = this.GetVisualAncestors().OfType() + .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); + + if (scope != null) + { + _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); + _sharedSizeHost.RegisterGrid(this); + } + + InvalidateMeasure(); + } + + /// + /// Callback when a grid is attached to the visual tree. Finds the innermost SharedSizeScope and registers the grid + /// in it. + /// + /// The source of the event. + /// The event arguments. + private void Grid_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) + { + var scope = + new Control[] { this }.Concat(this.GetVisualAncestors().OfType()) + .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); + + if (_sharedSizeHost != null) + throw new AvaloniaInternalException("Shared size scope already present when attaching to visual tree!"); + + if (scope != null) + { + _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); + _sharedSizeHost.RegisterGrid(this); + } + } + + /// + /// Callback when a grid is detached from the visual tree. Unregisters the grid from its SharedSizeScope if any. + /// + /// The source of the event. + /// The event arguments. + private void Grid_DetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) + { + _sharedSizeHost?.UnegisterGrid(this); + _sharedSizeHost = null; + } + + + /// + /// Get the safe column/columnspan and safe row/rowspan. + /// This method ensures that none of the children has a column/row outside the bounds of the definitions. + /// + [Pure] + private (Dictionary safeColumns, + Dictionary safeRows) GetSafeColumnRows() + { + var columnCount = ColumnDefinitions.Count; + var rowCount = RowDefinitions.Count; + columnCount = columnCount == 0 ? 1 : columnCount; + rowCount = rowCount == 0 ? 1 : rowCount; + var safeColumns = Children.OfType().ToDictionary(child => child, + child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child))); + var safeRows = Children.OfType().ToDictionary(child => child, + child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child))); + return (safeColumns, safeRows); + } + + /// + /// Gets the safe row/column and rowspan/columnspan for a specified range. + /// The user may assign row/column properties outside the bounds of the row/column count, this method coerces them inside. + /// + /// The row or column count. + /// The row or column that the user assigned. + /// The rowspan or columnspan that the user assigned. + /// The safe row/column and rowspan/columnspan. + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan) + { + var index = userIndex; + var span = userSpan; + + if (index < 0) + { + span = index + span; + index = 0; + } + + if (span <= 0) + { + span = 1; + } + + if (userIndex >= length) + { + index = length - 1; + span = 1; + } + else if (userIndex + userSpan > length) + { + span = length - userIndex; + } + + return (index, span); + } + + private static int ValidateColumn(AvaloniaObject o, int value) + { + if (value < 0) + { + throw new ArgumentException("Invalid Grid.Column value."); + } + + return value; + } + + private static int ValidateRow(AvaloniaObject o, int value) + { + if (value < 0) + { + throw new ArgumentException("Invalid Grid.Row value."); + } + + return value; + } + + /// + /// Called when the value of changes for a control. + /// + /// The control that triggered the change. + /// Change arguments. + private static void IsSharedSizeScopeChanged(Control source, AvaloniaPropertyChangedEventArgs arg2) + { + var shouldDispose = (arg2.OldValue is bool d) && d; + if (shouldDispose) + { + var host = source.GetValue(s_sharedSizeScopeHostProperty) as SharedSizeScopeHost; + if (host == null) + throw new AvaloniaInternalException("SharedScopeHost wasn't set when IsSharedSizeScope was true!"); + host.Dispose(); + source.ClearValue(s_sharedSizeScopeHostProperty); + } + + var shouldAssign = (arg2.NewValue is bool a) && a; + if (shouldAssign) + { + if (source.GetValue(s_sharedSizeScopeHostProperty) != null) + throw new AvaloniaInternalException("SharedScopeHost was already set when IsSharedSizeScope is only now being set to true!"); + source.SetValue(s_sharedSizeScopeHostProperty, new SharedSizeScopeHost()); + } + + // if the scope has changed, notify the descendant grids that they need to update. + if (source.GetVisualRoot() != null && shouldAssign || shouldDispose) + { + var participatingGrids = new[] { source }.Concat(source.GetVisualDescendants()).OfType(); + + foreach (var grid in participatingGrids) + grid.SharedScopeChanged(); + } + } + } +} From d39d132d103e0a2c6a1309418b979e51b077ab44 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 23 May 2019 14:09:12 +0800 Subject: [PATCH 018/130] Part 3 of n --- src/Avalonia.Controls/GridWPF.cs | 97 +++++++------------------------- 1 file changed, 21 insertions(+), 76 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index fc498608e3..0c8f571506 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -165,7 +165,7 @@ namespace Avalonia.Controls bool sizeToContentV = double.IsPositiveInfinity(constraint.Height); // Clear index information and rounding errors - if (RowDefinitionCollectionDirty || ColumnDefinitionCollectionDirty) + if (RowDefinitionsDirty || ColumnDefinitionsDirty) { if (_definitionIndices != null) { @@ -493,27 +493,6 @@ namespace Avalonia.Controls return (arrangeSize); } - /// - /// - /// - protected internal override void OnVisualChildrenChanged( - DependencyObject visualAdded, - DependencyObject visualRemoved) - { - CellsStructureDirty = true; - - base.OnVisualChildrenChanged(visualAdded, visualRemoved); - } - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - /// /// Invalidates grid caches and makes the grid dirty for measure. /// @@ -533,10 +512,10 @@ namespace Avalonia.Controls { double value = 0.0; - Invariant.Assert(_data != null); + Contract.Requires(_data != null); // actual value calculations require structure to be up-to-date - if (!ColumnDefinitionCollectionDirty) + if (!ColumnDefinitionsDirty) { DefinitionBase[] definitions = DefinitionsU; value = definitions[(columnIndex + 1) % definitions.Length].FinalOffset; @@ -555,10 +534,10 @@ namespace Avalonia.Controls { double value = 0.0; - Invariant.Assert(_data != null); + Contract.Requires(_data != null); // actual value calculations require structure to be up-to-date - if (!RowDefinitionCollectionDirty) + if (!RowDefinitionsDirty) { DefinitionBase[] definitions = DefinitionsV; value = definitions[(rowIndex + 1) % definitions.Length].FinalOffset; @@ -567,16 +546,6 @@ namespace Avalonia.Controls return (value); } - #endregion Internal Methods - - //------------------------------------------------------ - // - // Internal Properties - // - //------------------------------------------------------ - - #region Internal Properties - /// /// Convenience accessor to MeasureOverrideInProgress bit flag. /// @@ -598,7 +567,7 @@ namespace Avalonia.Controls /// /// Convenience accessor to ValidDefinitionsUStructure bit flag. /// - internal bool ColumnDefinitionCollectionDirty + internal bool ColumnDefinitionsDirty { get { return (!CheckFlagsAnd(Flags.ValidDefinitionsUStructure)); } set { SetFlags(!value, Flags.ValidDefinitionsUStructure); } @@ -607,22 +576,12 @@ namespace Avalonia.Controls /// /// Convenience accessor to ValidDefinitionsVStructure bit flag. /// - internal bool RowDefinitionCollectionDirty + internal bool RowDefinitionsDirty { get { return (!CheckFlagsAnd(Flags.ValidDefinitionsVStructure)); } set { SetFlags(!value, Flags.ValidDefinitionsVStructure); } } - #endregion Internal Properties - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - /// /// Lays out cells according to rows and columns, and creates lookup grids. /// @@ -665,11 +624,11 @@ namespace Avalonia.Controls // // read and cache child positioning properties // - + // read indices from the corresponding properties // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Length - 1); + cell.ColumnIndex = Math.Min(Grid.ColumnProperty (child), DefinitionsU.Length - 1); // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Length - 1); @@ -750,9 +709,7 @@ namespace Avalonia.Controls /// private void ValidateDefinitionsUStructure() { - EnterCounter(Counters._ValidateColsStructure); - - if (ColumnDefinitionCollectionDirty) + if (ColumnDefinitionsDirty) { ExtendedData extData = ExtData; @@ -779,12 +736,10 @@ namespace Avalonia.Controls } } - ColumnDefinitionCollectionDirty = false; + ColumnDefinitionsDirty = false; } Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Length > 0); - - ExitCounter(Counters._ValidateColsStructure); } /// @@ -797,9 +752,7 @@ namespace Avalonia.Controls /// private void ValidateDefinitionsVStructure() { - EnterCounter(Counters._ValidateRowsStructure); - - if (RowDefinitionCollectionDirty) + if (RowDefinitionsDirty) { ExtendedData extData = ExtData; @@ -826,12 +779,10 @@ namespace Avalonia.Controls } } - RowDefinitionCollectionDirty = false; + RowDefinitionsDirty = false; } Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Length > 0); - - ExitCounter(Counters._ValidateRowsStructure); } /// @@ -1071,8 +1022,6 @@ namespace Avalonia.Controls int cell, bool forceInfinityV) { - EnterCounter(Counters._MeasureCell); - double cellMeasureWidth; double cellMeasureHeight; @@ -1113,19 +1062,15 @@ namespace Avalonia.Controls PrivateCells[cell].RowSpan); } - EnterCounter(Counters.__MeasureChild); - IControl child = InternalChildren[cell]; + var child = Children[cell]; + if (child != null) { Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); child.Measure(childConstraint); } - ExitCounter(Counters.__MeasureChild); - - ExitCounter(Counters._MeasureCell); } - /// /// Calculates one dimensional measure size for given definitions' range. /// @@ -3032,8 +2977,8 @@ namespace Avalonia.Controls /// private class ExtendedData { - internal ColumnDefinitionCollection ColumnDefinitions; // collection of column definitions (logical tree support) - internal RowDefinitionCollection RowDefinitions; // collection of row definitions (logical tree support) + internal ColumnDefinitions ColumnDefinitions; // collection of column definitions (logical tree support) + internal RowDefinitions RowDefinitions; // collection of row definitions (logical tree support) internal DefinitionBase[] DefinitionsU; // collection of column definitions used during calc internal DefinitionBase[] DefinitionsV; // collection of row definitions used during calc internal CellCache[] CellCachesCollection; // backing store for logical Children @@ -3764,8 +3709,8 @@ namespace Avalonia.Controls { Debug.Assert(grid != null); _currentEnumerator = -1; - _enumerator0 = new ColumnDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.ColumnDefinitions : null); - _enumerator1 = new RowDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.RowDefinitions : null); + _enumerator0 = new ColumnDefinitions.Enumerator(grid.ExtData != null ? grid.ExtData.ColumnDefinitions : null); + _enumerator1 = new RowDefinitions.Enumerator(grid.ExtData != null ? grid.ExtData.RowDefinitions : null); // GridLineRenderer is NOT included into this enumerator. _enumerator2Index = 0; if (includeChildren) @@ -3816,8 +3761,8 @@ namespace Avalonia.Controls private int _currentEnumerator; private Object _currentChild; - private ColumnDefinitionCollection.Enumerator _enumerator0; - private RowDefinitionCollection.Enumerator _enumerator1; + private ColumnDefinitions.Enumerator _enumerator0; + private RowDefinitions.Enumerator _enumerator1; private Controls _enumerator2Collection; private int _enumerator2Index; private int _enumerator2Count; From 9f1d70e5926681eb8bc538bc84cbcb55f255e819 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 23 May 2019 18:52:44 +0800 Subject: [PATCH 019/130] Part 4 of n Removed more legacy stuff Added Math Functions needed from WPF's DoubleUtil. --- src/Avalonia.Base/Utilities/MathUtilities.cs | 123 +++ src/Avalonia.Controls/DefinitionBase.cs | 894 ++++++++++++++++++- src/Avalonia.Controls/GridWPF.cs | 468 +--------- 3 files changed, 1031 insertions(+), 454 deletions(-) diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index dcb3ef4a2b..2f138d9e83 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; + namespace Avalonia.Utilities { /// @@ -8,6 +10,127 @@ namespace Avalonia.Utilities /// public static class MathUtilities { + /// + /// AreClose - Returns whether or not two doubles are "close". That is, whether or + /// not they are within epsilon of each other. Note that this epsilon is proportional + /// to the numbers themselves to that AreClose survives scalar multiplication. + /// There are plenty of ways for this to return false even for numbers which + /// are theoretically identical, so no code calling this should fail to work if this + /// returns false. This is important enough to repeat: + /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be + /// used for optimizations *only*. + /// + /// + /// bool - the result of the AreClose comparision. + /// + /// The first double to compare. + /// The second double to compare. + public static bool AreClose(double value1, double value2) + { + //in case they are Infinities (then epsilon check does not work) + if (value1 == value2) return true; + double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * double.Epsilon; + double delta = value1 - value2; + return (-eps < delta) && (eps > delta); + } + + /// + /// LessThan - Returns whether or not the first double is less than the second double. + /// That is, whether or not the first is strictly less than *and* not within epsilon of + /// the other number. Note that this epsilon is proportional to the numbers themselves + /// to that AreClose survives scalar multiplication. Note, + /// There are plenty of ways for this to return false even for numbers which + /// are theoretically identical, so no code calling this should fail to work if this + /// returns false. This is important enough to repeat: + /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be + /// used for optimizations *only*. + /// + /// + /// bool - the result of the LessThan comparision. + /// + /// The first double to compare. + /// The second double to compare. + public static bool LessThan(double value1, double value2) + { + return (value1 < value2) && !AreClose(value1, value2); + } + + + /// + /// GreaterThan - Returns whether or not the first double is greater than the second double. + /// That is, whether or not the first is strictly greater than *and* not within epsilon of + /// the other number. Note that this epsilon is proportional to the numbers themselves + /// to that AreClose survives scalar multiplication. + /// + /// - the result of the GreaterThan comparision. + /// + /// The first double to compare. + /// The second double to compare. + public static bool GreaterThan(double value1, double value2) + { + return (value1 > value2) && !AreClose(value1, value2); + } + + /// + /// LessThanOrClose - Returns whether or not the first double is less than or close to + /// the second double. That is, whether or not the first is strictly less than or within + /// epsilon of the other number. Note that this epsilon is proportional to the numbers + /// themselves to that AreClose survives scalar multiplication. Note, + /// There are plenty of ways for this to return false even for numbers which + /// are theoretically identical, so no code calling this should fail to work if this + /// returns false. This is important enough to repeat: + /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be + /// used for optimizations *only*. + /// + /// + /// bool - the result of the LessThanOrClose comparision. + /// + /// The first double to compare. + /// The second double to compare. + public static bool LessThanOrClose(double value1, double value2) + { + return (value1 < value2) || AreClose(value1, value2); + } + + /// + /// GreaterThanOrClose - Returns whether or not the first double is greater than or close to + /// the second double. That is, whether or not the first is strictly greater than or within + /// epsilon of the other number. + /// + /// The first double to compare. + /// The second double to compare. + /// the result of the GreaterThanOrClose comparision. + public static bool GreaterThanOrClose(double value1, double value2) + { + return (value1 > value2) || AreClose(value1, value2); + } + + /// + /// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), + /// but this is faster. + /// + /// + /// bool - the result of the AreClose comparision. + /// + /// The double to compare to 1. + public static bool IsOne(double value) + { + return Math.Abs(value - 1.0) < 10.0 * double.Epsilon; + } + + /// + /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), + /// but this is faster. + /// + /// + /// bool - the result of the AreClose comparision. + /// + /// The double to compare to 0. + public static bool IsZero(double value) + { + return Math.Abs(value) < 10.0 * double.Epsilon; + } + /// /// Clamps a value between a minimum and maximum value. /// diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 5726356830..05cb9dfb28 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -1,18 +1,24 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using System.Collections.Generic; +using System.Diagnostics; + namespace Avalonia.Controls { + /// /// Base class for and . /// public class DefinitionBase : AvaloniaObject { + /// /// Defines the property. /// public static readonly StyledProperty SharedSizeGroupProperty = - AvaloniaProperty.Register(nameof(SharedSizeGroup), inherits: true); + AvaloniaProperty.Register(nameof(SharedSizeGroup), inherits: true, validate: SharedSizeGroupPropertyValueValid); /// /// Gets or sets the name of the shared size group of the column or row. @@ -22,5 +28,889 @@ namespace Avalonia.Controls get { return GetValue(SharedSizeGroupProperty); } set { SetValue(SharedSizeGroupProperty, value); } } + //------------------------------------------------------ + // + // Constructors + // + //------------------------------------------------------ + + + internal DefinitionBase(bool isColumnDefinition) + { + _isColumnDefinition = isColumnDefinition; + _parentIndex = -1; + } + + + + //------------------------------------------------------ + // + // Internal Methods + // + //------------------------------------------------------ + + #region Internal Methods + + /// + /// Callback to notify about entering model tree. + /// + internal void OnEnterParentTree() + { + if (_sharedState == null) + { + // start with getting SharedSizeGroup value. + // this property is NOT inhereted which should result in better overall perf. + string sharedSizeGroupId = SharedSizeGroup; + if (sharedSizeGroupId != null) + { + SharedSizeScope privateSharedSizeScope = PrivateSharedSizeScope; + if (privateSharedSizeScope != null) + { + _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + _sharedState.AddMember(this); + } + } + } + } + + /// + /// Callback to notify about exitting model tree. + /// + internal void OnExitParentTree() + { + _offset = 0; + if (_sharedState != null) + { + _sharedState.RemoveMember(this); + _sharedState = null; + } + } + + /// + /// Performs action preparing definition to enter layout calculation mode. + /// + internal void OnBeforeLayout(Grid grid) + { + // reset layout state. + _minSize = 0; + LayoutWasUpdated = true; + + // defer verification for shared definitions + if (_sharedState != null) { _sharedState.EnsureDeferredValidation(grid); } + } + + /// + /// Updates min size. + /// + /// New size. + internal void UpdateMinSize(double minSize) + { + _minSize = Math.Max(_minSize, minSize); + } + + /// + /// Sets min size. + /// + /// New size. + internal void SetMinSize(double minSize) + { + _minSize = minSize; + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static void OnUserSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase)d; + + if (definition.InParentLogicalTree) + { + if (definition._sharedState != null) + { + definition._sharedState.Invalidate(); + } + else + { + Grid parentGrid = (Grid)definition.Parent; + + if (((GridLength)e.OldValue).GridUnitType != ((GridLength)e.NewValue).GridUnitType) + { + parentGrid.Invalidate(); + } + else + { + parentGrid.InvalidateMeasure(); + } + } + } + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserSizePropertyValueValid(object value) + { + return (((GridLength)value).Value >= 0); + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static void OnUserMinSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase)d; + + if (definition.InParentLogicalTree) + { + Grid parentGrid = (Grid)definition.Parent; + parentGrid.InvalidateMeasure(); + } + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserMinSizePropertyValueValid(object value) + { + double v = (double)value; + return (!DoubleUtil.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static void OnUserMaxSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase)d; + + if (definition.InParentLogicalTree) + { + Grid parentGrid = (Grid)definition.Parent; + parentGrid.InvalidateMeasure(); + } + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserMaxSizePropertyValueValid(object value) + { + double v = (double)value; + return (!DoubleUtil.IsNaN(v) && v >= 0.0d); + } + + /// + /// + /// + /// + /// This method reflects Grid.SharedScopeProperty state by setting / clearing + /// dynamic property PrivateSharedSizeScopeProperty. Value of PrivateSharedSizeScopeProperty + /// is a collection of SharedSizeState objects for the scope. + /// Also PrivateSharedSizeScopeProperty is FrameworkPropertyMetadataOptions.Inherits property. So that all children + /// elements belonging to a certain scope can easily access SharedSizeState collection. As well + /// as been norified about enter / exit a scope. + /// + internal static void OnIsSharedSizeScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + // is it possible to optimize here something like this: + // if ((bool)d.GetValue(Grid.IsSharedSizeScopeProperty) == (d.GetLocalValue(PrivateSharedSizeScopeProperty) != null) + // { /* do nothing */ } + if ((bool)e.NewValue) + { + SharedSizeScope sharedStatesCollection = new SharedSizeScope(); + d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection); + } + else + { + d.ClearValue(PrivateSharedSizeScopeProperty); + } + } + + #endregion Internal Methods + + //------------------------------------------------------ + // + // Internal Properties + // + //------------------------------------------------------ + + #region Internal Properties + + /// + /// Returns true if this definition is a part of shared group. + /// + internal bool IsShared + { + get { return (_sharedState != null); } + } + + /// + /// Internal accessor to user size field. + /// + internal GridLength UserSize + { + get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); } + } + + /// + /// Internal accessor to user min size field. + /// + internal double UserMinSize + { + get { return (UserMinSizeValueCache); } + } + + /// + /// Internal accessor to user max size field. + /// + internal double UserMaxSize + { + get { return (UserMaxSizeValueCache); } + } + + /// + /// DefinitionBase's index in the parents collection. + /// + internal int Index + { + get + { + return (_parentIndex); + } + set + { + Debug.Assert(value >= -1 && _parentIndex != value); + _parentIndex = value; + } + } + + /// + /// Layout-time user size type. + /// + internal Grid.LayoutTimeSizeType SizeType + { + get { return (_sizeType); } + set { _sizeType = value; } + } + + /// + /// Returns or sets measure size for the definition. + /// + internal double MeasureSize + { + get { return (_measureSize); } + set { _measureSize = value; } + } + + /// + /// Returns definition's layout time type sensitive preferred size. + /// + /// + /// Returned value is guaranteed to be true preferred size. + /// + internal double PreferredSize + { + get + { + double preferredSize = MinSize; + if (_sizeType != Grid.LayoutTimeSizeType.Auto + && preferredSize < _measureSize) + { + preferredSize = _measureSize; + } + return (preferredSize); + } + } + + /// + /// Returns or sets size cache for the definition. + /// + internal double SizeCache + { + get { return (_sizeCache); } + set { _sizeCache = value; } + } + + /// + /// Returns min size. + /// + internal double MinSize + { + get + { + double minSize = _minSize; + if (UseSharedMinimum + && _sharedState != null + && minSize < _sharedState.MinSize) + { + minSize = _sharedState.MinSize; + } + return (minSize); + } + } + + /// + /// Returns min size, always taking into account shared state. + /// + internal double MinSizeForArrange + { + get + { + double minSize = _minSize; + if (_sharedState != null + && (UseSharedMinimum || !LayoutWasUpdated) + && minSize < _sharedState.MinSize) + { + minSize = _sharedState.MinSize; + } + return (minSize); + } + } + + /// + /// Offset. + /// + internal double FinalOffset + { + get { return _offset; } + set { _offset = value; } + } + + /// + /// Internal helper to access up-to-date UserSize property value. + /// + internal GridLength UserSizeValueCache + { + get + { + return (GridLength)GetValue( + _isColumnDefinition ? + ColumnDefinition.WidthProperty : + RowDefinition.HeightProperty); + } + } + + /// + /// Internal helper to access up-to-date UserMinSize property value. + /// + internal double UserMinSizeValueCache + { + get + { + return (double)GetValue( + _isColumnDefinition ? + ColumnDefinition.MinWidthProperty : + RowDefinition.MinHeightProperty); + } + } + + /// + /// Internal helper to access up-to-date UserMaxSize property value. + /// + internal double UserMaxSizeValueCache + { + get + { + return (double)GetValue( + _isColumnDefinition ? + ColumnDefinition.MaxWidthProperty : + RowDefinition.MaxHeightProperty); + } + } + + /// + /// Protected. Returns true if this DefinitionBase instance is in parent's logical tree. + /// + internal bool InParentLogicalTree + { + get { return (_parentIndex != -1); } + } + + #endregion Internal Properties + + //------------------------------------------------------ + // + // Private Methods + // + //------------------------------------------------------ + + #region Private Methods + + /// + /// SetFlags is used to set or unset one or multiple + /// flags on the object. + /// + private void SetFlags(bool value, Flags flags) + { + _flags = value ? (_flags | flags) : (_flags & (~flags)); + } + + /// + /// CheckFlagsAnd returns true if all the flags in the + /// given bitmask are set on the object. + /// + private bool CheckFlagsAnd(Flags flags) + { + return ((_flags & flags) == flags); + } + + /// + /// + /// + private static void OnSharedSizeGroupPropertyChanged(AvaloniaPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase)e.Sender; + + if (definition.InParentLogicalTree) + { + string sharedSizeGroupId = (string)e.NewValue; + + if (definition._sharedState != null) + { + // if definition is already registered AND shared size group id is changing, + // then un-register the definition from the current shared size state object. + definition._sharedState.RemoveMember(definition); + definition._sharedState = null; + } + + if ((definition._sharedState == null) && (sharedSizeGroupId != null)) + { + SharedSizeScope privateSharedSizeScope = definition.PrivateSharedSizeScope; + if (privateSharedSizeScope != null) + { + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + definition._sharedState.AddMember(definition); + } + } + } + } + + /// + /// Verifies that Shared Size Group Property string + /// a) not empty. + /// b) contains only letters, digits and underscore ('_'). + /// c) does not start with a digit. + /// + private static bool SharedSizeGroupPropertyValueValid(string value) + { + // null is default value + if (value == null) + { + return (true); + } + + string id = (string)value; + + if (id != string.Empty) + { + int i = -1; + while (++i < id.Length) + { + bool isDigit = Char.IsDigit(id[i]); + + if ((i == 0 && isDigit) + || !(isDigit + || Char.IsLetter(id[i]) + || '_' == id[i])) + { + break; + } + } + + if (i == id.Length) + { + return (true); + } + } + + return (false); + } + + /// + /// + /// + /// + /// OnPrivateSharedSizeScopePropertyChanged is called when new scope enters or + /// existing scope just left. In both cases if the DefinitionBase object is already registered + /// in SharedSizeState, it should un-register and register itself in a new one. + /// + private static void OnPrivateSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase)d; + + if (definition.InParentLogicalTree) + { + SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue; + + if (definition._sharedState != null) + { + // if definition is already registered And shared size scope is changing, + // then un-register the definition from the current shared size state object. + definition._sharedState.RemoveMember(definition); + definition._sharedState = null; + } + + if ((definition._sharedState == null) && (privateSharedSizeScope != null)) + { + string sharedSizeGroup = definition.SharedSizeGroup; + if (sharedSizeGroup != null) + { + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(definition.SharedSizeGroup); + definition._sharedState.AddMember(definition); + } + } + } + } + + #endregion Private Methods + + //------------------------------------------------------ + // + // Private Properties + // + //------------------------------------------------------ + + #region Private Properties + + /// + /// Private getter of shared state collection dynamic property. + /// + private SharedSizeScope PrivateSharedSizeScope + { + get { return (SharedSizeScope)GetValue(PrivateSharedSizeScopeProperty); } + } + + /// + /// Convenience accessor to UseSharedMinimum flag + /// + private bool UseSharedMinimum + { + get { return (CheckFlagsAnd(Flags.UseSharedMinimum)); } + set { SetFlags(value, Flags.UseSharedMinimum); } + } + + /// + /// Convenience accessor to LayoutWasUpdated flag + /// + private bool LayoutWasUpdated + { + get { return (CheckFlagsAnd(Flags.LayoutWasUpdated)); } + set { SetFlags(value, Flags.LayoutWasUpdated); } + } + + #endregion Private Properties + + //------------------------------------------------------ + // + // Private Fields + // + //------------------------------------------------------ + + #region Private Fields + private readonly bool _isColumnDefinition; // when "true", this is a ColumnDefinition; when "false" this is a RowDefinition (faster than a type check) + private Flags _flags; // flags reflecting various aspects of internal state + private int _parentIndex; // this instance's index in parent's children collection + + private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" + + private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's + private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure + private double _sizeCache; // cache used for various purposes (sorting, caching, etc) during calculations + private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) + + private SharedSizeState _sharedState; // reference to shared state object this instance is registered with + + internal const bool ThisIsColumnDefinition = true; + internal const bool ThisIsRowDefinition = false; + + #endregion Private Fields + + //------------------------------------------------------ + // + // Private Structures / Classes + // + //------------------------------------------------------ + + #region Private Structures Classes + + [System.Flags] + private enum Flags : byte + { + // + // bool flags + // + UseSharedMinimum = 0x00000020, // when "1", definition will take into account shared state's minimum + LayoutWasUpdated = 0x00000040, // set to "1" every time the parent grid is measured + } + + /// + /// Collection of shared states objects for a single scope + /// + private class SharedSizeScope + { + /// + /// Returns SharedSizeState object for a given group. + /// Creates a new StatedState object if necessary. + /// + internal SharedSizeState EnsureSharedState(string sharedSizeGroup) + { + // check that sharedSizeGroup is not default + Debug.Assert(sharedSizeGroup != null); + + SharedSizeState sharedState = _registry[sharedSizeGroup] as SharedSizeState; + if (sharedState == null) + { + sharedState = new SharedSizeState(this, sharedSizeGroup); + _registry[sharedSizeGroup] = sharedState; + } + return (sharedState); + } + + /// + /// Removes an entry in the registry by the given key. + /// + internal void Remove(object key) + { + Debug.Assert(_registry.Contains(key)); + _registry.Remove(key); + } + + private Hashtable _registry = new Hashtable(); // storage for shared state objects + } + + /// + /// Implementation of per shared group state object + /// + private class SharedSizeState + { + /// + /// Default ctor. + /// + internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) + { + Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null); + _sharedSizeScope = sharedSizeScope; + _sharedSizeGroupId = sharedSizeGroupId; + _registry = new List(); + _layoutUpdated = new EventHandler(OnLayoutUpdated); + _broadcastInvalidation = true; + } + + /// + /// Adds / registers a definition instance. + /// + internal void AddMember(DefinitionBase member) + { + Debug.Assert(!_registry.Contains(member)); + _registry.Add(member); + Invalidate(); + } + + /// + /// Removes / un-registers a definition instance. + /// + /// + /// If the collection of registered definitions becomes empty + /// instantiates self removal from owner's collection. + /// + internal void RemoveMember(DefinitionBase member) + { + Invalidate(); + _registry.Remove(member); + + if (_registry.Count == 0) + { + _sharedSizeScope.Remove(_sharedSizeGroupId); + } + } + + /// + /// Propogates invalidations for all registered definitions. + /// Resets its own state. + /// + internal void Invalidate() + { + _userSizeValid = false; + + if (_broadcastInvalidation) + { + for (int i = 0, count = _registry.Count; i < count; ++i) + { + Grid parentGrid = (Grid)(_registry[i].Parent); + parentGrid.Invalidate(); + } + _broadcastInvalidation = false; + } + } + + /// + /// Makes sure that one and only one layout updated handler is registered for this shared state. + /// + internal void EnsureDeferredValidation(IControl layoutUpdatedHost) + { + if (_layoutUpdatedHost == null) + { + _layoutUpdatedHost = layoutUpdatedHost; + _layoutUpdatedHost.LayoutUpdated += _layoutUpdated; + } + } + + /// + /// DefinitionBase's specific code. + /// + internal double MinSize + { + get + { + if (!_userSizeValid) { EnsureUserSizeValid(); } + return (_minSize); + } + } + + /// + /// DefinitionBase's specific code. + /// + internal GridLength UserSize + { + get + { + if (!_userSizeValid) { EnsureUserSizeValid(); } + return (_userSize); + } + } + + private void EnsureUserSizeValid() + { + _userSize = new GridLength(1, GridUnitType.Auto); + + for (int i = 0, count = _registry.Count; i < count; ++i) + { + Debug.Assert(_userSize.GridUnitType == GridUnitType.Auto + || _userSize.GridUnitType == GridUnitType.Pixel); + + GridLength currentGridLength = _registry[i].UserSizeValueCache; + if (currentGridLength.GridUnitType == GridUnitType.Pixel) + { + if (_userSize.GridUnitType == GridUnitType.Auto) + { + _userSize = currentGridLength; + } + else if (_userSize.Value < currentGridLength.Value) + { + _userSize = currentGridLength; + } + } + } + // taking maximum with user size effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + _minSize = _userSize.IsAbsolute ? _userSize.Value : 0.0; + + _userSizeValid = true; + } + + /// + /// OnLayoutUpdated handler. Validates that all participating definitions + /// have updated min size value. Forces another layout update cycle if needed. + /// + private void OnLayoutUpdated(object sender, EventArgs e) + { + double sharedMinSize = 0; + + // accumulate min size of all participating definitions + for (int i = 0, count = _registry.Count; i < count; ++i) + { + sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); + } + + bool sharedMinSizeChanged = !DoubleUtil.AreClose(_minSize, sharedMinSize); + + // compare accumulated min size with min sizes of the individual definitions + for (int i = 0, count = _registry.Count; i < count; ++i) + { + DefinitionBase definitionBase = _registry[i]; + + if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) + { + // if definition's min size is different, then need to re-measure + if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.MinSize)) + { + Grid parentGrid = (Grid)definitionBase.Parent; + parentGrid.InvalidateMeasure(); + definitionBase.UseSharedMinimum = true; + } + else + { + definitionBase.UseSharedMinimum = false; + + // if measure is valid then also need to check arrange. + // Note: definitionBase.SizeCache is volatile but at this point + // it contains up-to-date final size + if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.SizeCache)) + { + Grid parentGrid = (Grid)definitionBase.Parent; + parentGrid.InvalidateArrange(); + } + } + + definitionBase.LayoutWasUpdated = false; + } + } + + _minSize = sharedMinSize; + + _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; + _layoutUpdatedHost = null; + + _broadcastInvalidation = true; + } + + private readonly SharedSizeScope _sharedSizeScope; // the scope this state belongs to + private readonly string _sharedSizeGroupId; // Id of the shared size group this object is servicing + private readonly List _registry; // registry of participating definitions + private readonly EventHandler _layoutUpdated; // instance event handler for layout updated event + private IControl _layoutUpdatedHost; // IControl for which layout updated event handler is registered + private bool _broadcastInvalidation; // "true" when broadcasting of invalidation is needed + private bool _userSizeValid; // "true" when _userSize is up to date + private GridLength _userSize; // shared state + private double _minSize; // shared state + } + + + /// + /// Static ctor. Used for static registration of properties. + /// + static DefinitionBase() + { + SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); + } + + #endregion Properties } -} \ No newline at end of file +} + + diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 0c8f571506..96543fda81 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -868,7 +868,7 @@ namespace Avalonia.Controls { for (int i = 0; i < minSizes.Length; i++) { - if (DoubleUtil.GreaterThanOrClose(minSizes[i], 0)) + if (MathUtilities.GreaterThanOrClose(minSizes[i], 0)) { if (isRows) { @@ -926,7 +926,7 @@ namespace Avalonia.Controls MeasureCell(i, forceInfinityV); - hasDesiredSizeUChanged |= !DoubleUtil.AreClose(oldWidth, Children[i].DesiredSize.Width); + hasDesiredSizeUChanged |= !MathUtilities.AreClose(oldWidth, Children[i].DesiredSize.Width); if (!ignoreDesiredSizeU) { @@ -1284,10 +1284,10 @@ namespace Avalonia.Controls // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers Debug.Assert(!double.IsInfinity(totalRemainingSize) - && !DoubleUtil.IsNaN(totalRemainingSize) + && !MathUtilities.IsNaN(totalRemainingSize) && totalRemainingSize > 0 && !double.IsInfinity(sizeToDistribute) - && !DoubleUtil.IsNaN(sizeToDistribute) + && !MathUtilities.IsNaN(sizeToDistribute) && sizeToDistribute > 0); for (int i = 0; i < count; ++i) @@ -1320,107 +1320,6 @@ namespace Avalonia.Controls /// /// Must initialize LayoutSize for all Star entries in given array of definitions. /// - private void ResolveStar( - DefinitionBase[] definitions, - double availableSize) - { - if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) - { - ResolveStarLegacy(definitions, availableSize); - } - else - { - ResolveStarMaxDiscrepancy(definitions, availableSize); - } - } - - // original implementation, used from 3.0 through 4.6.2 - private void ResolveStarLegacy( - DefinitionBase[] definitions, - double availableSize) - { - DefinitionBase[] tempDefinitions = TempDefinitions; - int starDefinitionsCount = 0; - double takenSize = 0; - - for (int i = 0; i < definitions.Length; ++i) - { - switch (definitions[i].SizeType) - { - case (LayoutTimeSizeType.Auto): - takenSize += definitions[i].MinSize; - break; - case (LayoutTimeSizeType.Pixel): - takenSize += definitions[i].MeasureSize; - break; - case (LayoutTimeSizeType.Star): - { - tempDefinitions[starDefinitionsCount++] = definitions[i]; - - double starValue = definitions[i].UserSize.Value; - - if (_IsZero(starValue)) - { - definitions[i].MeasureSize = 0; - definitions[i].SizeCache = 0; - } - else - { - // clipping by c_starClip guarantees that sum of even a very big number of max'ed out star values - // can be summed up without overflow - starValue = Math.Min(starValue, c_starClip); - - // Note: normalized star value is temporary cached into MeasureSize - definitions[i].MeasureSize = starValue; - double maxSize = Math.Max(definitions[i].MinSize, definitions[i].UserMaxSize); - maxSize = Math.Min(maxSize, c_starClip); - definitions[i].SizeCache = maxSize / starValue; - } - } - break; - } - } - - if (starDefinitionsCount > 0) - { - Array.Sort(tempDefinitions, 0, starDefinitionsCount, s_starDistributionOrderComparer); - - // the 'do {} while' loop below calculates sum of star weights in order to avoid fp overflow... - // partial sum value is stored in each definition's SizeCache member. - // this way the algorithm guarantees (starValue <= definition.SizeCache) and thus - // (starValue / definition.SizeCache) will never overflow due to sum of star weights becoming zero. - // this is an important change from previous implementation where the following was possible: - // ((BigValueStar + SmallValueStar) - BigValueStar) resulting in 0... - double allStarWeights = 0; - int i = starDefinitionsCount - 1; - do - { - allStarWeights += tempDefinitions[i].MeasureSize; - tempDefinitions[i].SizeCache = allStarWeights; - } while (--i >= 0); - - i = 0; - do - { - double resolvedSize; - double starValue = tempDefinitions[i].MeasureSize; - - if (_IsZero(starValue)) - { - resolvedSize = tempDefinitions[i].MinSize; - } - else - { - double userSize = Math.Max(availableSize - takenSize, 0.0) * (starValue / tempDefinitions[i].SizeCache); - resolvedSize = Math.Min(userSize, tempDefinitions[i].UserMaxSize); - resolvedSize = Math.Max(tempDefinitions[i].MinSize, resolvedSize); - } - - tempDefinitions[i].MeasureSize = resolvedSize; - takenSize += resolvedSize; - } while (++i < starDefinitionsCount); - } - } // new implementation as of 4.7. Several improvements: // 1. Allocate to *-defs hitting their min or max constraints, before allocating @@ -1435,7 +1334,8 @@ namespace Avalonia.Controls // discrepancy (defined below). This avoids discontinuities - small // change in available space resulting in large change to one def's allocation. // 3. Correct handling of large *-values, including Infinity. - private void ResolveStarMaxDiscrepancy( + + private void ResolveStar( DefinitionBase[] definitions, double availableSize) { @@ -1755,272 +1655,7 @@ namespace Avalonia.Controls DefinitionBase[] definitions, double finalSize, bool columns) - { - if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) - { - SetFinalSizeLegacy(definitions, finalSize, columns); - } - else - { - SetFinalSizeMaxDiscrepancy(definitions, finalSize, columns); - } - } - - // original implementation, used from 3.0 through 4.6.2 - private void SetFinalSizeLegacy( - DefinitionBase[] definitions, - double finalSize, - bool columns) - { - int starDefinitionsCount = 0; // traverses form the first entry up - int nonStarIndex = definitions.Length; // traverses from the last entry down - double allPreferredArrangeSize = 0; - bool useLayoutRounding = this.UseLayoutRounding; - int[] definitionIndices = DefinitionIndices; - double[] roundingErrors = null; - - // If using layout rounding, check whether rounding needs to compensate for high DPI - double dpi = 1.0; - - if (useLayoutRounding) - { - DpiScale dpiScale = GetDpi(); - dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; - roundingErrors = RoundingErrors; - } - - for (int i = 0; i < definitions.Length; ++i) - { - // if definition is shared then is cannot be star - Debug.Assert(!definitions[i].IsShared || !definitions[i].UserSize.IsStar); - - if (definitions[i].UserSize.IsStar) - { - double starValue = definitions[i].UserSize.Value; - - if (_IsZero(starValue)) - { - // cach normilized star value temporary into MeasureSize - definitions[i].MeasureSize = 0; - definitions[i].SizeCache = 0; - } - else - { - // clipping by c_starClip guarantees that sum of even a very big number of max'ed out star values - // can be summed up without overflow - starValue = Math.Min(starValue, c_starClip); - - // Note: normalized star value is temporary cached into MeasureSize - definitions[i].MeasureSize = starValue; - double maxSize = Math.Max(definitions[i].MinSizeForArrange, definitions[i].UserMaxSize); - maxSize = Math.Min(maxSize, c_starClip); - definitions[i].SizeCache = maxSize / starValue; - if (useLayoutRounding) - { - roundingErrors[i] = definitions[i].SizeCache; - definitions[i].SizeCache = IControl.RoundLayoutValue(definitions[i].SizeCache, dpi); - } - } - definitionIndices[starDefinitionsCount++] = i; - } - else - { - double userSize = 0; - - switch (definitions[i].UserSize.GridUnitType) - { - case (GridUnitType.Pixel): - userSize = definitions[i].UserSize.Value; - break; - - case (GridUnitType.Auto): - userSize = definitions[i].MinSizeForArrange; - break; - } - - double userMaxSize; - - if (definitions[i].IsShared) - { - // overriding userMaxSize effectively prevents squishy-ness. - // this is a "solution" to avoid shared definitions from been sized to - // different final size at arrange time, if / when different grids receive - // different final sizes. - userMaxSize = userSize; - } - else - { - userMaxSize = definitions[i].UserMaxSize; - } - - definitions[i].SizeCache = Math.Max(definitions[i].MinSizeForArrange, Math.Min(userSize, userMaxSize)); - if (useLayoutRounding) - { - roundingErrors[i] = definitions[i].SizeCache; - definitions[i].SizeCache = IControl.RoundLayoutValue(definitions[i].SizeCache, dpi); - } - - allPreferredArrangeSize += definitions[i].SizeCache; - definitionIndices[--nonStarIndex] = i; - } - } - - // indices should meet - Debug.Assert(nonStarIndex == starDefinitionsCount); - - if (starDefinitionsCount > 0) - { - StarDistributionOrderIndexComparer starDistributionOrderIndexComparer = new StarDistributionOrderIndexComparer(definitions); - Array.Sort(definitionIndices, 0, starDefinitionsCount, starDistributionOrderIndexComparer); - - // the 'do {} while' loop below calculates sum of star weights in order to avoid fp overflow... - // partial sum value is stored in each definition's SizeCache member. - // this way the algorithm guarantees (starValue <= definition.SizeCache) and thus - // (starValue / definition.SizeCache) will never overflow due to sum of star weights becoming zero. - // this is an important change from previous implementation where the following was possible: - // ((BigValueStar + SmallValueStar) - BigValueStar) resulting in 0... - double allStarWeights = 0; - int i = starDefinitionsCount - 1; - do - { - allStarWeights += definitions[definitionIndices[i]].MeasureSize; - definitions[definitionIndices[i]].SizeCache = allStarWeights; - } while (--i >= 0); - - i = 0; - do - { - double resolvedSize; - double starValue = definitions[definitionIndices[i]].MeasureSize; - - if (_IsZero(starValue)) - { - resolvedSize = definitions[definitionIndices[i]].MinSizeForArrange; - } - else - { - double userSize = Math.Max(finalSize - allPreferredArrangeSize, 0.0) * (starValue / definitions[definitionIndices[i]].SizeCache); - resolvedSize = Math.Min(userSize, definitions[definitionIndices[i]].UserMaxSize); - resolvedSize = Math.Max(definitions[definitionIndices[i]].MinSizeForArrange, resolvedSize); - } - - definitions[definitionIndices[i]].SizeCache = resolvedSize; - if (useLayoutRounding) - { - roundingErrors[definitionIndices[i]] = definitions[definitionIndices[i]].SizeCache; - definitions[definitionIndices[i]].SizeCache = IControl.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi); - } - - allPreferredArrangeSize += definitions[definitionIndices[i]].SizeCache; - } while (++i < starDefinitionsCount); - } - - if (allPreferredArrangeSize > finalSize - && !_AreClose(allPreferredArrangeSize, finalSize)) - { - DistributionOrderIndexComparer distributionOrderIndexComparer = new DistributionOrderIndexComparer(definitions); - Array.Sort(definitionIndices, 0, definitions.Length, distributionOrderIndexComparer); - double sizeToDistribute = finalSize - allPreferredArrangeSize; - - for (int i = 0; i < definitions.Length; ++i) - { - int definitionIndex = definitionIndices[i]; - double final = definitions[definitionIndex].SizeCache + (sizeToDistribute / (definitions.Length - i)); - double finalOld = final; - final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); - final = Math.Min(final, definitions[definitionIndex].SizeCache); - - if (useLayoutRounding) - { - roundingErrors[definitionIndex] = final; - final = IControl.RoundLayoutValue(finalOld, dpi); - final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); - final = Math.Min(final, definitions[definitionIndex].SizeCache); - } - - sizeToDistribute -= (final - definitions[definitionIndex].SizeCache); - definitions[definitionIndex].SizeCache = final; - } - - allPreferredArrangeSize = finalSize - sizeToDistribute; - } - - if (useLayoutRounding) - { - if (!_AreClose(allPreferredArrangeSize, finalSize)) - { - // Compute deltas - for (int i = 0; i < definitions.Length; ++i) - { - roundingErrors[i] = roundingErrors[i] - definitions[i].SizeCache; - definitionIndices[i] = i; - } - - // Sort rounding errors - RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); - Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); - double adjustedSize = allPreferredArrangeSize; - double dpiIncrement = IControl.RoundLayoutValue(1.0, dpi); - - if (allPreferredArrangeSize > finalSize) - { - int i = definitions.Length - 1; - while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) - { - DefinitionBase definition = definitions[definitionIndices[i]]; - double final = definition.SizeCache - dpiIncrement; - final = Math.Max(final, definition.MinSizeForArrange); - if (final < definition.SizeCache) - { - adjustedSize -= dpiIncrement; - } - definition.SizeCache = final; - i--; - } - } - else if (allPreferredArrangeSize < finalSize) - { - int i = 0; - while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length) - { - DefinitionBase definition = definitions[definitionIndices[i]]; - double final = definition.SizeCache + dpiIncrement; - final = Math.Max(final, definition.MinSizeForArrange); - if (final > definition.SizeCache) - { - adjustedSize += dpiIncrement; - } - definition.SizeCache = final; - i++; - } - } - } - } - - definitions[0].FinalOffset = 0.0; - for (int i = 0; i < definitions.Length; ++i) - { - definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; - } - } - - // new implementation, as of 4.7. This incorporates the same algorithm - // as in ResolveStarMaxDiscrepancy. It differs in the same way that SetFinalSizeLegacy - // differs from ResolveStarLegacy, namely (a) leaves results in def.SizeCache - // instead of def.MeasureSize, (b) implements LayoutRounding if requested, - // (c) stores intermediate results differently. - // The LayoutRounding logic is improved: - // 1. Use pre-rounded values during proportional allocation. This avoids the - // same kind of problems arising from interaction with min/max that - // motivated the new algorithm in the first place. - // 2. Use correct "nudge" amount when distributing roundoff space. This - // comes into play at high DPI - greater than 134. - // 3. Applies rounding only to real pixel values (not to ratios) - private void SetFinalSizeMaxDiscrepancy( - DefinitionBase[] definitions, - double finalSize, - bool columns) - { + { int defCount = definitions.Length; int[] definitionIndices = DefinitionIndices; int minCount = 0, maxCount = 0; @@ -2359,7 +1994,7 @@ namespace Avalonia.Controls for (int i = 0; i < definitions.Length; ++i) { DefinitionBase def = definitions[i]; - double roundedSize = IControl.RoundLayoutValue(def.SizeCache, dpi); + double roundedSize = Control.RoundLayoutValue(def.SizeCache, dpi); roundingErrors[i] = (roundedSize - def.SizeCache); def.SizeCache = roundedSize; roundedTakenSize += roundedSize; @@ -2561,9 +2196,6 @@ namespace Avalonia.Controls ExtendedData extData = ExtData; if (extData != null) { - // for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid (); - // for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid (); - if (extData.TempDefinitions != null) { // TempDefinitions has to be cleared to avoid "memory leaks" @@ -3403,7 +3035,7 @@ namespace Avalonia.Controls internal StarDistributionOrderIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } @@ -3444,7 +3076,7 @@ namespace Avalonia.Controls internal DistributionOrderIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } @@ -3487,7 +3119,7 @@ namespace Avalonia.Controls internal RoundingErrorIndexComparer(double[] errors) { - Invariant.Assert(errors != null); + Contract.Requires(errors != null); this.errors = errors; } @@ -3586,7 +3218,7 @@ namespace Avalonia.Controls internal MinRatioIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } @@ -3627,7 +3259,7 @@ namespace Avalonia.Controls internal MaxRatioIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } @@ -3668,7 +3300,7 @@ namespace Avalonia.Controls internal StarWeightIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } @@ -3699,79 +3331,11 @@ namespace Avalonia.Controls return result; } } - - /// - /// Implementation of a simple enumerator of grid's logical Children - /// - private class GridChildrenCollectionEnumeratorSimple : IEnumerator - { - internal GridChildrenCollectionEnumeratorSimple(Grid grid, bool includeChildren) - { - Debug.Assert(grid != null); - _currentEnumerator = -1; - _enumerator0 = new ColumnDefinitions.Enumerator(grid.ExtData != null ? grid.ExtData.ColumnDefinitions : null); - _enumerator1 = new RowDefinitions.Enumerator(grid.ExtData != null ? grid.ExtData.RowDefinitions : null); - // GridLineRenderer is NOT included into this enumerator. - _enumerator2Index = 0; - if (includeChildren) - { - _enumerator2Collection = grid.Children; - _enumerator2Count = _enumerator2Collection.Count; - } - else - { - _enumerator2Collection = null; - _enumerator2Count = 0; - } - } - - public bool MoveNext() - { - while (_currentEnumerator < 3) - { - if (_currentEnumerator >= 0) - { - switch (_currentEnumerator) - { - case (0): if (_enumerator0.MoveNext()) { _currentChild = _enumerator0.Current; return (true); } break; - case (1): if (_enumerator1.MoveNext()) { _currentChild = _enumerator1.Current; return (true); } break; - case (2): - if (_enumerator2Index < _enumerator2Count) - { - _currentChild = _enumerator2Collection[_enumerator2Index]; - _enumerator2Index++; - return (true); - } - break; - } - } - _currentEnumerator++; - } - return (false); - } - - public void Reset() - { - _currentEnumerator = -1; - _currentChild = null; - _enumerator0.Reset(); - _enumerator1.Reset(); - _enumerator2Index = 0; - } - - private int _currentEnumerator; - private Object _currentChild; - private ColumnDefinitions.Enumerator _enumerator0; - private RowDefinitions.Enumerator _enumerator1; - private Controls _enumerator2Collection; - private int _enumerator2Index; - private int _enumerator2Count; - } - + /// /// Helper to render grid lines. /// - internal class GridLinesRenderer : DrawingVisual + internal class GridLinesRenderer : Visual { /// /// Static initialization From 9e052c5acca28ebe16ec0fd0afd4fb8708ad0d07 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 24 May 2019 12:38:20 +0800 Subject: [PATCH 020/130] Fix misplaced compile command on proj. --- src/Avalonia.Controls/Avalonia.Controls.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index eabe136791..c321d3a2f1 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -11,8 +11,8 @@ + - From d7b3ebb9f788e31c718977511e003f0c58adf6d2 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 24 May 2019 13:23:23 +0800 Subject: [PATCH 021/130] Part 5 of n --- src/Avalonia.Controls/DefinitionBase.cs | 11 +- src/Avalonia.Controls/GridWPF.cs | 430 ++++-------------------- 2 files changed, 63 insertions(+), 378 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 05cb9dfb28..10d6b55285 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Avalonia.Utilities; namespace Avalonia.Controls { @@ -186,7 +187,7 @@ namespace Avalonia.Controls internal static bool IsUserMinSizePropertyValueValid(object value) { double v = (double)value; - return (!DoubleUtil.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); + return (!MathUtilities.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); } /// @@ -215,7 +216,7 @@ namespace Avalonia.Controls internal static bool IsUserMaxSizePropertyValueValid(object value) { double v = (double)value; - return (!DoubleUtil.IsNaN(v) && v >= 0.0d); + return (!MathUtilities.IsNaN(v) && v >= 0.0d); } /// @@ -847,7 +848,7 @@ namespace Avalonia.Controls sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); } - bool sharedMinSizeChanged = !DoubleUtil.AreClose(_minSize, sharedMinSize); + bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); // compare accumulated min size with min sizes of the individual definitions for (int i = 0, count = _registry.Count; i < count; ++i) @@ -857,7 +858,7 @@ namespace Avalonia.Controls if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) { // if definition's min size is different, then need to re-measure - if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.MinSize)) + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) { Grid parentGrid = (Grid)definitionBase.Parent; parentGrid.InvalidateMeasure(); @@ -870,7 +871,7 @@ namespace Avalonia.Controls // if measure is valid then also need to check arrange. // Note: definitionBase.SizeCache is volatile but at this point // it contains up-to-date final size - if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.SizeCache)) + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) { Grid parentGrid = (Grid)definitionBase.Parent; parentGrid.InvalidateArrange(); diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 96543fda81..30ca751e57 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -16,6 +16,7 @@ using Avalonia.Controls; using Avalonia.Media; using Avalonia; using System.Collections; +using Avalonia.Utilities; namespace Avalonia.Controls { @@ -153,7 +154,7 @@ namespace Avalonia.Controls { child.Measure(constraint); gridDesiredSize = new Size( - Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), + Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); } } @@ -199,156 +200,6 @@ namespace Avalonia.Controls Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); - // Grid classifies cells into four groups depending on - // the column / row type a cell belongs to (number corresponds to - // group number): - // - // Px Auto Star - // +--------+--------+--------+ - // | | | | - // Px | 1 | 1 | 3 | - // | | | | - // +--------+--------+--------+ - // | | | | - // Auto | 1 | 1 | 3 | - // | | | | - // +--------+--------+--------+ - // | | | | - // Star | 4 | 2 | 4 | - // | | | | - // +--------+--------+--------+ - // - // The group number indicates the order in which cells are measured. - // Certain order is necessary to be able to dynamically resolve star - // columns / rows sizes which are used as input for measuring of - // the cells belonging to them. - // - // However, there are cases when topology of a grid causes cyclical - // size dependences. For example: - // - // - // column width="Auto" column width="*" - // +----------------------+----------------------+ - // | | | - // | | | - // | | | - // | | | - // row height="Auto" | | cell 1 2 | - // | | | - // | | | - // | | | - // | | | - // +----------------------+----------------------+ - // | | | - // | | | - // | | | - // | | | - // row height="*" | cell 2 1 | | - // | | | - // | | | - // | | | - // | | | - // +----------------------+----------------------+ - // - // In order to accurately calculate constraint width for "cell 1 2" - // (which is the remaining of grid's available width and calculated - // value of Auto column), "cell 2 1" needs to be calculated first, - // as it contributes to the Auto column's calculated value. - // At the same time in order to accurately calculate constraint - // height for "cell 2 1", "cell 1 2" needs to be calcualted first, - // as it contributes to Auto row height, which is used in the - // computation of Star row resolved height. - // - // to "break" this cyclical dependency we are making (arbitrary) - // decision to treat cells like "cell 2 1" as if they appear in Auto - // rows. And then recalculate them one more time when star row - // heights are resolved. - // - // (Or more strictly) the code below implement the following logic: - // - // +---------+ - // | enter | - // +---------+ - // | - // V - // +----------------+ - // | Measure Group1 | - // +----------------+ - // | - // V - // / - \ - // / \ - // Y / Can \ N - // +--------| Resolve |-----------+ - // | \ StarsV? / | - // | \ / | - // | \ - / | - // V V - // +----------------+ / - \ - // | Resolve StarsV | / \ - // +----------------+ Y / Can \ N - // | +----| Resolve |------+ - // V | \ StarsU? / | - // +----------------+ | \ / | - // | Measure Group2 | | \ - / | - // +----------------+ | V - // | | +-----------------+ - // V | | Measure Group2' | - // +----------------+ | +-----------------+ - // | Resolve StarsU | | | - // +----------------+ V V - // | +----------------+ +----------------+ - // V | Resolve StarsU | | Resolve StarsU | - // +----------------+ +----------------+ +----------------+ - // | Measure Group3 | | | - // +----------------+ V V - // | +----------------+ +----------------+ - // | | Measure Group3 | | Measure Group3 | - // | +----------------+ +----------------+ - // | | | - // | V V - // | +----------------+ +----------------+ - // | | Resolve StarsV | | Resolve StarsV | - // | +----------------+ +----------------+ - // | | | - // | | V - // | | +------------------+ - // | | | Measure Group2'' | - // | | +------------------+ - // | | | - // +----------------------+-------------------------+ - // | - // V - // +----------------+ - // | Measure Group4 | - // +----------------+ - // | - // V - // +--------+ - // | exit | - // +--------+ - // - // where: - // * all [Measure GroupN] - regular Children measure process - - // each cell is measured given contraint size as an input - // and each cell's desired size is accumulated on the - // corresponding column / row; - // * [Measure Group2'] - is when each cell is measured with - // infinit height as a constraint and a cell's desired - // height is ignored; - // * [Measure Groups''] - is when each cell is measured (second - // time during single Grid.MeasureOverride) regularly but its - // returned width is ignored; - // - // This algorithm is believed to be as close to ideal as possible. - // It has the following drawbacks: - // * cells belonging to Group2 can be called to measure twice; - // * iff during second measure a cell belonging to Group2 returns - // desired width greater than desired width returned the first - // time, such a cell is going to be clipped, even though it - // appears in Auto column. - // - MeasureCellsGroup(extData.CellGroup1, constraint, false, false); { @@ -478,7 +329,7 @@ namespace Avalonia.Controls } // update render bound on grid lines renderer visual - GridLinesRenderer gridLinesRenderer = EnsureGridLinesRenderer(); + var gridLinesRenderer = EnsureGridLinesRenderer(); if (gridLinesRenderer != null) { gridLinesRenderer.UpdateRenderBounds(arrangeSize); @@ -624,23 +475,23 @@ namespace Avalonia.Controls // // read and cache child positioning properties // - + // read indices from the corresponding properties // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(Grid.ColumnProperty (child), DefinitionsU.Length - 1); + cell.ColumnIndex = Math.Min(child.GetValue(Grid.ColumnProperty), DefinitionsU.Length - 1); // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Length - 1); + cell.RowIndex = Math.Min(child.GetValue(Grid.RowProperty), DefinitionsV.Length - 1); // read span properties // clamp to not exceed beyond right side of the grid // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Length - cell.ColumnIndex); + cell.ColumnSpan = Math.Min(child.GetValue(Grid.ColumnSpanProperty), DefinitionsU.Length - cell.ColumnIndex); // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Length - cell.RowIndex); + cell.RowSpan = Math.Min(child.GetValue(Grid.RowSpanProperty), DefinitionsV.Length - cell.RowIndex); Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Length); Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Length); @@ -1063,7 +914,7 @@ namespace Avalonia.Controls } var child = Children[cell]; - + if (child != null) { Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); @@ -1334,7 +1185,7 @@ namespace Avalonia.Controls // discrepancy (defined below). This avoids discontinuities - small // change in available space resulting in large change to one def's allocation. // 3. Correct handling of large *-values, including Infinity. - + private void ResolveStar( DefinitionBase[] definitions, double availableSize) @@ -1655,7 +1506,7 @@ namespace Avalonia.Controls DefinitionBase[] definitions, double finalSize, bool columns) - { + { int defCount = definitions.Length; int[] definitionIndices = DefinitionIndices; int minCount = 0, maxCount = 0; @@ -2219,12 +2070,12 @@ namespace Avalonia.Controls if (ShowGridLines && (_gridLinesRenderer == null)) { _gridLinesRenderer = new GridLinesRenderer(); - this.AddVisualChild(_gridLinesRenderer); + this.VisualChildren.Add(_gridLinesRenderer); } if ((!ShowGridLines) && (_gridLinesRenderer != null)) { - this.RemoveVisualChild(_gridLinesRenderer); + this.VisualChildren.Remove(_gridLinesRenderer); _gridLinesRenderer = null; } @@ -2287,7 +2138,7 @@ namespace Avalonia.Controls if (child != null) { - Grid grid = VisualTreeHelper.GetParent(child) as Grid; + Grid grid = child.GetVisualParent() as Grid; if (grid != null && grid.ExtData != null && grid.ListenToNotifications) @@ -2514,7 +2365,7 @@ namespace Avalonia.Controls /// true if d == 0. private static bool _IsZero(double d) { - return (Math.Abs(d) < c_epsilon); + return (Math.Abs(d) < double.Epsilon); } /// @@ -2525,7 +2376,7 @@ namespace Avalonia.Controls /// true if d1 == d2 private static bool _AreClose(double d1, double d2) { - return (Math.Abs(d1 - d2) < c_epsilon); + return (Math.Abs(d1 - d2) < double.Epsilon); } /// @@ -2554,15 +2405,6 @@ namespace Avalonia.Controls } } - #endregion Private Properties - - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields private ExtendedData _data; // extended data instantiated on demand, for non-trivial case handling only private Flags _flags; // grid validity / property caches dirtiness flags private GridLinesRenderer _gridLinesRenderer; @@ -2573,15 +2415,6 @@ namespace Avalonia.Controls // Stores unrounded values and rounding errors during layout rounding. double[] _roundingErrors; - #endregion Private Fields - - //------------------------------------------------------ - // - // Static Fields - // - //------------------------------------------------------ - - #region Static Fields private const double c_epsilon = 1e-5; // used in fp calculations private const double c_starClip = 1e298; // used as maximum for clipping star values during normalization private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop @@ -2594,16 +2427,6 @@ namespace Avalonia.Controls private static readonly IComparer s_maxRatioComparer = new MaxRatioComparer(); private static readonly IComparer s_starWeightComparer = new StarWeightComparer(); - #endregion Static Fields - - //------------------------------------------------------ - // - // Private Structures / Classes - // - //------------------------------------------------------ - - #region Private Structures Classes - /// /// Extended data instantiated on demand, when grid handles non-trivial case. /// @@ -2656,140 +2479,6 @@ namespace Avalonia.Controls ArrangeOverrideInProgress = 0x00080000, // "1" while in the context of Grid.ArrangeOverride } - #endregion Private Structures Classes - - //------------------------------------------------------ - // - // Properties - // - //------------------------------------------------------ - - #region Properties - - /// - /// ShowGridLines property. This property is used mostly - /// for simplification of visual debuggig. When it is set - /// to true grid lines are drawn to visualize location - /// of grid lines. - /// - public static readonly DependencyProperty ShowGridLinesProperty = - DependencyProperty.Register( - "ShowGridLines", - typeof(bool), - typeof(Grid), - new FrameworkPropertyMetadata( - false, - new PropertyChangedCallback(OnShowGridLinesPropertyChanged))); - - /// - /// Column property. This is an attached property. - /// Grid defines Column property, so that it can be set - /// on any element treated as a cell. Column property - /// specifies child's position with respect to columns. - /// - /// - /// Columns are 0 - based. In order to appear in first column, element - /// should have Column property set to 0. - /// Default value for the property is 0. - /// - [CommonDependencyProperty] - public static readonly DependencyProperty ColumnProperty = - DependencyProperty.RegisterAttached( - "Column", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 0, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueNotNegative)); - - /// - /// Row property. This is an attached property. - /// Grid defines Row, so that it can be set - /// on any element treated as a cell. Row property - /// specifies child's position with respect to rows. - /// - /// Rows are 0 - based. In order to appear in first row, element - /// should have Row property set to 0. - /// Default value for the property is 0. - /// - /// - [CommonDependencyProperty] - public static readonly DependencyProperty RowProperty = - DependencyProperty.RegisterAttached( - "Row", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 0, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueNotNegative)); - - /// - /// ColumnSpan property. This is an attached property. - /// Grid defines ColumnSpan, so that it can be set - /// on any element treated as a cell. ColumnSpan property - /// specifies child's width with respect to columns. - /// Example, ColumnSpan == 2 means that child will span across two columns. - /// - /// - /// Default value for the property is 1. - /// - [CommonDependencyProperty] - public static readonly DependencyProperty ColumnSpanProperty = - DependencyProperty.RegisterAttached( - "ColumnSpan", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 1, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueGreaterThanZero)); - - /// - /// RowSpan property. This is an attached property. - /// Grid defines RowSpan, so that it can be set - /// on any element treated as a cell. RowSpan property - /// specifies child's height with respect to row grid lines. - /// Example, RowSpan == 3 means that child will span across three rows. - /// - /// - /// Default value for the property is 1. - /// - [CommonDependencyProperty] - public static readonly DependencyProperty RowSpanProperty = - DependencyProperty.RegisterAttached( - "RowSpan", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 1, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueGreaterThanZero)); - - - /// - /// IsSharedSizeScope property marks scoping element for shared size. - /// - public static readonly DependencyProperty IsSharedSizeScopeProperty = - DependencyProperty.RegisterAttached( - "IsSharedSizeScope", - typeof(bool), - typeof(Grid), - new FrameworkPropertyMetadata( - false, - new PropertyChangedCallback(DefinitionBase.OnIsSharedSizeScopePropertyChanged))); - - #endregion Properties - - //------------------------------------------------------ - // - // Internal Structures / Classes - // - //------------------------------------------------------ - - #region Internal Structures Classes - /// /// LayoutTimeSizeType is used internally and reflects layout-time size type. /// @@ -2802,16 +2491,6 @@ namespace Avalonia.Controls Star = 0x04, } - #endregion Internal Structures Classes - - //------------------------------------------------------ - // - // Private Structures / Classes - // - //------------------------------------------------------ - - #region Private Structures Classes - /// /// CellCache stored calculated values of /// 1. attached cell positioning properties; @@ -3331,7 +3010,7 @@ namespace Avalonia.Controls return result; } } - + /// /// Helper to render grid lines. /// @@ -3342,53 +3021,52 @@ namespace Avalonia.Controls /// static GridLinesRenderer() { - s_oddDashPen = new Pen(Brushes.Blue, c_penWidth); - DoubleCollection oddDashArray = new DoubleCollection(); + var oddDashArray = new List(); oddDashArray.Add(c_dashLength); oddDashArray.Add(c_dashLength); - s_oddDashPen.DashStyle = new DashStyle(oddDashArray, 0); - s_oddDashPen.DashCap = PenLineCap.Flat; - s_oddDashPen.Freeze(); + var ds1 = new DashStyle(oddDashArray, 0); + s_oddDashPen = new Pen(Brushes.Blue, + c_penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds1); - s_evenDashPen = new Pen(Brushes.Yellow, c_penWidth); - DoubleCollection evenDashArray = new DoubleCollection(); + var evenDashArray = new List(); evenDashArray.Add(c_dashLength); evenDashArray.Add(c_dashLength); - s_evenDashPen.DashStyle = new DashStyle(evenDashArray, c_dashLength); - s_evenDashPen.DashCap = PenLineCap.Flat; - s_evenDashPen.Freeze(); + var ds2 = new DashStyle(evenDashArray, 0); + s_evenDashPen = new Pen(Brushes.Yellow, + c_penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds2); } /// /// UpdateRenderBounds. /// - /// Size of render bounds - internal void UpdateRenderBounds(Size boundsSize) + public override void Render(DrawingContext drawingContext) { - using (DrawingContext drawingContext = RenderOpen()) + var grid = this.GetVisualParent() as Grid; + + if (grid == null + || grid.ShowGridLines == false) { - Grid grid = VisualTreeHelper.GetParent(this) as Grid; - if (grid == null - || grid.ShowGridLines == false) - { - return; - } + return; + } - for (int i = 1; i < grid.DefinitionsU.Length; ++i) - { - DrawGridLine( - drawingContext, - grid.DefinitionsU[i].FinalOffset, 0.0, - grid.DefinitionsU[i].FinalOffset, boundsSize.Height); - } + for (int i = 1; i < grid.DefinitionsU.Length; ++i) + { + DrawGridLine( + drawingContext, + grid.DefinitionsU[i].FinalOffset, 0.0, + grid.DefinitionsU[i].FinalOffset, lastArrangeSize.Height); + } - for (int i = 1; i < grid.DefinitionsV.Length; ++i) - { - DrawGridLine( - drawingContext, - 0.0, grid.DefinitionsV[i].FinalOffset, - boundsSize.Width, grid.DefinitionsV[i].FinalOffset); - } + for (int i = 1; i < grid.DefinitionsV.Length; ++i) + { + DrawGridLine( + drawingContext, + 0.0, grid.DefinitionsV[i].FinalOffset, + lastArrangeSize.Width, grid.DefinitionsV[i].FinalOffset); } } @@ -3402,12 +3080,18 @@ namespace Avalonia.Controls double endX, double endY) { - Point start = new Point(startX, startY); - Point end = new Point(endX, endY); + var start = new Point(startX, startY); + var end = new Point(endX, endY); drawingContext.DrawLine(s_oddDashPen, start, end); drawingContext.DrawLine(s_evenDashPen, start, end); } + internal void UpdateRenderBounds(Size arrangeSize) + { + lastArrangeSize = arrangeSize; + this.InvalidateVisual(); + } + private static Size lastArrangeSize; private const double c_dashLength = 4.0; // private const double c_penWidth = 1.0; // private static readonly Pen s_oddDashPen; // first pen to draw dash From aea3bbcbd026ad72698a79cdeba260702f1cf55d Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 24 May 2019 14:12:44 +0800 Subject: [PATCH 022/130] Part 6 of n --- src/Avalonia.Base/Utilities/MathUtilities.cs | 52 ++----------- src/Avalonia.Controls/DefinitionBase.cs | 8 +- src/Avalonia.Controls/GridWPF.cs | 81 +++++++++++--------- 3 files changed, 54 insertions(+), 87 deletions(-) diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 2f138d9e83..dc47584f32 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Runtime.InteropServices; namespace Avalonia.Utilities { @@ -12,17 +13,8 @@ namespace Avalonia.Utilities { /// /// AreClose - Returns whether or not two doubles are "close". That is, whether or - /// not they are within epsilon of each other. Note that this epsilon is proportional - /// to the numbers themselves to that AreClose survives scalar multiplication. - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// - /// bool - the result of the AreClose comparision. - /// + /// not they are within epsilon of each other. + /// /// The first double to compare. /// The second double to compare. public static bool AreClose(double value1, double value2) @@ -37,17 +29,7 @@ namespace Avalonia.Utilities /// /// LessThan - Returns whether or not the first double is less than the second double. /// That is, whether or not the first is strictly less than *and* not within epsilon of - /// the other number. Note that this epsilon is proportional to the numbers themselves - /// to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// - /// bool - the result of the LessThan comparision. - /// + /// the other number. /// The first double to compare. /// The second double to compare. public static bool LessThan(double value1, double value2) @@ -55,15 +37,10 @@ namespace Avalonia.Utilities return (value1 < value2) && !AreClose(value1, value2); } - /// /// GreaterThan - Returns whether or not the first double is greater than the second double. /// That is, whether or not the first is strictly greater than *and* not within epsilon of - /// the other number. Note that this epsilon is proportional to the numbers themselves - /// to that AreClose survives scalar multiplication. - /// - /// - the result of the GreaterThan comparision. - /// + /// the other number. /// The first double to compare. /// The second double to compare. public static bool GreaterThan(double value1, double value2) @@ -74,17 +51,7 @@ namespace Avalonia.Utilities /// /// LessThanOrClose - Returns whether or not the first double is less than or close to /// the second double. That is, whether or not the first is strictly less than or within - /// epsilon of the other number. Note that this epsilon is proportional to the numbers - /// themselves to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// - /// bool - the result of the LessThanOrClose comparision. - /// + /// epsilon of the other number. /// The first double to compare. /// The second double to compare. public static bool LessThanOrClose(double value1, double value2) @@ -99,7 +66,6 @@ namespace Avalonia.Utilities /// /// The first double to compare. /// The second double to compare. - /// the result of the GreaterThanOrClose comparision. public static bool GreaterThanOrClose(double value1, double value2) { return (value1 > value2) || AreClose(value1, value2); @@ -109,9 +75,6 @@ namespace Avalonia.Utilities /// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), /// but this is faster. /// - /// - /// bool - the result of the AreClose comparision. - /// /// The double to compare to 1. public static bool IsOne(double value) { @@ -122,9 +85,6 @@ namespace Avalonia.Utilities /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), /// but this is faster. /// - /// - /// bool - the result of the AreClose comparision. - /// /// The double to compare to 0. public static bool IsZero(double value) { diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 10d6b55285..e1c5979476 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls /// /// Base class for and . /// - public class DefinitionBase : AvaloniaObject + public class DefinitionBase : ContentControl { /// @@ -307,7 +307,7 @@ namespace Avalonia.Controls /// /// Layout-time user size type. /// - internal Grid.LayoutTimeSizeType SizeType + internal Grid.GridLayoutTimeSizeType SizeType { get { return (_sizeType); } set { _sizeType = value; } @@ -333,7 +333,7 @@ namespace Avalonia.Controls get { double preferredSize = MinSize; - if (_sizeType != Grid.LayoutTimeSizeType.Auto + if (_sizeType != Grid.GridLayoutTimeSizeType.Auto && preferredSize < _measureSize) { preferredSize = _measureSize; @@ -635,7 +635,7 @@ namespace Avalonia.Controls private Flags _flags; // flags reflecting various aspects of internal state private int _parentIndex; // this instance's index in parent's children collection - private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" + private Grid.GridLayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 30ca751e57..5364f99dae 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -56,6 +56,14 @@ namespace Avalonia.Controls public static readonly AttachedProperty IsSharedSizeScopeProperty = AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); + /// + /// Defines the property. + /// + public static readonly StyledProperty ShowGridLinesProperty = + AvaloniaProperty.Register( + nameof(ShowGridLines), + defaultValue: false); + /// /// ShowGridLines property. /// @@ -64,6 +72,7 @@ namespace Avalonia.Controls get { return (CheckFlagsAnd(Flags.ShowGridLinesPropertyValue)); } set { SetValue(ShowGridLinesProperty, value); } } + private ColumnDefinitions _columnDefinitions; private RowDefinitions _rowDefinitions; @@ -1135,10 +1144,10 @@ namespace Avalonia.Controls // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers Debug.Assert(!double.IsInfinity(totalRemainingSize) - && !MathUtilities.IsNaN(totalRemainingSize) + && !double.IsNaN(totalRemainingSize) && totalRemainingSize > 0 && !double.IsInfinity(sizeToDistribute) - && !MathUtilities.IsNaN(sizeToDistribute) + && !double.IsNaN(sizeToDistribute) && sizeToDistribute > 0); for (int i = 0; i < count; ++i) @@ -1163,15 +1172,6 @@ namespace Avalonia.Controls } } - /// - /// Resolves Star's for given array of definitions. - /// - /// Array of definitions to resolve stars. - /// All available size. - /// - /// Must initialize LayoutSize for all Star entries in given array of definitions. - /// - // new implementation as of 4.7. Several improvements: // 1. Allocate to *-defs hitting their min or max constraints, before allocating // to other *-defs. A def that hits its min uses more space than its @@ -1186,6 +1186,14 @@ namespace Avalonia.Controls // change in available space resulting in large change to one def's allocation. // 3. Correct handling of large *-values, including Infinity. + /// + /// Resolves Star's for given array of definitions. + /// + /// Array of definitions to resolve stars. + /// All available size. + /// + /// Must initialize LayoutSize for all Star entries in given array of definitions. + /// private void ResolveStar( DefinitionBase[] definitions, double availableSize) @@ -2113,12 +2121,30 @@ namespace Avalonia.Controls return (flags == 0 || (_flags & flags) != 0); } - /// - /// - /// - private static void OnShowGridLinesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + + private static int ValidateColumn(AvaloniaObject o, int value) { - Grid grid = (Grid)d; + if (value < 0) + { + throw new ArgumentException("Invalid Grid.Column value."); + } + + return value; + } + + private static int ValidateRow(AvaloniaObject o, int value) + { + if (value < 0) + { + throw new ArgumentException("Invalid Grid.Row value."); + } + + return value; + } + + private static void OnShowGridLinesPropertyChanged(AvaloniaPropertyChangedEventArgs e) + { + var grid = e.Sender as Grid; if (grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway && grid.ListenToNotifications) @@ -2129,12 +2155,9 @@ namespace Avalonia.Controls grid.SetFlags((bool)e.NewValue, Flags.ShowGridLinesPropertyValue); } - /// - /// - /// - private static void OnCellAttachedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnCellAttachedPropertyChanged(AvaloniaPropertyChangedEventArgs e) { - Visual child = d as Visual; + var child = e.Sender as Visual; if (child != null) { @@ -2149,22 +2172,6 @@ namespace Avalonia.Controls } } - /// - /// - /// - private static bool IsIntValueNotNegative(object value) - { - return ((int)value >= 0); - } - - /// - /// - /// - private static bool IsIntValueGreaterThanZero(object value) - { - return ((int)value > 0); - } - /// /// Helper for Comparer methods. /// From 645e3131cc21ef59ee86eb4ec3d1f3e37430f5fa Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 24 May 2019 15:20:45 +0800 Subject: [PATCH 023/130] Part 7 of n --- src/Avalonia.Controls/GridWPF.cs | 63 ++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 5364f99dae..d42d6fc651 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -17,6 +17,7 @@ using Avalonia.Media; using Avalonia; using System.Collections; using Avalonia.Utilities; +using Avalonia.Layout; namespace Avalonia.Controls { @@ -25,6 +26,16 @@ namespace Avalonia.Controls /// public class Grid : Panel { + + static Grid() + { + ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); + ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + RowProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + RowSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + } + /// /// Defines the Column attached property. /// @@ -582,9 +593,9 @@ namespace Avalonia.Controls } else { - extData.ColumnDefinitions.InternalTrimToSize(); + // extData.ColumnDefinitions.InternalTrimToSize(); - if (extData.ColumnDefinitions.InternalCount == 0) + if (extData.ColumnDefinitions.Count == 0) { // if column definitions collection is empty // mockup array with one column @@ -592,7 +603,7 @@ namespace Avalonia.Controls } else { - extData.DefinitionsU = extData.ColumnDefinitions.InternalItems; + extData.DefinitionsU = extData.ColumnDefinitions.ToArray(); } } @@ -625,9 +636,9 @@ namespace Avalonia.Controls } else { - extData.RowDefinitions.InternalTrimToSize(); + // extData.RowDefinitions.InternalTrimToSize(); - if (extData.RowDefinitions.InternalCount == 0) + if (extData.RowDefinitions.Count == 0) { // if row definitions collection is empty // mockup array with one row @@ -635,7 +646,7 @@ namespace Avalonia.Controls } else { - extData.DefinitionsV = extData.RowDefinitions.InternalItems; + extData.DefinitionsV = extData.RowDefinitions.ToArray(); } } @@ -1844,8 +1855,8 @@ namespace Avalonia.Controls // unrounded sizes, to avoid breaking assumptions in the previous phases if (UseLayoutRounding) { - DpiScale dpiScale = GetDpi(); - double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; + var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 96; + double[] roundingErrors = RoundingErrors; double roundedTakenSize = 0.0; @@ -1853,7 +1864,7 @@ namespace Avalonia.Controls for (int i = 0; i < definitions.Length; ++i) { DefinitionBase def = definitions[i]; - double roundedSize = Control.RoundLayoutValue(def.SizeCache, dpi); + double roundedSize = RoundLayoutValue(def.SizeCache, dpi); roundingErrors[i] = (roundedSize - def.SizeCache); def.SizeCache = roundedSize; roundedTakenSize += roundedSize; @@ -2099,6 +2110,30 @@ namespace Avalonia.Controls _flags = value ? (_flags | flags) : (_flags & (~flags)); } + private double RoundLayoutValue(double value, double dpiScale) + { + double newValue; + + // If DPI == 1, don't use DPI-aware rounding. + if (!MathUtilities.AreClose(dpiScale, 1.0)) + { + newValue = Math.Round(value * dpiScale) / dpiScale; + // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. + if (double.IsNaN(newValue) || + double.IsInfinity(newValue) || + MathUtilities.AreClose(newValue, Double.MaxValue)) + { + newValue = value; + } + } + else + { + newValue = Math.Round(value); + } + + return newValue; + } + /// /// CheckFlagsAnd returns true if all the flags in the /// given bitmask are set on the object. @@ -2142,10 +2177,8 @@ namespace Avalonia.Controls return value; } - private static void OnShowGridLinesPropertyChanged(AvaloniaPropertyChangedEventArgs e) + private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) { - var grid = e.Sender as Grid; - if (grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway && grid.ListenToNotifications) { @@ -2155,13 +2188,11 @@ namespace Avalonia.Controls grid.SetFlags((bool)e.NewValue, Flags.ShowGridLinesPropertyValue); } - private static void OnCellAttachedPropertyChanged(AvaloniaPropertyChangedEventArgs e) + private static void OnCellAttachedPropertyChanged(Visual child, AvaloniaPropertyChangedEventArgs e) { - var child = e.Sender as Visual; - if (child != null) { - Grid grid = child.GetVisualParent() as Grid; + var grid = child.GetVisualParent() as Grid; if (grid != null && grid.ExtData != null && grid.ListenToNotifications) From 58b6399a52eedc5005b552565dc9620066ebe4d3 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 24 May 2019 15:37:28 +0800 Subject: [PATCH 024/130] Part 8 of preliminary error-plugging, Grid running but somewhat broken. --- src/Avalonia.Controls/ColumnDefinition.cs | 6 +- src/Avalonia.Controls/DefinitionBase.cs | 117 +++++++++--------- src/Avalonia.Controls/GridWPF.cs | 105 ++++++++++++++-- src/Avalonia.Controls/RowDefinition.cs | 6 +- .../Utils/SharedSizeScopeHost.cs | 4 +- 5 files changed, 164 insertions(+), 74 deletions(-) diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs index d316881a05..8c9f6323a9 100644 --- a/src/Avalonia.Controls/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls /// /// Initializes a new instance of the class. /// - public ColumnDefinition() + public ColumnDefinition() : base(true) { } @@ -38,7 +38,7 @@ namespace Avalonia.Controls /// /// The width of the column. /// The width unit of the column. - public ColumnDefinition(double value, GridUnitType type) + public ColumnDefinition(double value, GridUnitType type): base(true) { Width = new GridLength(value, type); } @@ -47,7 +47,7 @@ namespace Avalonia.Controls /// Initializes a new instance of the class. /// /// The width of the column. - public ColumnDefinition(GridLength width) + public ColumnDefinition(GridLength width): base(true) { Width = width; } diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index e1c5979476..b25ae3ab59 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using Avalonia.Utilities; @@ -14,6 +15,13 @@ namespace Avalonia.Controls /// public class DefinitionBase : ContentControl { + /// + /// Static ctor. Used for static registration of properties. + /// + static DefinitionBase() + { + SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); + } /// /// Defines the property. @@ -57,21 +65,21 @@ namespace Avalonia.Controls /// internal void OnEnterParentTree() { - if (_sharedState == null) - { - // start with getting SharedSizeGroup value. - // this property is NOT inhereted which should result in better overall perf. - string sharedSizeGroupId = SharedSizeGroup; - if (sharedSizeGroupId != null) - { - SharedSizeScope privateSharedSizeScope = PrivateSharedSizeScope; - if (privateSharedSizeScope != null) - { - _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - _sharedState.AddMember(this); - } - } - } + // if (_sharedState == null) + // { + // // start with getting SharedSizeGroup value. + // // this property is NOT inhereted which should result in better overall perf. + // string sharedSizeGroupId = SharedSizeGroup; + // if (sharedSizeGroupId != null) + // { + // SharedSizeScope privateSharedSizeScope = PrivateSharedSizeScope; + // if (privateSharedSizeScope != null) + // { + // _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + // _sharedState.AddMember(this); + // } + // } + // } } /// @@ -124,7 +132,7 @@ namespace Avalonia.Controls /// /// This method needs to be internal to be accessable from derived classes. /// - internal static void OnUserSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + internal static void OnUserSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { DefinitionBase definition = (DefinitionBase)d; @@ -167,7 +175,7 @@ namespace Avalonia.Controls /// /// This method needs to be internal to be accessable from derived classes. /// - internal static void OnUserMinSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + internal static void OnUserMinSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { DefinitionBase definition = (DefinitionBase)d; @@ -187,7 +195,7 @@ namespace Avalonia.Controls internal static bool IsUserMinSizePropertyValueValid(object value) { double v = (double)value; - return (!MathUtilities.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); + return (!Double.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); } /// @@ -196,7 +204,7 @@ namespace Avalonia.Controls /// /// This method needs to be internal to be accessable from derived classes. /// - internal static void OnUserMaxSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + internal static void OnUserMaxSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { DefinitionBase definition = (DefinitionBase)d; @@ -216,7 +224,7 @@ namespace Avalonia.Controls internal static bool IsUserMaxSizePropertyValueValid(object value) { double v = (double)value; - return (!MathUtilities.IsNaN(v) && v >= 0.0d); + return (!Double.IsNaN(v) && v >= 0.0d); } /// @@ -230,7 +238,7 @@ namespace Avalonia.Controls /// elements belonging to a certain scope can easily access SharedSizeState collection. As well /// as been norified about enter / exit a scope. /// - internal static void OnIsSharedSizeScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + internal static void OnIsSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { // is it possible to optimize here something like this: // if ((bool)d.GetValue(Grid.IsSharedSizeScopeProperty) == (d.GetLocalValue(PrivateSharedSizeScopeProperty) != null) @@ -238,11 +246,11 @@ namespace Avalonia.Controls if ((bool)e.NewValue) { SharedSizeScope sharedStatesCollection = new SharedSizeScope(); - d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection); + // d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection); } else { - d.ClearValue(PrivateSharedSizeScopeProperty); + // d.ClearValue(PrivateSharedSizeScopeProperty); } } @@ -307,7 +315,7 @@ namespace Avalonia.Controls /// /// Layout-time user size type. /// - internal Grid.GridLayoutTimeSizeType SizeType + internal Grid.LayoutTimeSizeType SizeType { get { return (_sizeType); } set { _sizeType = value; } @@ -333,7 +341,7 @@ namespace Avalonia.Controls get { double preferredSize = MinSize; - if (_sizeType != Grid.GridLayoutTimeSizeType.Auto + if (_sizeType != Grid.LayoutTimeSizeType.Auto && preferredSize < _measureSize) { preferredSize = _measureSize; @@ -477,10 +485,8 @@ namespace Avalonia.Controls /// /// /// - private static void OnSharedSizeGroupPropertyChanged(AvaloniaPropertyChangedEventArgs e) + private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) { - DefinitionBase definition = (DefinitionBase)e.Sender; - if (definition.InParentLogicalTree) { string sharedSizeGroupId = (string)e.NewValue; @@ -495,14 +501,14 @@ namespace Avalonia.Controls if ((definition._sharedState == null) && (sharedSizeGroupId != null)) { - SharedSizeScope privateSharedSizeScope = definition.PrivateSharedSizeScope; - if (privateSharedSizeScope != null) - { - // if definition is not registered and both: shared size group id AND private shared scope - // are available, then register definition. - definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - definition._sharedState.AddMember(definition); - } + // SharedSizeScope privateSharedSizeScope = definition.PrivateSharedSizeScope; + // if (privateSharedSizeScope != null) + // { + // // if definition is not registered and both: shared size group id AND private shared scope + // // are available, then register definition. + // definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + // definition._sharedState.AddMember(definition); + // } } } } @@ -513,12 +519,12 @@ namespace Avalonia.Controls /// b) contains only letters, digits and underscore ('_'). /// c) does not start with a digit. /// - private static bool SharedSizeGroupPropertyValueValid(string value) + private static string SharedSizeGroupPropertyValueValid(DefinitionBase _, string value) { // null is default value if (value == null) { - return (true); + return value; } string id = (string)value; @@ -541,11 +547,11 @@ namespace Avalonia.Controls if (i == id.Length) { - return (true); + return value; } } - return (false); + return null; } /// @@ -596,13 +602,13 @@ namespace Avalonia.Controls #region Private Properties - /// - /// Private getter of shared state collection dynamic property. - /// - private SharedSizeScope PrivateSharedSizeScope - { - get { return (SharedSizeScope)GetValue(PrivateSharedSizeScopeProperty); } - } + // /// + // /// Private getter of shared state collection dynamic property. + // /// + // private SharedSizeScope PrivateSharedSizeScope + // { + // get { return (SharedSizeScope)GetValue(PrivateSharedSizeScopeProperty); } + // } /// /// Convenience accessor to UseSharedMinimum flag @@ -635,7 +641,7 @@ namespace Avalonia.Controls private Flags _flags; // flags reflecting various aspects of internal state private int _parentIndex; // this instance's index in parent's children collection - private Grid.GridLayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" + private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure @@ -716,7 +722,7 @@ namespace Avalonia.Controls _sharedSizeScope = sharedSizeScope; _sharedSizeGroupId = sharedSizeGroupId; _registry = new List(); - _layoutUpdated = new EventHandler(OnLayoutUpdated); + // _layoutUpdated = new EventHandler(OnLayoutUpdated); _broadcastInvalidation = true; } @@ -775,7 +781,8 @@ namespace Avalonia.Controls if (_layoutUpdatedHost == null) { _layoutUpdatedHost = layoutUpdatedHost; - _layoutUpdatedHost.LayoutUpdated += _layoutUpdated; + // PORTING HACK... Remove this when resolved. + _layoutUpdatedHost.GetSubject(Visual.BoundsProperty).Subscribe(p => _layoutUpdated?.Invoke(this, null)); } } @@ -884,7 +891,7 @@ namespace Avalonia.Controls _minSize = sharedMinSize; - _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; + // _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; _layoutUpdatedHost = null; _broadcastInvalidation = true; @@ -902,14 +909,6 @@ namespace Avalonia.Controls } - /// - /// Static ctor. Used for static registration of properties. - /// - static DefinitionBase() - { - SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - } - #endregion Properties } } diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index d42d6fc651..c9397e2e20 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -84,6 +84,98 @@ namespace Avalonia.Controls set { SetValue(ShowGridLinesProperty, value); } } + + /// + /// Gets the value of the Column attached property for a control. + /// + /// The control. + /// The control's column. + public static int GetColumn(AvaloniaObject element) + { + return element.GetValue(ColumnProperty); + } + + /// + /// Gets the value of the ColumnSpan attached property for a control. + /// + /// The control. + /// The control's column span. + public static int GetColumnSpan(AvaloniaObject element) + { + return element.GetValue(ColumnSpanProperty); + } + + /// + /// Gets the value of the Row attached property for a control. + /// + /// The control. + /// The control's row. + public static int GetRow(AvaloniaObject element) + { + return element.GetValue(RowProperty); + } + + /// + /// Gets the value of the RowSpan attached property for a control. + /// + /// The control. + /// The control's row span. + public static int GetRowSpan(AvaloniaObject element) + { + return element.GetValue(RowSpanProperty); + } + + + /// + /// Gets the value of the IsSharedSizeScope attached property for a control. + /// + /// The control. + /// The control's IsSharedSizeScope value. + public static bool GetIsSharedSizeScope(AvaloniaObject element) + { + return element.GetValue(IsSharedSizeScopeProperty); + } + + /// + /// Sets the value of the Column attached property for a control. + /// + /// The control. + /// The column value. + public static void SetColumn(AvaloniaObject element, int value) + { + element.SetValue(ColumnProperty, value); + } + + /// + /// Sets the value of the ColumnSpan attached property for a control. + /// + /// The control. + /// The column span value. + public static void SetColumnSpan(AvaloniaObject element, int value) + { + element.SetValue(ColumnSpanProperty, value); + } + + /// + /// Sets the value of the Row attached property for a control. + /// + /// The control. + /// The row value. + public static void SetRow(AvaloniaObject element, int value) + { + element.SetValue(RowProperty, value); + } + + /// + /// Sets the value of the RowSpan attached property for a control. + /// + /// The control. + /// The row span value. + public static void SetRowSpan(AvaloniaObject element, int value) + { + element.SetValue(RowSpanProperty, value); + } + private ColumnDefinitions _columnDefinitions; private RowDefinitions _rowDefinitions; @@ -484,12 +576,11 @@ namespace Avalonia.Controls for (int i = PrivateCells.Length - 1; i >= 0; --i) { - var child = Children[i]; + var child = Children[i] as Control; if (child == null) { continue; } - CellCache cell = new CellCache(); // @@ -499,20 +590,20 @@ namespace Avalonia.Controls // read indices from the corresponding properties // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(child.GetValue(Grid.ColumnProperty), DefinitionsU.Length - 1); + cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Length - 1); // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(child.GetValue(Grid.RowProperty), DefinitionsV.Length - 1); + cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Length - 1); // read span properties // clamp to not exceed beyond right side of the grid // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(child.GetValue(Grid.ColumnSpanProperty), DefinitionsU.Length - cell.ColumnIndex); + cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Length - cell.ColumnIndex); // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(child.GetValue(Grid.RowSpanProperty), DefinitionsV.Length - cell.RowIndex); - + cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Length - cell.RowIndex); + Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Length); Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Length); diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs index 7307843417..d42ffdfc28 100644 --- a/src/Avalonia.Controls/RowDefinition.cs +++ b/src/Avalonia.Controls/RowDefinition.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls /// /// Initializes a new instance of the class. /// - public RowDefinition() + public RowDefinition() : base(false) { } @@ -38,7 +38,7 @@ namespace Avalonia.Controls /// /// The height of the row. /// The height unit of the column. - public RowDefinition(double value, GridUnitType type) + public RowDefinition(double value, GridUnitType type): base(false) { Height = new GridLength(value, type); } @@ -47,7 +47,7 @@ namespace Avalonia.Controls /// Initializes a new instance of the class. /// /// The height of the column. - public RowDefinition(GridLength height) + public RowDefinition(GridLength height): base(false) { Height = height; } diff --git a/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs b/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs index 8553165e4b..5f70557385 100644 --- a/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs +++ b/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs @@ -395,8 +395,8 @@ namespace Avalonia.Controls /// public void Dispose() { - while (_measurementCaches.Any()) - _measurementCaches[0].Grid.SharedScopeChanged(); + // while (_measurementCaches.Any()) + // _measurementCaches[0].Grid.SharedScopeChanged(); } /// From efdcd1195222caf1e60ee4f068df142ca0cf463a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 24 May 2019 23:22:51 +0800 Subject: [PATCH 025/130] Part 9 of n --- src/Avalonia.Controls/DefinitionBase.cs | 1 + src/Avalonia.Controls/GridWPF.cs | 27 +++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index b25ae3ab59..f9a7fc648d 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -21,6 +21,7 @@ namespace Avalonia.Controls static DefinitionBase() { SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); + BoundsProperty.Changed.AddClassHandler(OnUserSizePropertyChanged); } /// diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index c9397e2e20..e85fa1cdb0 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -80,11 +80,10 @@ namespace Avalonia.Controls /// public bool ShowGridLines { - get { return (CheckFlagsAnd(Flags.ShowGridLinesPropertyValue)); } + get { return GetValue(ShowGridLinesProperty); } set { SetValue(ShowGridLinesProperty, value); } } - /// /// Gets the value of the Column attached property for a control. /// @@ -186,7 +185,6 @@ namespace Avalonia.Controls { get { - if (_data == null) { _data = new ExtendedData(); } if (_columnDefinitions == null) { @@ -204,6 +202,9 @@ namespace Avalonia.Controls throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented."); } + if (_data == null) { _data = new ExtendedData(); } + + _columnDefinitions = value; _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); @@ -217,8 +218,6 @@ namespace Avalonia.Controls { get { - if (_data == null) { _data = new ExtendedData(); } - if (_rowDefinitions == null) { RowDefinitions = new RowDefinitions(); @@ -234,12 +233,17 @@ namespace Avalonia.Controls throw new NotSupportedException("Reassigning RowDefinitions not yet implemented."); } + if (_data == null) { _data = new ExtendedData(); } + _rowDefinitions = value; _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); } } + private bool rowColDefsEmpty => (RowDefinitions == null || RowDefinitions?.Count == 0 ) && + (ColumnDefinitions == null || ColumnDefinitions?.Count == 0); + /// /// Content measurement. /// @@ -255,7 +259,7 @@ namespace Avalonia.Controls ListenToNotifications = true; MeasureOverrideInProgress = true; - if (extData == null) + if (rowColDefsEmpty) { gridDesiredSize = new Size(); @@ -400,7 +404,7 @@ namespace Avalonia.Controls ArrangeOverrideInProgress = true; - if (_data == null) + if (rowColDefsEmpty) { for (int i = 0, count = Children.Count; i < count; ++i) { @@ -603,7 +607,7 @@ namespace Avalonia.Controls // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Length - cell.RowIndex); - + Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Length); Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Length); @@ -2275,8 +2279,6 @@ namespace Avalonia.Controls { grid.InvalidateVisual(); } - - grid.SetFlags((bool)e.NewValue, Flags.ShowGridLinesPropertyValue); } private static void OnCellAttachedPropertyChanged(Visual child, AvaloniaPropertyChangedEventArgs e) @@ -2590,11 +2592,6 @@ namespace Avalonia.Controls ValidDefinitionsVStructure = 0x00000002, ValidCellsStructure = 0x00000004, - // - // boolean properties state - // - ShowGridLinesPropertyValue = 0x00000100, // show grid lines ? - // // boolean flags // From 9dc40dc99bb4555d0b76614103ea5a7baca2e43c Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 25 May 2019 11:39:40 +0800 Subject: [PATCH 026/130] Temporarily hook row and col def change to ExtData. --- src/Avalonia.Controls/GridWPF.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index e85fa1cdb0..89ae5c6bf5 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -207,7 +207,12 @@ namespace Avalonia.Controls _columnDefinitions = value; _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); - _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); + _columnDefinitions.CollectionChanged += (_, __) => + { + _data.DefinitionsU = _columnDefinitions.Select(p => (DefinitionBase)p).ToArray(); + InvalidateMeasure(); + }; + } } @@ -237,11 +242,15 @@ namespace Avalonia.Controls _rowDefinitions = value; _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); - _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); + _rowDefinitions.CollectionChanged += (_, __) => + { + _data.DefinitionsV = _rowDefinitions.Select(p => (DefinitionBase)p).ToArray(); + InvalidateMeasure(); + }; } } - private bool rowColDefsEmpty => (RowDefinitions == null || RowDefinitions?.Count == 0 ) && + private bool rowColDefsEmpty => (RowDefinitions == null || RowDefinitions?.Count == 0) && (ColumnDefinitions == null || ColumnDefinitions?.Count == 0); /// From 9b1eb2ac8c752b9b3061a1d60cee2d0e7adb6a20 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 00:29:43 +0800 Subject: [PATCH 027/130] Part 10 of n --- src/Avalonia.Controls/ColumnDefinition.cs | 12 +- src/Avalonia.Controls/DefinitionBase.cs | 370 ++-------------------- src/Avalonia.Controls/GridWPF.cs | 209 +++--------- src/Avalonia.Controls/RowDefinition.cs | 13 +- 4 files changed, 101 insertions(+), 503 deletions(-) diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs index 8c9f6323a9..015484dbcc 100644 --- a/src/Avalonia.Controls/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls /// /// Initializes a new instance of the class. /// - public ColumnDefinition() : base(true) + public ColumnDefinition() { } @@ -38,7 +38,7 @@ namespace Avalonia.Controls /// /// The width of the column. /// The width unit of the column. - public ColumnDefinition(double value, GridUnitType type): base(true) + public ColumnDefinition(double value, GridUnitType type) { Width = new GridLength(value, type); } @@ -47,7 +47,7 @@ namespace Avalonia.Controls /// Initializes a new instance of the class. /// /// The width of the column. - public ColumnDefinition(GridLength width): base(true) + public ColumnDefinition(GridLength width) { Width = width; } @@ -87,5 +87,11 @@ namespace Avalonia.Controls get { return GetValue(WidthProperty); } set { SetValue(WidthProperty, value); } } + + internal override GridLength UserSizeValueCache => this.Width; + + internal override double UserMinSizeValueCache => this.MinWidth; + + internal override double UserMaxSizeValueCache => this.MaxWidth; } } diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index f9a7fc648d..643d9da751 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls /// /// Base class for and . /// - public class DefinitionBase : ContentControl + public abstract class DefinitionBase : ContentControl { /// /// Static ctor. Used for static registration of properties. @@ -21,8 +21,9 @@ namespace Avalonia.Controls static DefinitionBase() { SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - BoundsProperty.Changed.AddClassHandler(OnUserSizePropertyChanged); + // BoundsProperty.Changed.AddClassHandler(OnUserSizePropertyChanged); } + /// /// Defines the property. @@ -38,63 +39,6 @@ namespace Avalonia.Controls get { return GetValue(SharedSizeGroupProperty); } set { SetValue(SharedSizeGroupProperty, value); } } - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - - internal DefinitionBase(bool isColumnDefinition) - { - _isColumnDefinition = isColumnDefinition; - _parentIndex = -1; - } - - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - /// - /// Callback to notify about entering model tree. - /// - internal void OnEnterParentTree() - { - // if (_sharedState == null) - // { - // // start with getting SharedSizeGroup value. - // // this property is NOT inhereted which should result in better overall perf. - // string sharedSizeGroupId = SharedSizeGroup; - // if (sharedSizeGroupId != null) - // { - // SharedSizeScope privateSharedSizeScope = PrivateSharedSizeScope; - // if (privateSharedSizeScope != null) - // { - // _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - // _sharedState.AddMember(this); - // } - // } - // } - } - - /// - /// Callback to notify about exitting model tree. - /// - internal void OnExitParentTree() - { - _offset = 0; - if (_sharedState != null) - { - _sharedState.RemoveMember(this); - _sharedState = null; - } - } /// /// Performs action preparing definition to enter layout calculation mode. @@ -109,161 +53,6 @@ namespace Avalonia.Controls if (_sharedState != null) { _sharedState.EnsureDeferredValidation(grid); } } - /// - /// Updates min size. - /// - /// New size. - internal void UpdateMinSize(double minSize) - { - _minSize = Math.Max(_minSize, minSize); - } - - /// - /// Sets min size. - /// - /// New size. - internal void SetMinSize(double minSize) - { - _minSize = minSize; - } - - /// - /// - /// - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal static void OnUserSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) - { - DefinitionBase definition = (DefinitionBase)d; - - if (definition.InParentLogicalTree) - { - if (definition._sharedState != null) - { - definition._sharedState.Invalidate(); - } - else - { - Grid parentGrid = (Grid)definition.Parent; - - if (((GridLength)e.OldValue).GridUnitType != ((GridLength)e.NewValue).GridUnitType) - { - parentGrid.Invalidate(); - } - else - { - parentGrid.InvalidateMeasure(); - } - } - } - } - - /// - /// - /// - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal static bool IsUserSizePropertyValueValid(object value) - { - return (((GridLength)value).Value >= 0); - } - - /// - /// - /// - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal static void OnUserMinSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) - { - DefinitionBase definition = (DefinitionBase)d; - - if (definition.InParentLogicalTree) - { - Grid parentGrid = (Grid)definition.Parent; - parentGrid.InvalidateMeasure(); - } - } - - /// - /// - /// - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal static bool IsUserMinSizePropertyValueValid(object value) - { - double v = (double)value; - return (!Double.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); - } - - /// - /// - /// - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal static void OnUserMaxSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) - { - DefinitionBase definition = (DefinitionBase)d; - - if (definition.InParentLogicalTree) - { - Grid parentGrid = (Grid)definition.Parent; - parentGrid.InvalidateMeasure(); - } - } - - /// - /// - /// - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal static bool IsUserMaxSizePropertyValueValid(object value) - { - double v = (double)value; - return (!Double.IsNaN(v) && v >= 0.0d); - } - - /// - /// - /// - /// - /// This method reflects Grid.SharedScopeProperty state by setting / clearing - /// dynamic property PrivateSharedSizeScopeProperty. Value of PrivateSharedSizeScopeProperty - /// is a collection of SharedSizeState objects for the scope. - /// Also PrivateSharedSizeScopeProperty is FrameworkPropertyMetadataOptions.Inherits property. So that all children - /// elements belonging to a certain scope can easily access SharedSizeState collection. As well - /// as been norified about enter / exit a scope. - /// - internal static void OnIsSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) - { - // is it possible to optimize here something like this: - // if ((bool)d.GetValue(Grid.IsSharedSizeScopeProperty) == (d.GetLocalValue(PrivateSharedSizeScopeProperty) != null) - // { /* do nothing */ } - if ((bool)e.NewValue) - { - SharedSizeScope sharedStatesCollection = new SharedSizeScope(); - // d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection); - } - else - { - // d.ClearValue(PrivateSharedSizeScopeProperty); - } - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Internal Properties - // - //------------------------------------------------------ - - #region Internal Properties /// /// Returns true if this definition is a part of shared group. @@ -376,6 +165,25 @@ namespace Avalonia.Controls } return (minSize); } + + } + + /// + /// Updates min size. + /// + /// New size. + internal void UpdateMinSize(double minSize) + { + _minSize = Math.Max(_minSize, minSize); + } + + /// + /// Sets min size. + /// + /// New size. + internal void SetMinSize(double minSize) + { + _minSize = minSize; } /// @@ -408,44 +216,18 @@ namespace Avalonia.Controls /// /// Internal helper to access up-to-date UserSize property value. /// - internal GridLength UserSizeValueCache - { - get - { - return (GridLength)GetValue( - _isColumnDefinition ? - ColumnDefinition.WidthProperty : - RowDefinition.HeightProperty); - } - } + internal abstract GridLength UserSizeValueCache { get; } /// /// Internal helper to access up-to-date UserMinSize property value. /// - internal double UserMinSizeValueCache - { - get - { - return (double)GetValue( - _isColumnDefinition ? - ColumnDefinition.MinWidthProperty : - RowDefinition.MinHeightProperty); - } - } + internal abstract double UserMinSizeValueCache { get; } /// /// Internal helper to access up-to-date UserMaxSize property value. /// - internal double UserMaxSizeValueCache - { - get - { - return (double)GetValue( - _isColumnDefinition ? - ColumnDefinition.MaxWidthProperty : - RowDefinition.MaxHeightProperty); - } - } + internal abstract double UserMaxSizeValueCache { get; } + /// /// Protected. Returns true if this DefinitionBase instance is in parent's logical tree. @@ -455,37 +237,6 @@ namespace Avalonia.Controls get { return (_parentIndex != -1); } } - #endregion Internal Properties - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - /// - /// SetFlags is used to set or unset one or multiple - /// flags on the object. - /// - private void SetFlags(bool value, Flags flags) - { - _flags = value ? (_flags | flags) : (_flags & (~flags)); - } - - /// - /// CheckFlagsAnd returns true if all the flags in the - /// given bitmask are set on the object. - /// - private bool CheckFlagsAnd(Flags flags) - { - return ((_flags & flags) == flags); - } - - /// - /// - /// private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) { if (definition.InParentLogicalTree) @@ -593,31 +344,13 @@ namespace Avalonia.Controls } } - #endregion Private Methods - - //------------------------------------------------------ - // - // Private Properties - // - //------------------------------------------------------ - - #region Private Properties - - // /// - // /// Private getter of shared state collection dynamic property. - // /// - // private SharedSizeScope PrivateSharedSizeScope - // { - // get { return (SharedSizeScope)GetValue(PrivateSharedSizeScopeProperty); } - // } - /// /// Convenience accessor to UseSharedMinimum flag /// private bool UseSharedMinimum { - get { return (CheckFlagsAnd(Flags.UseSharedMinimum)); } - set { SetFlags(value, Flags.UseSharedMinimum); } + get { return _useSharedMinimum; } + set { _useSharedMinimum = value; } } /// @@ -625,22 +358,11 @@ namespace Avalonia.Controls /// private bool LayoutWasUpdated { - get { return (CheckFlagsAnd(Flags.LayoutWasUpdated)); } - set { SetFlags(value, Flags.LayoutWasUpdated); } + get { return _layoutWasUpdated; } + set { _layoutWasUpdated = value; } } - #endregion Private Properties - - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields - private readonly bool _isColumnDefinition; // when "true", this is a ColumnDefinition; when "false" this is a RowDefinition (faster than a type check) - private Flags _flags; // flags reflecting various aspects of internal state - private int _parentIndex; // this instance's index in parent's children collection + private int _parentIndex = -1; // this instance's index in parent's children collection private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" @@ -650,29 +372,10 @@ namespace Avalonia.Controls private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) private SharedSizeState _sharedState; // reference to shared state object this instance is registered with + private bool _layoutWasUpdated; + private bool _useSharedMinimum; - internal const bool ThisIsColumnDefinition = true; - internal const bool ThisIsRowDefinition = false; - - #endregion Private Fields - //------------------------------------------------------ - // - // Private Structures / Classes - // - //------------------------------------------------------ - - #region Private Structures Classes - - [System.Flags] - private enum Flags : byte - { - // - // bool flags - // - UseSharedMinimum = 0x00000020, // when "1", definition will take into account shared state's minimum - LayoutWasUpdated = 0x00000040, // set to "1" every time the parent grid is measured - } /// /// Collection of shared states objects for a single scope @@ -908,10 +611,5 @@ namespace Avalonia.Controls private GridLength _userSize; // shared state private double _minSize; // shared state } - - - #endregion Properties } -} - - +} \ No newline at end of file diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 89ae5c6bf5..d049f9fd4b 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -186,6 +186,7 @@ namespace Avalonia.Controls get { + if (_columnDefinitions == null) { ColumnDefinitions = new ColumnDefinitions(); @@ -207,9 +208,13 @@ namespace Avalonia.Controls _columnDefinitions = value; _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); - _columnDefinitions.CollectionChanged += (_, __) => + ColumnDefinitionsDirty = true; + DefinitionsU = _columnDefinitions.Cast().ToArray(); + + _columnDefinitions.CollectionChanged += (_, e) => { - _data.DefinitionsU = _columnDefinitions.Select(p => (DefinitionBase)p).ToArray(); + DefinitionsU = e.NewItems.Cast().ToArray(); + ColumnDefinitionsDirty = true; InvalidateMeasure(); }; @@ -242,16 +247,21 @@ namespace Avalonia.Controls _rowDefinitions = value; _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); - _rowDefinitions.CollectionChanged += (_, __) => + RowDefinitionsDirty = true; + + DefinitionsV = _rowDefinitions.Cast().ToArray(); + + _rowDefinitions.CollectionChanged += (_, e) => { - _data.DefinitionsV = _rowDefinitions.Select(p => (DefinitionBase)p).ToArray(); + DefinitionsV = e.NewItems.Cast().ToArray(); + RowDefinitionsDirty = true; InvalidateMeasure(); }; } } - private bool rowColDefsEmpty => (RowDefinitions == null || RowDefinitions?.Count == 0) && - (ColumnDefinitions == null || ColumnDefinitions?.Count == 0); + private bool rowColDefsEmpty => (DefinitionsU.Length == 0) && + (DefinitionsV.Length == 0); /// /// Content measurement. @@ -309,10 +319,10 @@ namespace Avalonia.Controls } } - ValidateDefinitionsUStructure(); + // ValidateColumnDefinitionsStructure(); ValidateDefinitionsLayout(DefinitionsU, sizeToContentU); - ValidateDefinitionsVStructure(); + // ValidateRowDefinitionsStructure(); ValidateDefinitionsLayout(DefinitionsV, sizeToContentV); CellsStructureDirty |= (SizeToContentU != sizeToContentU) || (SizeToContentV != sizeToContentV); @@ -453,12 +463,12 @@ namespace Avalonia.Controls cell.Arrange(cellRect); } - // update render bound on grid lines renderer visual - var gridLinesRenderer = EnsureGridLinesRenderer(); - if (gridLinesRenderer != null) - { - gridLinesRenderer.UpdateRenderBounds(arrangeSize); - } + // // update render bound on grid lines renderer visual + // var gridLinesRenderer = EnsureGridLinesRenderer(); + // if (gridLinesRenderer != null) + // { + // gridLinesRenderer.UpdateRenderBounds(arrangeSize); + // } } } finally @@ -493,7 +503,7 @@ namespace Avalonia.Controls // actual value calculations require structure to be up-to-date if (!ColumnDefinitionsDirty) { - DefinitionBase[] definitions = DefinitionsU; + var definitions = DefinitionsU; value = definitions[(columnIndex + 1) % definitions.Length].FinalOffset; if (columnIndex != 0) { value -= definitions[columnIndex].FinalOffset; } } @@ -515,7 +525,7 @@ namespace Avalonia.Controls // actual value calculations require structure to be up-to-date if (!RowDefinitionsDirty) { - DefinitionBase[] definitions = DefinitionsV; + var definitions = DefinitionsV; value = definitions[(rowIndex + 1) % definitions.Length].FinalOffset; if (rowIndex != 0) { value -= definitions[rowIndex].FinalOffset; } } @@ -525,38 +535,20 @@ namespace Avalonia.Controls /// /// Convenience accessor to MeasureOverrideInProgress bit flag. /// - internal bool MeasureOverrideInProgress - { - get { return (CheckFlagsAnd(Flags.MeasureOverrideInProgress)); } - set { SetFlags(value, Flags.MeasureOverrideInProgress); } - } - + internal bool MeasureOverrideInProgress { get; set; } /// /// Convenience accessor to ArrangeOverrideInProgress bit flag. /// - internal bool ArrangeOverrideInProgress - { - get { return (CheckFlagsAnd(Flags.ArrangeOverrideInProgress)); } - set { SetFlags(value, Flags.ArrangeOverrideInProgress); } - } - + internal bool ArrangeOverrideInProgress { get; set; } /// - /// Convenience accessor to ValidDefinitionsUStructure bit flag. + /// Convenience accessor to ValidColumnDefinitionsStructure bit flag. /// - internal bool ColumnDefinitionsDirty - { - get { return (!CheckFlagsAnd(Flags.ValidDefinitionsUStructure)); } - set { SetFlags(!value, Flags.ValidDefinitionsUStructure); } - } + internal bool ColumnDefinitionsDirty { get; set; } /// - /// Convenience accessor to ValidDefinitionsVStructure bit flag. + /// Convenience accessor to ValidRowDefinitionsStructure bit flag. /// - internal bool RowDefinitionsDirty - { - get { return (!CheckFlagsAnd(Flags.ValidDefinitionsVStructure)); } - set { SetFlags(!value, Flags.ValidDefinitionsVStructure); } - } + internal bool RowDefinitionsDirty { get; set; } /// /// Lays out cells according to rows and columns, and creates lookup grids. @@ -674,92 +666,6 @@ namespace Avalonia.Controls HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows; } - /// - /// Initializes DefinitionsU memeber either to user supplied ColumnDefinitions collection - /// or to a default single element collection. DefinitionsU gets trimmed to size. - /// - /// - /// This is one of two methods, where ColumnDefinitions and DefinitionsU are directly accessed. - /// All the rest measure / arrange / render code must use DefinitionsU. - /// - private void ValidateDefinitionsUStructure() - { - if (ColumnDefinitionsDirty) - { - ExtendedData extData = ExtData; - - if (extData.ColumnDefinitions == null) - { - if (extData.DefinitionsU == null) - { - extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() }; - } - } - else - { - // extData.ColumnDefinitions.InternalTrimToSize(); - - if (extData.ColumnDefinitions.Count == 0) - { - // if column definitions collection is empty - // mockup array with one column - extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() }; - } - else - { - extData.DefinitionsU = extData.ColumnDefinitions.ToArray(); - } - } - - ColumnDefinitionsDirty = false; - } - - Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Length > 0); - } - - /// - /// Initializes DefinitionsV memeber either to user supplied RowDefinitions collection - /// or to a default single element collection. DefinitionsV gets trimmed to size. - /// - /// - /// This is one of two methods, where RowDefinitions and DefinitionsV are directly accessed. - /// All the rest measure / arrange / render code must use DefinitionsV. - /// - private void ValidateDefinitionsVStructure() - { - if (RowDefinitionsDirty) - { - ExtendedData extData = ExtData; - - if (extData.RowDefinitions == null) - { - if (extData.DefinitionsV == null) - { - extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() }; - } - } - else - { - // extData.RowDefinitions.InternalTrimToSize(); - - if (extData.RowDefinitions.Count == 0) - { - // if row definitions collection is empty - // mockup array with one row - extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() }; - } - else - { - extData.DefinitionsV = extData.RowDefinitions.ToArray(); - } - } - - RowDefinitionsDirty = false; - } - - Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Length > 0); - } - /// /// Validates layout time size type information on given array of definitions. /// Sets MinSize and MeasureSizes. @@ -1607,7 +1513,7 @@ namespace Avalonia.Controls /// Array of definitions to use for calculations. /// Desired size. private double CalculateDesiredSize( - DefinitionBase[] definitions) + DefinitionBase[] definitions) { double desiredSize = 0; @@ -1762,9 +1668,9 @@ namespace Avalonia.Controls double remainingAvailableSize = finalSize - takenSize; double remainingStarWeight = totalStarWeight - takenStarWeight; - MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions); + MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer((DefinitionBase[])definitions); Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer); - MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions); + MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer((DefinitionBase[])definitions); Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer); while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) @@ -2147,7 +2053,7 @@ namespace Avalonia.Controls /// Number of items in the range. /// Final size. private double GetFinalSizeForRange( - DefinitionBase[] definitions, + DefinitionBase[] definitions, int start, int count) { @@ -2338,22 +2244,6 @@ namespace Avalonia.Controls return (result != 2); } - /// - /// Private version returning array of column definitions. - /// - private DefinitionBase[] DefinitionsU - { - get { return (ExtData.DefinitionsU); } - } - - /// - /// Private version returning array of row definitions. - /// - private DefinitionBase[] DefinitionsV - { - get { return (ExtData.DefinitionsV); } - } - /// /// Helper accessor to layout time array of definitions. /// @@ -2545,7 +2435,7 @@ namespace Avalonia.Controls } } - private ExtendedData _data; // extended data instantiated on demand, for non-trivial case handling only + private ExtendedData _data = new ExtendedData(); // extended data instantiated on demand, for non-trivial case handling only private Flags _flags; // grid validity / property caches dirtiness flags private GridLinesRenderer _gridLinesRenderer; @@ -2554,7 +2444,8 @@ namespace Avalonia.Controls // Stores unrounded values and rounding errors during layout rounding. double[] _roundingErrors; - + private DefinitionBase[] DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + private DefinitionBase[] DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; private const double c_epsilon = 1e-5; // used in fp calculations private const double c_starClip = 1e298; // used as maximum for clipping star values during normalization private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop @@ -2572,17 +2463,13 @@ namespace Avalonia.Controls /// private class ExtendedData { - internal ColumnDefinitions ColumnDefinitions; // collection of column definitions (logical tree support) - internal RowDefinitions RowDefinitions; // collection of row definitions (logical tree support) - internal DefinitionBase[] DefinitionsU; // collection of column definitions used during calc - internal DefinitionBase[] DefinitionsV; // collection of row definitions used during calc internal CellCache[] CellCachesCollection; // backing store for logical Children internal int CellGroup1; // index of the first cell in first cell group internal int CellGroup2; // index of the first cell in second cell group internal int CellGroup3; // index of the first cell in third cell group internal int CellGroup4; // index of the first cell in forth cell group internal DefinitionBase[] TempDefinitions; // temporary array used during layout for various purposes - // TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length) + // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length) } /// @@ -2597,8 +2484,8 @@ namespace Avalonia.Controls // * Valid???Layout flags indicate that layout time portion of the information // stored on the objects should be updated. // - ValidDefinitionsUStructure = 0x00000001, - ValidDefinitionsVStructure = 0x00000002, + ValidColumnDefinitionsStructure = 0x00000001, + ValidRowDefinitionsStructure = 0x00000002, ValidCellsStructure = 0x00000004, // @@ -3188,20 +3075,20 @@ namespace Avalonia.Controls return; } - for (int i = 1; i < grid.DefinitionsU.Length; ++i) + for (int i = 1; i < grid.ColumnDefinitions.Count; ++i) { DrawGridLine( drawingContext, - grid.DefinitionsU[i].FinalOffset, 0.0, - grid.DefinitionsU[i].FinalOffset, lastArrangeSize.Height); + grid.ColumnDefinitions[i].FinalOffset, 0.0, + grid.ColumnDefinitions[i].FinalOffset, lastArrangeSize.Height); } - for (int i = 1; i < grid.DefinitionsV.Length; ++i) + for (int i = 1; i < grid.RowDefinitions.Count; ++i) { DrawGridLine( drawingContext, - 0.0, grid.DefinitionsV[i].FinalOffset, - lastArrangeSize.Width, grid.DefinitionsV[i].FinalOffset); + 0.0, grid.RowDefinitions[i].FinalOffset, + lastArrangeSize.Width, grid.RowDefinitions[i].FinalOffset); } } diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs index d42ffdfc28..1cb09e16e9 100644 --- a/src/Avalonia.Controls/RowDefinition.cs +++ b/src/Avalonia.Controls/RowDefinition.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls /// /// Initializes a new instance of the class. /// - public RowDefinition() : base(false) + public RowDefinition() { } @@ -38,7 +38,7 @@ namespace Avalonia.Controls /// /// The height of the row. /// The height unit of the column. - public RowDefinition(double value, GridUnitType type): base(false) + public RowDefinition(double value, GridUnitType type) { Height = new GridLength(value, type); } @@ -47,7 +47,7 @@ namespace Avalonia.Controls /// Initializes a new instance of the class. /// /// The height of the column. - public RowDefinition(GridLength height): base(false) + public RowDefinition(GridLength height) { Height = height; } @@ -87,5 +87,12 @@ namespace Avalonia.Controls get { return GetValue(HeightProperty); } set { SetValue(HeightProperty, value); } } + + + internal override GridLength UserSizeValueCache => this.Height; + + internal override double UserMinSizeValueCache => this.MinHeight; + + internal override double UserMaxSizeValueCache => this.MaxHeight; } } \ No newline at end of file From fa95d9cd10f7d0778a0a557c80caafa8c920334d Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 03:26:17 +0800 Subject: [PATCH 028/130] Fix rowcol definitions not sync'ing with DefinitionsU/V, hence fixing bugs for the Calendar control. --- src/Avalonia.Controls/GridWPF.cs | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index d049f9fd4b..0cd61d0bb1 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -185,8 +185,6 @@ namespace Avalonia.Controls { get { - - if (_columnDefinitions == null) { ColumnDefinitions = new ColumnDefinitions(); @@ -194,18 +192,8 @@ namespace Avalonia.Controls return _columnDefinitions; } - set - { - - if (_columnDefinitions != null) - { - throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented."); - } - - if (_data == null) { _data = new ExtendedData(); } - - + { _columnDefinitions = value; _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); ColumnDefinitionsDirty = true; @@ -213,7 +201,7 @@ namespace Avalonia.Controls _columnDefinitions.CollectionChanged += (_, e) => { - DefinitionsU = e.NewItems.Cast().ToArray(); + DefinitionsU = _columnDefinitions.Cast().ToArray(); ColumnDefinitionsDirty = true; InvalidateMeasure(); }; @@ -235,25 +223,18 @@ namespace Avalonia.Controls return _rowDefinitions; } - set { - if (_rowDefinitions != null) - { - throw new NotSupportedException("Reassigning RowDefinitions not yet implemented."); - } - - if (_data == null) { _data = new ExtendedData(); } - _rowDefinitions = value; _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); + RowDefinitionsDirty = true; DefinitionsV = _rowDefinitions.Cast().ToArray(); _rowDefinitions.CollectionChanged += (_, e) => { - DefinitionsV = e.NewItems.Cast().ToArray(); + DefinitionsV = _rowDefinitions.Cast().ToArray(); RowDefinitionsDirty = true; InvalidateMeasure(); }; From 0e15b3f972d132ec430a30d46ab76a9d4909c874 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 04:10:26 +0800 Subject: [PATCH 029/130] Cleaning up redundant/unreferenced code. --- src/Avalonia.Controls/GridWPF.cs | 653 ++++++++----------------------- 1 file changed, 157 insertions(+), 496 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 0cd61d0bb1..0549c7f20a 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -26,6 +26,49 @@ namespace Avalonia.Controls /// public class Grid : Panel { + internal bool CellsStructureDirty = true; + internal bool ListenToNotifications; + internal bool SizeToContentU; + internal bool SizeToContentV; + internal bool HasStarCellsU; + internal bool HasStarCellsV; + internal bool HasGroup3CellsInAutoRows; + + // index of the first cell in first cell group + internal int CellGroup1; + + // index of the first cell in second cell group + internal int CellGroup2; + + // index of the first cell in third cell group + internal int CellGroup3; + + // index of the first cell in fourth cell group + + internal int CellGroup4; + + // temporary array used during layout for various purposes + // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length) + internal DefinitionBase[] _tempDefinitions; + private GridLinesRenderer _gridLinesRenderer; + + // Keeps track of definition indices. + private int[] _definitionIndices; + + private CellCache[] _cellCache; + + + // Stores unrounded values and rounding errors during layout rounding. + private double[] _roundingErrors; + private DefinitionBase[] DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + private DefinitionBase[] DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; + private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop + private static readonly LocalDataStoreSlot s_tempDefinitionsDataSlot = Thread.AllocateDataSlot(); + private static readonly IComparer s_spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); + private static readonly IComparer s_spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); + private static readonly IComparer s_minRatioComparer = new MinRatioComparer(); + private static readonly IComparer s_maxRatioComparer = new MaxRatioComparer(); + private static readonly IComparer s_starWeightComparer = new StarWeightComparer(); static Grid() { @@ -193,19 +236,27 @@ namespace Avalonia.Controls return _columnDefinitions; } set - { + { _columnDefinitions = value; - _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); + _columnDefinitions.TrackItemPropertyChanged(_ => Invalidate()); ColumnDefinitionsDirty = true; - DefinitionsU = _columnDefinitions.Cast().ToArray(); + + if (_columnDefinitions.Count > 0) + DefinitionsU = _columnDefinitions.Cast().ToArray(); _columnDefinitions.CollectionChanged += (_, e) => { - DefinitionsU = _columnDefinitions.Cast().ToArray(); - ColumnDefinitionsDirty = true; - InvalidateMeasure(); + if (_columnDefinitions.Count == 0) + { + DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + } + else + { + DefinitionsU = _columnDefinitions.Cast().ToArray(); + ColumnDefinitionsDirty = true; + } + Invalidate(); }; - } } @@ -226,17 +277,25 @@ namespace Avalonia.Controls set { _rowDefinitions = value; - _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); + _rowDefinitions.TrackItemPropertyChanged(_ => Invalidate()); RowDefinitionsDirty = true; - DefinitionsV = _rowDefinitions.Cast().ToArray(); + if (_rowDefinitions.Count > 0) + DefinitionsV = _rowDefinitions.Cast().ToArray(); _rowDefinitions.CollectionChanged += (_, e) => { - DefinitionsV = _rowDefinitions.Cast().ToArray(); - RowDefinitionsDirty = true; - InvalidateMeasure(); + if (_rowDefinitions.Count == 0) + { + DefinitionsV = new DefinitionBase[1] { new ColumnDefinition() }; + } + else + { + DefinitionsV = _rowDefinitions.Cast().ToArray(); + RowDefinitionsDirty = true; + } + Invalidate(); }; } } @@ -252,7 +311,6 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size constraint) { Size gridDesiredSize; - ExtendedData extData = ExtData; try { @@ -316,7 +374,7 @@ namespace Avalonia.Controls Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); - MeasureCellsGroup(extData.CellGroup1, constraint, false, false); + MeasureCellsGroup(CellGroup1, constraint, false, false); { // after Group1 is measured, only Group3 may have cells belonging to Auto rows. @@ -325,19 +383,19 @@ namespace Avalonia.Controls if (canResolveStarsV) { if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } - MeasureCellsGroup(extData.CellGroup2, constraint, false, false); + MeasureCellsGroup(CellGroup2, constraint, false, false); if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } - MeasureCellsGroup(extData.CellGroup3, constraint, false, false); + MeasureCellsGroup(CellGroup3, constraint, false, false); } else { // if at least one cell exists in Group2, it must be measured before // StarsU can be resolved. - bool canResolveStarsU = extData.CellGroup2 > PrivateCells.Length; + bool canResolveStarsU = CellGroup2 > _cellCache.Length; if (canResolveStarsU) { if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } - MeasureCellsGroup(extData.CellGroup3, constraint, false, false); + MeasureCellsGroup(CellGroup3, constraint, false, false); if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } } else @@ -351,10 +409,10 @@ namespace Avalonia.Controls int cnt = 0; // Cache Group2MinWidths & Group3MinHeights - double[] group2MinSizes = CacheMinSizes(extData.CellGroup2, false); - double[] group3MinSizes = CacheMinSizes(extData.CellGroup3, true); + double[] group2MinSizes = CacheMinSizes(CellGroup2, false); + double[] group3MinSizes = CacheMinSizes(CellGroup3, true); - MeasureCellsGroup(extData.CellGroup2, constraint, false, true); + MeasureCellsGroup(CellGroup2, constraint, false, true); do { @@ -365,20 +423,20 @@ namespace Avalonia.Controls } if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } - MeasureCellsGroup(extData.CellGroup3, constraint, false, false); + MeasureCellsGroup(CellGroup3, constraint, false, false); // Reset cached Group2Widths ApplyCachedMinSizes(group2MinSizes, false); if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } - MeasureCellsGroup(extData.CellGroup2, constraint, cnt == c_layoutLoopMaxCount, false, out hasDesiredSizeUChanged); + MeasureCellsGroup(CellGroup2, constraint, cnt == c_layoutLoopMaxCount, false, out hasDesiredSizeUChanged); } while (hasDesiredSizeUChanged && ++cnt <= c_layoutLoopMaxCount); } } } - MeasureCellsGroup(extData.CellGroup4, constraint, false, false); + MeasureCellsGroup(CellGroup4, constraint, false, false); gridDesiredSize = new Size( CalculateDesiredSize(DefinitionsU), @@ -422,7 +480,7 @@ namespace Avalonia.Controls SetFinalSize(DefinitionsU, arrangeSize.Width, true); SetFinalSize(DefinitionsV, arrangeSize.Height, false); - for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) + for (int currentCell = 0; currentCell < _cellCache.Length; ++currentCell) { IControl cell = Children[currentCell]; if (cell == null) @@ -430,10 +488,10 @@ namespace Avalonia.Controls continue; } - int columnIndex = PrivateCells[currentCell].ColumnIndex; - int rowIndex = PrivateCells[currentCell].RowIndex; - int columnSpan = PrivateCells[currentCell].ColumnSpan; - int rowSpan = PrivateCells[currentCell].RowSpan; + int columnIndex = _cellCache[currentCell].ColumnIndex; + int rowIndex = _cellCache[currentCell].RowIndex; + int columnSpan = _cellCache[currentCell].ColumnSpan; + int rowSpan = _cellCache[currentCell].RowSpan; Rect cellRect = new Rect( columnIndex == 0 ? 0.0 : DefinitionsU[columnIndex].FinalOffset, @@ -469,109 +527,38 @@ namespace Avalonia.Controls InvalidateMeasure(); } - /// - /// Returns final width for a column. - /// - /// - /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data. - /// - internal double GetFinalColumnDefinitionWidth(int columnIndex) - { - double value = 0.0; - - Contract.Requires(_data != null); - - // actual value calculations require structure to be up-to-date - if (!ColumnDefinitionsDirty) - { - var definitions = DefinitionsU; - value = definitions[(columnIndex + 1) % definitions.Length].FinalOffset; - if (columnIndex != 0) { value -= definitions[columnIndex].FinalOffset; } - } - return (value); - } - - /// - /// Returns final height for a row. - /// - /// - /// Used from public RowDefinition ActualHeight. Calculates final height using offset data. - /// - internal double GetFinalRowDefinitionHeight(int rowIndex) - { - double value = 0.0; - - Contract.Requires(_data != null); - - // actual value calculations require structure to be up-to-date - if (!RowDefinitionsDirty) - { - var definitions = DefinitionsV; - value = definitions[(rowIndex + 1) % definitions.Length].FinalOffset; - if (rowIndex != 0) { value -= definitions[rowIndex].FinalOffset; } - } - return (value); - } - - /// - /// Convenience accessor to MeasureOverrideInProgress bit flag. - /// - internal bool MeasureOverrideInProgress { get; set; } - /// - /// Convenience accessor to ArrangeOverrideInProgress bit flag. - /// - internal bool ArrangeOverrideInProgress { get; set; } - /// - /// Convenience accessor to ValidColumnDefinitionsStructure bit flag. - /// - internal bool ColumnDefinitionsDirty { get; set; } - - /// - /// Convenience accessor to ValidRowDefinitionsStructure bit flag. - /// - internal bool RowDefinitionsDirty { get; set; } + internal bool MeasureOverrideInProgress; + internal bool ArrangeOverrideInProgress; + internal bool ColumnDefinitionsDirty; + internal bool RowDefinitionsDirty; /// /// Lays out cells according to rows and columns, and creates lookup grids. /// private void ValidateCells() { - if (CellsStructureDirty) - { - ValidateCellsCore(); - CellsStructureDirty = false; - } - } + if (!CellsStructureDirty) return; - /// - /// ValidateCellsCore - /// - private void ValidateCellsCore() - { - ExtendedData extData = ExtData; - - extData.CellCachesCollection = new CellCache[Children.Count]; - extData.CellGroup1 = int.MaxValue; - extData.CellGroup2 = int.MaxValue; - extData.CellGroup3 = int.MaxValue; - extData.CellGroup4 = int.MaxValue; + _cellCache = new CellCache[Children.Count]; + CellGroup1 = int.MaxValue; + CellGroup2 = int.MaxValue; + CellGroup3 = int.MaxValue; + CellGroup4 = int.MaxValue; bool hasStarCellsU = false; bool hasStarCellsV = false; bool hasGroup3CellsInAutoRows = false; - for (int i = PrivateCells.Length - 1; i >= 0; --i) + for (int i = _cellCache.Length - 1; i >= 0; --i) { var child = Children[i] as Control; + if (child == null) { continue; } - CellCache cell = new CellCache(); - // - // read and cache child positioning properties - // + var cell = new CellCache(); // read indices from the corresponding properties // clamp to value < number_of_columns @@ -611,13 +598,13 @@ namespace Avalonia.Controls { if (!cell.IsStarU) { - cell.Next = extData.CellGroup1; - extData.CellGroup1 = i; + cell.Next = CellGroup1; + CellGroup1 = i; } else { - cell.Next = extData.CellGroup3; - extData.CellGroup3 = i; + cell.Next = CellGroup3; + CellGroup3 = i; // remember if this cell belongs to auto row hasGroup3CellsInAutoRows |= cell.IsAutoV; @@ -629,22 +616,24 @@ namespace Avalonia.Controls // note below: if spans through Star column it is NOT Auto && !cell.IsStarU) { - cell.Next = extData.CellGroup2; - extData.CellGroup2 = i; + cell.Next = CellGroup2; + CellGroup2 = i; } else { - cell.Next = extData.CellGroup4; - extData.CellGroup4 = i; + cell.Next = CellGroup4; + CellGroup4 = i; } } - PrivateCells[i] = cell; + _cellCache[i] = cell; } HasStarCellsU = hasStarCellsU; HasStarCellsV = hasStarCellsV; HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows; + + CellsStructureDirty = false; } /// @@ -713,15 +702,15 @@ namespace Avalonia.Controls { if (isRows) { - minSizes[PrivateCells[i].RowIndex] = DefinitionsV[PrivateCells[i].RowIndex].MinSize; + minSizes[_cellCache[i].RowIndex] = DefinitionsV[_cellCache[i].RowIndex].MinSize; } else { - minSizes[PrivateCells[i].ColumnIndex] = DefinitionsU[PrivateCells[i].ColumnIndex].MinSize; + minSizes[_cellCache[i].ColumnIndex] = DefinitionsU[_cellCache[i].ColumnIndex].MinSize; } - i = PrivateCells[i].Next; - } while (i < PrivateCells.Length); + i = _cellCache[i].Next; + } while (i < _cellCache.Length); return minSizes; } @@ -773,7 +762,7 @@ namespace Avalonia.Controls { hasDesiredSizeUChanged = false; - if (cellsHead >= PrivateCells.Length) + if (cellsHead >= _cellCache.Length) { return; } @@ -792,16 +781,16 @@ namespace Avalonia.Controls if (!ignoreDesiredSizeU) { - if (PrivateCells[i].ColumnSpan == 1) + if (_cellCache[i].ColumnSpan == 1) { - DefinitionsU[PrivateCells[i].ColumnIndex].UpdateMinSize(Math.Min(Children[i].DesiredSize.Width, DefinitionsU[PrivateCells[i].ColumnIndex].UserMaxSize)); + DefinitionsU[_cellCache[i].ColumnIndex].UpdateMinSize(Math.Min(Children[i].DesiredSize.Width, DefinitionsU[_cellCache[i].ColumnIndex].UserMaxSize)); } else { RegisterSpan( ref spanStore, - PrivateCells[i].ColumnIndex, - PrivateCells[i].ColumnSpan, + _cellCache[i].ColumnIndex, + _cellCache[i].ColumnSpan, true, Children[i].DesiredSize.Width); } @@ -809,23 +798,23 @@ namespace Avalonia.Controls if (!ignoreDesiredSizeV) { - if (PrivateCells[i].RowSpan == 1) + if (_cellCache[i].RowSpan == 1) { - DefinitionsV[PrivateCells[i].RowIndex].UpdateMinSize(Math.Min(Children[i].DesiredSize.Height, DefinitionsV[PrivateCells[i].RowIndex].UserMaxSize)); + DefinitionsV[_cellCache[i].RowIndex].UpdateMinSize(Math.Min(Children[i].DesiredSize.Height, DefinitionsV[_cellCache[i].RowIndex].UserMaxSize)); } else { RegisterSpan( ref spanStore, - PrivateCells[i].RowIndex, - PrivateCells[i].RowSpan, + _cellCache[i].RowIndex, + _cellCache[i].RowSpan, false, Children[i].DesiredSize.Height); } } - i = PrivateCells[i].Next; - } while (i < PrivateCells.Length); + i = _cellCache[i].Next; + } while (i < _cellCache.Length); if (spanStore != null) { @@ -887,8 +876,8 @@ namespace Avalonia.Controls double cellMeasureWidth; double cellMeasureHeight; - if (PrivateCells[cell].IsAutoU - && !PrivateCells[cell].IsStarU) + if (_cellCache[cell].IsAutoU + && !_cellCache[cell].IsStarU) { // if cell belongs to at least one Auto column and not a single Star column // then it should be calculated "to content", thus it is possible to "shortcut" @@ -900,16 +889,16 @@ namespace Avalonia.Controls // otherwise... cellMeasureWidth = GetMeasureSizeForRange( DefinitionsU, - PrivateCells[cell].ColumnIndex, - PrivateCells[cell].ColumnSpan); + _cellCache[cell].ColumnIndex, + _cellCache[cell].ColumnSpan); } if (forceInfinityV) { cellMeasureHeight = double.PositiveInfinity; } - else if (PrivateCells[cell].IsAutoV - && !PrivateCells[cell].IsStarV) + else if (_cellCache[cell].IsAutoV + && !_cellCache[cell].IsStarV) { // if cell belongs to at least one Auto row and not a single Star row // then it should be calculated "to content", thus it is possible to "shortcut" @@ -920,8 +909,8 @@ namespace Avalonia.Controls { cellMeasureHeight = GetMeasureSizeForRange( DefinitionsV, - PrivateCells[cell].RowIndex, - PrivateCells[cell].RowSpan); + _cellCache[cell].RowIndex, + _cellCache[cell].RowSpan); } var child = Children[cell]; @@ -1006,7 +995,7 @@ namespace Avalonia.Controls Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length); // avoid processing when asked to distribute "0" - if (!_IsZero(requestedSize)) + if (!MathUtilities.IsZero(requestedSize)) { DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting int end = start + count; @@ -1084,7 +1073,7 @@ namespace Avalonia.Controls } // sanity check: requested size must all be distributed - Debug.Assert(_IsZero(sizeToDistribute)); + Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); } else if (requestedSize <= rangeMaxSize) { @@ -1124,7 +1113,7 @@ namespace Avalonia.Controls } // sanity check: requested size must all be distributed - Debug.Assert(_IsZero(sizeToDistribute)); + Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); } else { @@ -1136,7 +1125,7 @@ namespace Avalonia.Controls double equalSize = requestedSize / count; if (equalSize < maxMaxSize - && !_AreClose(equalSize, maxMaxSize)) + && !MathUtilities.AreClose(equalSize, maxMaxSize)) { // equi-size is less than maximum of maxSizes. // in this case distribute so that smaller definitions grow faster than @@ -1903,7 +1892,7 @@ namespace Avalonia.Controls // and precision of floating-point computation. (However, the resulting // display is subject to anti-aliasing problems. TANSTAAFL.) - if (!_AreClose(roundedTakenSize, finalSize)) + if (!MathUtilities.AreClose(roundedTakenSize, finalSize)) { // Compute deltas for (int i = 0; i < definitions.Length; ++i) @@ -1920,7 +1909,7 @@ namespace Avalonia.Controls if (roundedTakenSize > finalSize) { int i = definitions.Length - 1; - while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) + while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0) { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache - dpiIncrement; @@ -1936,7 +1925,7 @@ namespace Avalonia.Controls else if (roundedTakenSize < finalSize) { int i = 0; - while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length) + while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Length) { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache + dpiIncrement; @@ -2054,14 +2043,13 @@ namespace Avalonia.Controls /// private void SetValid() { - ExtendedData extData = ExtData; - if (extData != null) + if (rowColDefsEmpty) { - if (extData.TempDefinitions != null) + if (_tempDefinitions != null) { // TempDefinitions has to be cleared to avoid "memory leaks" - Array.Clear(extData.TempDefinitions, 0, Math.Max(DefinitionsU.Length, DefinitionsV.Length)); - extData.TempDefinitions = null; + Array.Clear(_tempDefinitions, 0, Math.Max(DefinitionsU.Length, DefinitionsV.Length)); + _tempDefinitions = null; } } } @@ -2092,15 +2080,6 @@ namespace Avalonia.Controls return (_gridLinesRenderer); } - /// - /// SetFlags is used to set or unset one or multiple - /// flags on the object. - /// - private void SetFlags(bool value, Flags flags) - { - _flags = value ? (_flags | flags) : (_flags & (~flags)); - } - private double RoundLayoutValue(double value, double dpiScale) { double newValue; @@ -2125,28 +2104,6 @@ namespace Avalonia.Controls return newValue; } - /// - /// CheckFlagsAnd returns true if all the flags in the - /// given bitmask are set on the object. - /// - private bool CheckFlagsAnd(Flags flags) - { - return ((_flags & flags) == flags); - } - - /// - /// CheckFlagsOr returns true if at least one flag in the - /// given bitmask is set. - /// - /// - /// If no bits are set in the given bitmask, the method returns - /// true. - /// - private bool CheckFlagsOr(Flags flags) - { - return (flags == 0 || (_flags & flags) != 0); - } - private static int ValidateColumn(AvaloniaObject o, int value) { @@ -2170,7 +2127,7 @@ namespace Avalonia.Controls private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) { - if (grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway + if (grid.rowColDefsEmpty // trivial grid is 1 by 1. there is no grid lines anyway && grid.ListenToNotifications) { grid.InvalidateVisual(); @@ -2183,7 +2140,7 @@ namespace Avalonia.Controls { var grid = child.GetVisualParent() as Grid; if (grid != null - && grid.ExtData != null + && grid.rowColDefsEmpty && grid.ListenToNotifications) { grid.CellsStructureDirty = true; @@ -2232,30 +2189,29 @@ namespace Avalonia.Controls { get { - ExtendedData extData = ExtData; int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length) * 2; - if (extData.TempDefinitions == null - || extData.TempDefinitions.Length < requiredLength) + if (_tempDefinitions == null + || _tempDefinitions.Length < requiredLength) { WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(s_tempDefinitionsDataSlot); if (tempDefinitionsWeakRef == null) { - extData.TempDefinitions = new DefinitionBase[requiredLength]; - Thread.SetData(s_tempDefinitionsDataSlot, new WeakReference(extData.TempDefinitions)); + _tempDefinitions = new DefinitionBase[requiredLength]; + Thread.SetData(s_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions)); } else { - extData.TempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; - if (extData.TempDefinitions == null - || extData.TempDefinitions.Length < requiredLength) + _tempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; + if (_tempDefinitions == null + || _tempDefinitions.Length < requiredLength) { - extData.TempDefinitions = new DefinitionBase[requiredLength]; - tempDefinitionsWeakRef.Target = extData.TempDefinitions; + _tempDefinitions = new DefinitionBase[requiredLength]; + tempDefinitionsWeakRef.Target = _tempDefinitions; } } } - return (extData.TempDefinitions); + return (_tempDefinitions); } } @@ -2298,106 +2254,6 @@ namespace Avalonia.Controls } } - /// - /// Private version returning array of cells. - /// - private CellCache[] PrivateCells - { - get { return (ExtData.CellCachesCollection); } - } - - /// - /// Convenience accessor to ValidCellsStructure bit flag. - /// - private bool CellsStructureDirty - { - get { return (!CheckFlagsAnd(Flags.ValidCellsStructure)); } - set { SetFlags(!value, Flags.ValidCellsStructure); } - } - - /// - /// Convenience accessor to ListenToNotifications bit flag. - /// - private bool ListenToNotifications - { - get { return (CheckFlagsAnd(Flags.ListenToNotifications)); } - set { SetFlags(value, Flags.ListenToNotifications); } - } - - /// - /// Convenience accessor to SizeToContentU bit flag. - /// - private bool SizeToContentU - { - get { return (CheckFlagsAnd(Flags.SizeToContentU)); } - set { SetFlags(value, Flags.SizeToContentU); } - } - - /// - /// Convenience accessor to SizeToContentV bit flag. - /// - private bool SizeToContentV - { - get { return (CheckFlagsAnd(Flags.SizeToContentV)); } - set { SetFlags(value, Flags.SizeToContentV); } - } - - /// - /// Convenience accessor to HasStarCellsU bit flag. - /// - private bool HasStarCellsU - { - get { return (CheckFlagsAnd(Flags.HasStarCellsU)); } - set { SetFlags(value, Flags.HasStarCellsU); } - } - - /// - /// Convenience accessor to HasStarCellsV bit flag. - /// - private bool HasStarCellsV - { - get { return (CheckFlagsAnd(Flags.HasStarCellsV)); } - set { SetFlags(value, Flags.HasStarCellsV); } - } - - /// - /// Convenience accessor to HasGroup3CellsInAutoRows bit flag. - /// - private bool HasGroup3CellsInAutoRows - { - get { return (CheckFlagsAnd(Flags.HasGroup3CellsInAutoRows)); } - set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } - } - - /// - /// fp version of d == 0. - /// - /// Value to check. - /// true if d == 0. - private static bool _IsZero(double d) - { - return (Math.Abs(d) < double.Epsilon); - } - - /// - /// fp version of d1 == d2 - /// - /// First value to compare - /// Second value to compare - /// true if d1 == d2 - private static bool _AreClose(double d1, double d2) - { - return (Math.Abs(d1 - d2) < double.Epsilon); - } - - /// - /// Returns reference to extended data bag. - /// - private ExtendedData ExtData - { - get { return (_data); } - } - /// /// Returns *-weight, adjusted for scale computed during Phase 1 /// @@ -2416,72 +2272,6 @@ namespace Avalonia.Controls } } - private ExtendedData _data = new ExtendedData(); // extended data instantiated on demand, for non-trivial case handling only - private Flags _flags; // grid validity / property caches dirtiness flags - private GridLinesRenderer _gridLinesRenderer; - - // Keeps track of definition indices. - int[] _definitionIndices; - - // Stores unrounded values and rounding errors during layout rounding. - double[] _roundingErrors; - private DefinitionBase[] DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - private DefinitionBase[] DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; - private const double c_epsilon = 1e-5; // used in fp calculations - private const double c_starClip = 1e298; // used as maximum for clipping star values during normalization - private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop - private static readonly LocalDataStoreSlot s_tempDefinitionsDataSlot = Thread.AllocateDataSlot(); - private static readonly IComparer s_spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); - private static readonly IComparer s_spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); - private static readonly IComparer s_starDistributionOrderComparer = new StarDistributionOrderComparer(); - private static readonly IComparer s_distributionOrderComparer = new DistributionOrderComparer(); - private static readonly IComparer s_minRatioComparer = new MinRatioComparer(); - private static readonly IComparer s_maxRatioComparer = new MaxRatioComparer(); - private static readonly IComparer s_starWeightComparer = new StarWeightComparer(); - - /// - /// Extended data instantiated on demand, when grid handles non-trivial case. - /// - private class ExtendedData - { - internal CellCache[] CellCachesCollection; // backing store for logical Children - internal int CellGroup1; // index of the first cell in first cell group - internal int CellGroup2; // index of the first cell in second cell group - internal int CellGroup3; // index of the first cell in third cell group - internal int CellGroup4; // index of the first cell in forth cell group - internal DefinitionBase[] TempDefinitions; // temporary array used during layout for various purposes - // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length) - } - - /// - /// Grid validity / property caches dirtiness flags - /// - [System.Flags] - private enum Flags - { - // - // the foolowing flags let grid tracking dirtiness in more granular manner: - // * Valid???Structure flags indicate that elements were added or removed. - // * Valid???Layout flags indicate that layout time portion of the information - // stored on the objects should be updated. - // - ValidColumnDefinitionsStructure = 0x00000001, - ValidRowDefinitionsStructure = 0x00000002, - ValidCellsStructure = 0x00000004, - - // - // boolean flags - // - ListenToNotifications = 0x00001000, // "0" when all notifications are ignored - SizeToContentU = 0x00002000, // "1" if calculating to content in U direction - SizeToContentV = 0x00004000, // "1" if calculating to content in V direction - HasStarCellsU = 0x00008000, // "1" if at least one cell belongs to a Star column - HasStarCellsV = 0x00010000, // "1" if at least one cell belongs to a Star row - HasGroup3CellsInAutoRows = 0x00020000, // "1" if at least one cell of group 3 belongs to an Auto row - MeasureOverrideInProgress = 0x00040000, // "1" while in the context of Grid.MeasureOverride - ArrangeOverrideInProgress = 0x00080000, // "1" while in the context of Grid.ArrangeOverride - } - /// /// LayoutTimeSizeType is used internally and reflects layout-time size type. /// @@ -2663,135 +2453,6 @@ namespace Avalonia.Controls } } - /// - /// StarDistributionOrderComparer. - /// - private class StarDistributionOrderComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - - return result; - } - } - - /// - /// DistributionOrderComparer. - /// - private class DistributionOrderComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - double xprime = definitionX.SizeCache - definitionX.MinSizeForArrange; - double yprime = definitionY.SizeCache - definitionY.MinSizeForArrange; - result = xprime.CompareTo(yprime); - } - - return result; - } - } - - - /// - /// StarDistributionOrderIndexComparer. - /// - private class StarDistributionOrderIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal StarDistributionOrderIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - - return result; - } - } - - /// - /// DistributionOrderComparer. - /// - private class DistributionOrderIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal DistributionOrderIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - double xprime = definitionX.SizeCache - definitionX.MinSizeForArrange; - double yprime = definitionY.SizeCache - definitionY.MinSizeForArrange; - result = xprime.CompareTo(yprime); - } - - return result; - } - } - /// /// RoundingErrorIndexComparer. /// From 689ca63c7ac90c81d3eecbdcec6816f0c8bc7a6a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 10:51:07 +0800 Subject: [PATCH 030/130] Fix DPI fallback value on LayoutRounding; Rename `rowColDefsEmpty` to `IsTrivialGrid`; --- src/Avalonia.Controls/GridWPF.cs | 66 +++++++++++++++++++------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 0549c7f20a..119c94c6e0 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -33,6 +33,11 @@ namespace Avalonia.Controls internal bool HasStarCellsU; internal bool HasStarCellsV; internal bool HasGroup3CellsInAutoRows; + internal bool MeasureOverrideInProgress; + internal bool ArrangeOverrideInProgress; + internal bool ColumnDefinitionsDirty; + internal bool RowDefinitionsDirty; + // index of the first cell in first cell group internal int CellGroup1; @@ -44,7 +49,6 @@ namespace Avalonia.Controls internal int CellGroup3; // index of the first cell in fourth cell group - internal int CellGroup4; // temporary array used during layout for various purposes @@ -60,9 +64,9 @@ namespace Avalonia.Controls // Stores unrounded values and rounding errors during layout rounding. private double[] _roundingErrors; - private DefinitionBase[] DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - private DefinitionBase[] DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; - private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop + private DefinitionBase[] DefinitionsU; + private DefinitionBase[] DefinitionsV; + private const int layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop private static readonly LocalDataStoreSlot s_tempDefinitionsDataSlot = Thread.AllocateDataSlot(); private static readonly IComparer s_spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); private static readonly IComparer s_spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); @@ -73,6 +77,7 @@ namespace Avalonia.Controls static Grid() { ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); + ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); RowProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); @@ -167,7 +172,6 @@ namespace Avalonia.Controls return element.GetValue(RowSpanProperty); } - /// /// Gets the value of the IsSharedSizeScope attached property for a control. /// @@ -243,6 +247,8 @@ namespace Avalonia.Controls if (_columnDefinitions.Count > 0) DefinitionsU = _columnDefinitions.Cast().ToArray(); + else + DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; _columnDefinitions.CollectionChanged += (_, e) => { @@ -283,6 +289,8 @@ namespace Avalonia.Controls if (_rowDefinitions.Count > 0) DefinitionsV = _rowDefinitions.Cast().ToArray(); + else + DefinitionsV = new DefinitionBase[1] { new ColumnDefinition() }; _rowDefinitions.CollectionChanged += (_, e) => { @@ -300,8 +308,8 @@ namespace Avalonia.Controls } } - private bool rowColDefsEmpty => (DefinitionsU.Length == 0) && - (DefinitionsV.Length == 0); + private bool IsTrivialGrid => (DefinitionsU?.Length <= 1) && + (DefinitionsV?.Length <= 1); /// /// Content measurement. @@ -317,7 +325,7 @@ namespace Avalonia.Controls ListenToNotifications = true; MeasureOverrideInProgress = true; - if (rowColDefsEmpty) + if (IsTrivialGrid) { gridDesiredSize = new Size(); @@ -358,10 +366,10 @@ namespace Avalonia.Controls } } - // ValidateColumnDefinitionsStructure(); + ValidateColumnDefinitionsStructure(); ValidateDefinitionsLayout(DefinitionsU, sizeToContentU); - // ValidateRowDefinitionsStructure(); + ValidateRowDefinitionsStructure(); ValidateDefinitionsLayout(DefinitionsV, sizeToContentV); CellsStructureDirty |= (SizeToContentU != sizeToContentU) || (SizeToContentV != sizeToContentV); @@ -429,9 +437,9 @@ namespace Avalonia.Controls ApplyCachedMinSizes(group2MinSizes, false); if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } - MeasureCellsGroup(CellGroup2, constraint, cnt == c_layoutLoopMaxCount, false, out hasDesiredSizeUChanged); + MeasureCellsGroup(CellGroup2, constraint, cnt == layoutLoopMaxCount, false, out hasDesiredSizeUChanged); } - while (hasDesiredSizeUChanged && ++cnt <= c_layoutLoopMaxCount); + while (hasDesiredSizeUChanged && ++cnt <= layoutLoopMaxCount); } } } @@ -451,6 +459,18 @@ namespace Avalonia.Controls return (gridDesiredSize); } + private void ValidateColumnDefinitionsStructure() + { + if (DefinitionsU == null || DefinitionsU?.Count() == 0) + DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + } + + private void ValidateRowDefinitionsStructure() + { + if (DefinitionsV == null || DefinitionsV?.Count() == 0) + DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; + } + /// /// Content arrangement. /// @@ -459,10 +479,9 @@ namespace Avalonia.Controls { try { - ArrangeOverrideInProgress = true; - if (rowColDefsEmpty) + if (IsTrivialGrid) { for (int i = 0, count = Children.Count; i < count; ++i) { @@ -527,11 +546,6 @@ namespace Avalonia.Controls InvalidateMeasure(); } - internal bool MeasureOverrideInProgress; - internal bool ArrangeOverrideInProgress; - internal bool ColumnDefinitionsDirty; - internal bool RowDefinitionsDirty; - /// /// Lays out cells according to rows and columns, and creates lookup grids. /// @@ -1638,9 +1652,9 @@ namespace Avalonia.Controls double remainingAvailableSize = finalSize - takenSize; double remainingStarWeight = totalStarWeight - takenStarWeight; - MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer((DefinitionBase[])definitions); + MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions); Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer); - MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer((DefinitionBase[])definitions); + MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions); Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer); while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) @@ -1835,7 +1849,7 @@ namespace Avalonia.Controls // unrounded sizes, to avoid breaking assumptions in the previous phases if (UseLayoutRounding) { - var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 96; + var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; double[] roundingErrors = RoundingErrors; double roundedTakenSize = 0.0; @@ -2043,7 +2057,7 @@ namespace Avalonia.Controls /// private void SetValid() { - if (rowColDefsEmpty) + if (IsTrivialGrid) { if (_tempDefinitions != null) { @@ -2127,7 +2141,7 @@ namespace Avalonia.Controls private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) { - if (grid.rowColDefsEmpty // trivial grid is 1 by 1. there is no grid lines anyway + if (!grid.IsTrivialGrid // trivial grid is 1 by 1. there is no grid lines anyway && grid.ListenToNotifications) { grid.InvalidateVisual(); @@ -2140,7 +2154,7 @@ namespace Avalonia.Controls { var grid = child.GetVisualParent() as Grid; if (grid != null - && grid.rowColDefsEmpty + && !grid.IsTrivialGrid && grid.ListenToNotifications) { grid.CellsStructureDirty = true; @@ -2153,7 +2167,7 @@ namespace Avalonia.Controls /// Helper for Comparer methods. /// /// - /// true iff one or both of x and y are null, in which case result holds + /// true if one or both of x and y are null, in which case result holds /// the relative sort order. /// private static bool CompareNullRefs(object x, object y, out int result) From 505ed6b0cf9cacb107ab6baedae45c2d2692b24b Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 11:15:04 +0800 Subject: [PATCH 031/130] Replace per-property class handlers with `AffectsParentMeasure<>`; Fix `GridLinesRenderer`. --- src/Avalonia.Controls/GridWPF.cs | 39 ++++++++------------------------ 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 119c94c6e0..f1cf1e8f5b 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -38,7 +38,6 @@ namespace Avalonia.Controls internal bool ColumnDefinitionsDirty; internal bool RowDefinitionsDirty; - // index of the first cell in first cell group internal int CellGroup1; @@ -77,11 +76,7 @@ namespace Avalonia.Controls static Grid() { ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); - - ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); - ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); - RowProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); - RowSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); } /// @@ -521,12 +516,12 @@ namespace Avalonia.Controls cell.Arrange(cellRect); } - // // update render bound on grid lines renderer visual - // var gridLinesRenderer = EnsureGridLinesRenderer(); - // if (gridLinesRenderer != null) - // { - // gridLinesRenderer.UpdateRenderBounds(arrangeSize); - // } + // update render bound on grid lines renderer visual + var gridLinesRenderer = EnsureGridLinesRenderer(); + if (gridLinesRenderer != null) + { + gridLinesRenderer.UpdateRenderBounds(arrangeSize); + } } } finally @@ -2144,22 +2139,7 @@ namespace Avalonia.Controls if (!grid.IsTrivialGrid // trivial grid is 1 by 1. there is no grid lines anyway && grid.ListenToNotifications) { - grid.InvalidateVisual(); - } - } - - private static void OnCellAttachedPropertyChanged(Visual child, AvaloniaPropertyChangedEventArgs e) - { - if (child != null) - { - var grid = child.GetVisualParent() as Grid; - if (grid != null - && !grid.IsTrivialGrid - && grid.ListenToNotifications) - { - grid.CellsStructureDirty = true; - grid.InvalidateMeasure(); - } + grid.Invalidate(); } } @@ -2692,7 +2672,7 @@ namespace Avalonia.Controls /// /// Helper to render grid lines. /// - internal class GridLinesRenderer : Visual + internal class GridLinesRenderer : Control { /// /// Static initialization @@ -2769,6 +2749,7 @@ namespace Avalonia.Controls lastArrangeSize = arrangeSize; this.InvalidateVisual(); } + private static Size lastArrangeSize; private const double c_dashLength = 4.0; // private const double c_penWidth = 1.0; // From 4c1f47695773da777b984cf69032c7904a802368 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 11:35:58 +0800 Subject: [PATCH 032/130] Rename private fields, remove practically unused fields. --- src/Avalonia.Controls/GridWPF.cs | 55 +++++++++++++------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index f1cf1e8f5b..334d57ecf8 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -27,14 +27,11 @@ namespace Avalonia.Controls public class Grid : Panel { internal bool CellsStructureDirty = true; - internal bool ListenToNotifications; internal bool SizeToContentU; internal bool SizeToContentV; internal bool HasStarCellsU; internal bool HasStarCellsV; internal bool HasGroup3CellsInAutoRows; - internal bool MeasureOverrideInProgress; - internal bool ArrangeOverrideInProgress; internal bool ColumnDefinitionsDirty; internal bool RowDefinitionsDirty; @@ -65,13 +62,15 @@ namespace Avalonia.Controls private double[] _roundingErrors; private DefinitionBase[] DefinitionsU; private DefinitionBase[] DefinitionsV; - private const int layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop - private static readonly LocalDataStoreSlot s_tempDefinitionsDataSlot = Thread.AllocateDataSlot(); - private static readonly IComparer s_spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); - private static readonly IComparer s_spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); - private static readonly IComparer s_minRatioComparer = new MinRatioComparer(); - private static readonly IComparer s_maxRatioComparer = new MaxRatioComparer(); - private static readonly IComparer s_starWeightComparer = new StarWeightComparer(); + + // 5 is an arbitrary constant chosen to end the measure loop + private const int _layoutLoopMaxCount = 5; + private static readonly LocalDataStoreSlot _tempDefinitionsDataSlot = Thread.AllocateDataSlot(); + private static readonly IComparer _spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); + private static readonly IComparer _spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); + private static readonly IComparer _minRatioComparer = new MinRatioComparer(); + private static readonly IComparer _maxRatioComparer = new MaxRatioComparer(); + private static readonly IComparer _starWeightComparer = new StarWeightComparer(); static Grid() { @@ -317,9 +316,6 @@ namespace Avalonia.Controls try { - ListenToNotifications = true; - MeasureOverrideInProgress = true; - if (IsTrivialGrid) { gridDesiredSize = new Size(); @@ -432,9 +428,9 @@ namespace Avalonia.Controls ApplyCachedMinSizes(group2MinSizes, false); if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } - MeasureCellsGroup(CellGroup2, constraint, cnt == layoutLoopMaxCount, false, out hasDesiredSizeUChanged); + MeasureCellsGroup(CellGroup2, constraint, cnt == _layoutLoopMaxCount, false, out hasDesiredSizeUChanged); } - while (hasDesiredSizeUChanged && ++cnt <= layoutLoopMaxCount); + while (hasDesiredSizeUChanged && ++cnt <= _layoutLoopMaxCount); } } } @@ -448,7 +444,6 @@ namespace Avalonia.Controls } finally { - MeasureOverrideInProgress = false; } return (gridDesiredSize); @@ -474,8 +469,6 @@ namespace Avalonia.Controls { try { - ArrangeOverrideInProgress = true; - if (IsTrivialGrid) { for (int i = 0, count = Children.Count; i < count; ++i) @@ -527,7 +520,6 @@ namespace Avalonia.Controls finally { SetValid(); - ArrangeOverrideInProgress = false; } return (arrangeSize); } @@ -1061,7 +1053,7 @@ namespace Avalonia.Controls double sizeToDistribute; int i; - Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer); + Array.Sort(tempDefinitions, 0, count, _spanPreferredDistributionOrderComparer); for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) { // sanity check: only auto definitions allowed in this loop @@ -1098,7 +1090,7 @@ namespace Avalonia.Controls double sizeToDistribute; int i; - Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer); + Array.Sort(tempDefinitions, 0, count, _spanMaxDistributionOrderComparer); for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) { // sanity check: no auto definitions allowed in this loop @@ -1301,8 +1293,8 @@ namespace Avalonia.Controls double takenStarWeight = 0.0; double remainingAvailableSize = availableSize - takenSize; double remainingStarWeight = totalStarWeight - takenStarWeight; - Array.Sort(tempDefinitions, 0, minCount, s_minRatioComparer); - Array.Sort(tempDefinitions, defCount, maxCount, s_maxRatioComparer); + Array.Sort(tempDefinitions, 0, minCount, _minRatioComparer); + Array.Sort(tempDefinitions, defCount, maxCount, _maxRatioComparer); while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) { @@ -1458,7 +1450,7 @@ namespace Avalonia.Controls if (starCount > 0) { - Array.Sort(tempDefinitions, 0, starCount, s_starWeightComparer); + Array.Sort(tempDefinitions, 0, starCount, _starWeightComparer); // compute the partial sums of *-weight, in increasing order of weight // for minimal loss of precision. @@ -2136,8 +2128,7 @@ namespace Avalonia.Controls private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) { - if (!grid.IsTrivialGrid // trivial grid is 1 by 1. there is no grid lines anyway - && grid.ListenToNotifications) + if (!grid.IsTrivialGrid) // trivial grid is 1 by 1. there is no grid lines anyway { grid.Invalidate(); } @@ -2188,11 +2179,11 @@ namespace Avalonia.Controls if (_tempDefinitions == null || _tempDefinitions.Length < requiredLength) { - WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(s_tempDefinitionsDataSlot); + WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(_tempDefinitionsDataSlot); if (tempDefinitionsWeakRef == null) { _tempDefinitions = new DefinitionBase[requiredLength]; - Thread.SetData(s_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions)); + Thread.SetData(_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions)); } else { @@ -2672,7 +2663,7 @@ namespace Avalonia.Controls /// /// Helper to render grid lines. /// - internal class GridLinesRenderer : Control + private class GridLinesRenderer : Control { /// /// Static initialization @@ -2703,10 +2694,11 @@ namespace Avalonia.Controls /// public override void Render(DrawingContext drawingContext) { - var grid = this.GetVisualParent() as Grid; + var grid = this.GetVisualParent(); if (grid == null - || grid.ShowGridLines == false) + || !grid.ShowGridLines + || grid.IsTrivialGrid) { return; } @@ -2755,7 +2747,6 @@ namespace Avalonia.Controls private const double c_penWidth = 1.0; // private static readonly Pen s_oddDashPen; // first pen to draw dash private static readonly Pen s_evenDashPen; // second pen to draw dash - private static readonly Point c_zeroPoint = new Point(0, 0); } } } \ No newline at end of file From 69c056c728ffa6a504aeedc25c57cbdc7b532f92 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 12:08:14 +0800 Subject: [PATCH 033/130] Downsize DefinitionBase, removing most of shared size scope code. --- src/Avalonia.Controls/ColumnDefinition.cs | 6 +- src/Avalonia.Controls/DefinitionBase.cs | 517 +--------------------- src/Avalonia.Controls/GridWPF.cs | 50 +-- src/Avalonia.Controls/RowDefinition.cs | 6 +- 4 files changed, 49 insertions(+), 530 deletions(-) diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs index 015484dbcc..56a6d41b7b 100644 --- a/src/Avalonia.Controls/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -88,10 +88,10 @@ namespace Avalonia.Controls set { SetValue(WidthProperty, value); } } - internal override GridLength UserSizeValueCache => this.Width; + internal override GridLength UserSize => this.Width; - internal override double UserMinSizeValueCache => this.MinWidth; + internal override double UserMinSize => this.MinWidth; - internal override double UserMaxSizeValueCache => this.MaxWidth; + internal override double UserMaxSize => this.MaxWidth; } } diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 643d9da751..38fc375631 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -9,27 +9,16 @@ using Avalonia.Utilities; namespace Avalonia.Controls { - /// /// Base class for and . /// - public abstract class DefinitionBase : ContentControl + public abstract class DefinitionBase : AvaloniaObject { - /// - /// Static ctor. Used for static registration of properties. - /// - static DefinitionBase() - { - SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - // BoundsProperty.Changed.AddClassHandler(OnUserSizePropertyChanged); - } - - /// /// Defines the property. /// public static readonly StyledProperty SharedSizeGroupProperty = - AvaloniaProperty.Register(nameof(SharedSizeGroup), inherits: true, validate: SharedSizeGroupPropertyValueValid); + AvaloniaProperty.Register(nameof(SharedSizeGroup), inherits: true); /// /// Gets or sets the name of the shared size group of the column or row. @@ -41,84 +30,30 @@ namespace Avalonia.Controls } /// - /// Performs action preparing definition to enter layout calculation mode. - /// - internal void OnBeforeLayout(Grid grid) - { - // reset layout state. - _minSize = 0; - LayoutWasUpdated = true; - - // defer verification for shared definitions - if (_sharedState != null) { _sharedState.EnsureDeferredValidation(grid); } - } - - - /// - /// Returns true if this definition is a part of shared group. - /// - internal bool IsShared - { - get { return (_sharedState != null); } - } - - /// - /// Internal accessor to user size field. + /// Internal helper to access up-to-date UserSize property value. /// - internal GridLength UserSize - { - get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); } - } + internal abstract GridLength UserSize { get; } /// - /// Internal accessor to user min size field. - /// - internal double UserMinSize - { - get { return (UserMinSizeValueCache); } - } - - /// - /// Internal accessor to user max size field. + /// Internal helper to access up-to-date UserMinSize property value. /// - internal double UserMaxSize - { - get { return (UserMaxSizeValueCache); } - } + internal abstract double UserMinSize { get; } /// - /// DefinitionBase's index in the parents collection. + /// Internal helper to access up-to-date UserMaxSize property value. /// - internal int Index - { - get - { - return (_parentIndex); - } - set - { - Debug.Assert(value >= -1 && _parentIndex != value); - _parentIndex = value; - } - } + internal abstract double UserMaxSize { get; } + + private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's /// /// Layout-time user size type. /// - internal Grid.LayoutTimeSizeType SizeType - { - get { return (_sizeType); } - set { _sizeType = value; } - } - + internal Grid.LayoutTimeSizeType SizeType {get; set;} /// /// Returns or sets measure size for the definition. /// - internal double MeasureSize - { - get { return (_measureSize); } - set { _measureSize = value; } - } + internal double MeasureSize { get; set; } /// /// Returns definition's layout time type sensitive preferred size. @@ -131,10 +66,10 @@ namespace Avalonia.Controls get { double preferredSize = MinSize; - if (_sizeType != Grid.LayoutTimeSizeType.Auto - && preferredSize < _measureSize) + if (SizeType != Grid.LayoutTimeSizeType.Auto + && preferredSize < MeasureSize) { - preferredSize = _measureSize; + preferredSize = MeasureSize; } return (preferredSize); } @@ -143,11 +78,7 @@ namespace Avalonia.Controls /// /// Returns or sets size cache for the definition. /// - internal double SizeCache - { - get { return (_sizeCache); } - set { _sizeCache = value; } - } + internal double SizeCache { get; set; } /// /// Returns min size. @@ -157,12 +88,6 @@ namespace Avalonia.Controls get { double minSize = _minSize; - if (UseSharedMinimum - && _sharedState != null - && minSize < _sharedState.MinSize) - { - minSize = _sharedState.MinSize; - } return (minSize); } @@ -194,12 +119,6 @@ namespace Avalonia.Controls get { double minSize = _minSize; - if (_sharedState != null - && (UseSharedMinimum || !LayoutWasUpdated) - && minSize < _sharedState.MinSize) - { - minSize = _sharedState.MinSize; - } return (minSize); } } @@ -207,409 +126,11 @@ namespace Avalonia.Controls /// /// Offset. /// - internal double FinalOffset - { - get { return _offset; } - set { _offset = value; } - } - - /// - /// Internal helper to access up-to-date UserSize property value. - /// - internal abstract GridLength UserSizeValueCache { get; } - - /// - /// Internal helper to access up-to-date UserMinSize property value. - /// - internal abstract double UserMinSizeValueCache { get; } - - /// - /// Internal helper to access up-to-date UserMaxSize property value. - /// - internal abstract double UserMaxSizeValueCache { get; } - - - /// - /// Protected. Returns true if this DefinitionBase instance is in parent's logical tree. - /// - internal bool InParentLogicalTree - { - get { return (_parentIndex != -1); } - } - - private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) - { - if (definition.InParentLogicalTree) - { - string sharedSizeGroupId = (string)e.NewValue; - - if (definition._sharedState != null) - { - // if definition is already registered AND shared size group id is changing, - // then un-register the definition from the current shared size state object. - definition._sharedState.RemoveMember(definition); - definition._sharedState = null; - } - - if ((definition._sharedState == null) && (sharedSizeGroupId != null)) - { - // SharedSizeScope privateSharedSizeScope = definition.PrivateSharedSizeScope; - // if (privateSharedSizeScope != null) - // { - // // if definition is not registered and both: shared size group id AND private shared scope - // // are available, then register definition. - // definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - // definition._sharedState.AddMember(definition); - // } - } - } - } - - /// - /// Verifies that Shared Size Group Property string - /// a) not empty. - /// b) contains only letters, digits and underscore ('_'). - /// c) does not start with a digit. - /// - private static string SharedSizeGroupPropertyValueValid(DefinitionBase _, string value) - { - // null is default value - if (value == null) - { - return value; - } - - string id = (string)value; - - if (id != string.Empty) - { - int i = -1; - while (++i < id.Length) - { - bool isDigit = Char.IsDigit(id[i]); - - if ((i == 0 && isDigit) - || !(isDigit - || Char.IsLetter(id[i]) - || '_' == id[i])) - { - break; - } - } - - if (i == id.Length) - { - return value; - } - } - - return null; - } - - /// - /// - /// - /// - /// OnPrivateSharedSizeScopePropertyChanged is called when new scope enters or - /// existing scope just left. In both cases if the DefinitionBase object is already registered - /// in SharedSizeState, it should un-register and register itself in a new one. - /// - private static void OnPrivateSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) - { - DefinitionBase definition = (DefinitionBase)d; - - if (definition.InParentLogicalTree) - { - SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue; - - if (definition._sharedState != null) - { - // if definition is already registered And shared size scope is changing, - // then un-register the definition from the current shared size state object. - definition._sharedState.RemoveMember(definition); - definition._sharedState = null; - } - - if ((definition._sharedState == null) && (privateSharedSizeScope != null)) - { - string sharedSizeGroup = definition.SharedSizeGroup; - if (sharedSizeGroup != null) - { - // if definition is not registered and both: shared size group id AND private shared scope - // are available, then register definition. - definition._sharedState = privateSharedSizeScope.EnsureSharedState(definition.SharedSizeGroup); - definition._sharedState.AddMember(definition); - } - } - } - } - - /// - /// Convenience accessor to UseSharedMinimum flag - /// - private bool UseSharedMinimum - { - get { return _useSharedMinimum; } - set { _useSharedMinimum = value; } - } + internal double FinalOffset { get; set; } /// - /// Convenience accessor to LayoutWasUpdated flag - /// - private bool LayoutWasUpdated - { - get { return _layoutWasUpdated; } - set { _layoutWasUpdated = value; } - } - - private int _parentIndex = -1; // this instance's index in parent's children collection - - private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" - - private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's - private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure - private double _sizeCache; // cache used for various purposes (sorting, caching, etc) during calculations - private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) - - private SharedSizeState _sharedState; // reference to shared state object this instance is registered with - private bool _layoutWasUpdated; - private bool _useSharedMinimum; - - - - /// - /// Collection of shared states objects for a single scope - /// - private class SharedSizeScope - { - /// - /// Returns SharedSizeState object for a given group. - /// Creates a new StatedState object if necessary. - /// - internal SharedSizeState EnsureSharedState(string sharedSizeGroup) - { - // check that sharedSizeGroup is not default - Debug.Assert(sharedSizeGroup != null); - - SharedSizeState sharedState = _registry[sharedSizeGroup] as SharedSizeState; - if (sharedState == null) - { - sharedState = new SharedSizeState(this, sharedSizeGroup); - _registry[sharedSizeGroup] = sharedState; - } - return (sharedState); - } - - /// - /// Removes an entry in the registry by the given key. - /// - internal void Remove(object key) - { - Debug.Assert(_registry.Contains(key)); - _registry.Remove(key); - } - - private Hashtable _registry = new Hashtable(); // storage for shared state objects - } - - /// - /// Implementation of per shared group state object + /// Returns true if this definition is a part of shared group. /// - private class SharedSizeState - { - /// - /// Default ctor. - /// - internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) - { - Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null); - _sharedSizeScope = sharedSizeScope; - _sharedSizeGroupId = sharedSizeGroupId; - _registry = new List(); - // _layoutUpdated = new EventHandler(OnLayoutUpdated); - _broadcastInvalidation = true; - } - - /// - /// Adds / registers a definition instance. - /// - internal void AddMember(DefinitionBase member) - { - Debug.Assert(!_registry.Contains(member)); - _registry.Add(member); - Invalidate(); - } - - /// - /// Removes / un-registers a definition instance. - /// - /// - /// If the collection of registered definitions becomes empty - /// instantiates self removal from owner's collection. - /// - internal void RemoveMember(DefinitionBase member) - { - Invalidate(); - _registry.Remove(member); - - if (_registry.Count == 0) - { - _sharedSizeScope.Remove(_sharedSizeGroupId); - } - } - - /// - /// Propogates invalidations for all registered definitions. - /// Resets its own state. - /// - internal void Invalidate() - { - _userSizeValid = false; - - if (_broadcastInvalidation) - { - for (int i = 0, count = _registry.Count; i < count; ++i) - { - Grid parentGrid = (Grid)(_registry[i].Parent); - parentGrid.Invalidate(); - } - _broadcastInvalidation = false; - } - } - - /// - /// Makes sure that one and only one layout updated handler is registered for this shared state. - /// - internal void EnsureDeferredValidation(IControl layoutUpdatedHost) - { - if (_layoutUpdatedHost == null) - { - _layoutUpdatedHost = layoutUpdatedHost; - // PORTING HACK... Remove this when resolved. - _layoutUpdatedHost.GetSubject(Visual.BoundsProperty).Subscribe(p => _layoutUpdated?.Invoke(this, null)); - } - } - - /// - /// DefinitionBase's specific code. - /// - internal double MinSize - { - get - { - if (!_userSizeValid) { EnsureUserSizeValid(); } - return (_minSize); - } - } - - /// - /// DefinitionBase's specific code. - /// - internal GridLength UserSize - { - get - { - if (!_userSizeValid) { EnsureUserSizeValid(); } - return (_userSize); - } - } - - private void EnsureUserSizeValid() - { - _userSize = new GridLength(1, GridUnitType.Auto); - - for (int i = 0, count = _registry.Count; i < count; ++i) - { - Debug.Assert(_userSize.GridUnitType == GridUnitType.Auto - || _userSize.GridUnitType == GridUnitType.Pixel); - - GridLength currentGridLength = _registry[i].UserSizeValueCache; - if (currentGridLength.GridUnitType == GridUnitType.Pixel) - { - if (_userSize.GridUnitType == GridUnitType.Auto) - { - _userSize = currentGridLength; - } - else if (_userSize.Value < currentGridLength.Value) - { - _userSize = currentGridLength; - } - } - } - // taking maximum with user size effectively prevents squishy-ness. - // this is a "solution" to avoid shared definitions from been sized to - // different final size at arrange time, if / when different grids receive - // different final sizes. - _minSize = _userSize.IsAbsolute ? _userSize.Value : 0.0; - - _userSizeValid = true; - } - - /// - /// OnLayoutUpdated handler. Validates that all participating definitions - /// have updated min size value. Forces another layout update cycle if needed. - /// - private void OnLayoutUpdated(object sender, EventArgs e) - { - double sharedMinSize = 0; - - // accumulate min size of all participating definitions - for (int i = 0, count = _registry.Count; i < count; ++i) - { - sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); - } - - bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); - - // compare accumulated min size with min sizes of the individual definitions - for (int i = 0, count = _registry.Count; i < count; ++i) - { - DefinitionBase definitionBase = _registry[i]; - - if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) - { - // if definition's min size is different, then need to re-measure - if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) - { - Grid parentGrid = (Grid)definitionBase.Parent; - parentGrid.InvalidateMeasure(); - definitionBase.UseSharedMinimum = true; - } - else - { - definitionBase.UseSharedMinimum = false; - - // if measure is valid then also need to check arrange. - // Note: definitionBase.SizeCache is volatile but at this point - // it contains up-to-date final size - if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) - { - Grid parentGrid = (Grid)definitionBase.Parent; - parentGrid.InvalidateArrange(); - } - } - - definitionBase.LayoutWasUpdated = false; - } - } - - _minSize = sharedMinSize; - - // _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; - _layoutUpdatedHost = null; - - _broadcastInvalidation = true; - } - - private readonly SharedSizeScope _sharedSizeScope; // the scope this state belongs to - private readonly string _sharedSizeGroupId; // Id of the shared size group this object is servicing - private readonly List _registry; // registry of participating definitions - private readonly EventHandler _layoutUpdated; // instance event handler for layout updated event - private IControl _layoutUpdatedHost; // IControl for which layout updated event handler is registered - private bool _broadcastInvalidation; // "true" when broadcasting of invalidation is needed - private bool _userSizeValid; // "true" when _userSize is up to date - private GridLength _userSize; // shared state - private double _minSize; // shared state - } + internal bool IsShared { get; set; } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 334d57ecf8..e18d70c840 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -649,7 +649,7 @@ namespace Avalonia.Controls { for (int i = 0; i < definitions.Length; ++i) { - definitions[i].OnBeforeLayout(this); + // definitions[i].OnBeforeLayout(this); double userMinSize = definitions[i].UserMinSize; double userMaxSize = definitions[i].UserMaxSize; @@ -1215,17 +1215,17 @@ namespace Avalonia.Controls } } - if (Double.IsPositiveInfinity(maxStar)) + if (double.IsPositiveInfinity(maxStar)) { // negative scale means one or more of the weights was Infinity scale = -1.0; } else if (starCount > 0) { - // if maxStar * starCount > Double.Max, summing all the weights could cause + // if maxStar * starCount > double.Max, summing all the weights could cause // floating-point overflow. To avoid that, scale the weights by a factor to keep // the sum within limits. Choose a power of 2, to preserve precision. - double power = Math.Floor(Math.Log(Double.MaxValue / maxStar / starCount, 2.0)); + double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0)); if (power < 0.0) { scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia @@ -1277,7 +1277,7 @@ namespace Avalonia.Controls } double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); - if (!Double.IsPositiveInfinity(effectiveMaxSize)) + if (!double.IsPositiveInfinity(effectiveMaxSize)) { // store ratio w/max in SizeCache (for now) tempDefinitions[defCount + maxCount++] = def; @@ -1322,7 +1322,7 @@ namespace Avalonia.Controls remainingStarWeight = totalStarWeight - takenStarWeight; } - double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity; + double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : double.PositiveInfinity; double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; // choose the def with larger ratio to the current proportion ("max discrepancy") @@ -1532,17 +1532,17 @@ namespace Avalonia.Controls } } - if (Double.IsPositiveInfinity(maxStar)) + if (double.IsPositiveInfinity(maxStar)) { // negative scale means one or more of the weights was Infinity scale = -1.0; } else if (starCount > 0) { - // if maxStar * starCount > Double.Max, summing all the weights could cause + // if maxStar * starCount > double.Max, summing all the weights could cause // floating-point overflow. To avoid that, scale the weights by a factor to keep // the sum within limits. Choose a power of 2, to preserve precision. - double power = Math.Floor(Math.Log(Double.MaxValue / maxStar / starCount, 2.0)); + double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0)); if (power < 0.0) { scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia @@ -1590,7 +1590,7 @@ namespace Avalonia.Controls } double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize); - if (!Double.IsPositiveInfinity(effectiveMaxSize)) + if (!double.IsPositiveInfinity(effectiveMaxSize)) { // store ratio w/max in SizeCache (for now) definitionIndices[defCount + maxCount++] = i; @@ -1670,7 +1670,7 @@ namespace Avalonia.Controls remainingStarWeight = totalStarWeight - takenStarWeight; } - double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : Double.PositiveInfinity; + double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : double.PositiveInfinity; double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0; // choose the def with larger ratio to the current proportion ("max discrepancy") @@ -1950,19 +1950,17 @@ namespace Avalonia.Controls } } - /// - /// Choose the ratio with maximum discrepancy from the current proportion. - /// Returns: - /// true if proportion fails a min constraint but not a max, or - /// if the min constraint has higher discrepancy - /// false if proportion fails a max constraint but not a min, or - /// if the max constraint has higher discrepancy - /// null if proportion doesn't fail a min or max constraint - /// The discrepancy is the ratio of the proportion to the max- or min-ratio. - /// When both ratios hit the constraint, minRatio < proportion < maxRatio, - /// and the minRatio has higher discrepancy if - /// (proportion / minRatio) > (maxRatio / proportion) - /// + // Choose the ratio with maximum discrepancy from the current proportion. + // Returns: + // true if proportion fails a min constraint but not a max, or + // if the min constraint has higher discrepancy + // false if proportion fails a max constraint but not a min, or + // if the max constraint has higher discrepancy + // null if proportion doesn't fail a min or max constraint + // The discrepancy is the ratio of the proportion to the max- or min-ratio. + // When both ratios hit the constraint, minRatio < proportion < maxRatio, + // and the minRatio has higher discrepancy if + // (proportion / minRatio) > (maxRatio / proportion) private static bool? Choose(double minRatio, double maxRatio, double proportion) { if (minRatio < proportion) @@ -2092,7 +2090,7 @@ namespace Avalonia.Controls // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. if (double.IsNaN(newValue) || double.IsInfinity(newValue) || - MathUtilities.AreClose(newValue, Double.MaxValue)) + MathUtilities.AreClose(newValue, double.MaxValue)) { newValue = value; } @@ -2249,7 +2247,7 @@ namespace Avalonia.Controls // if one of the *-weights is Infinity, adjust the weights by mapping // Infinty to 1.0 and everything else to 0.0: the infinite items share the // available space equally, everyone else gets nothing. - return (Double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0; + return (double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0; } else { diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs index 1cb09e16e9..d9120b010e 100644 --- a/src/Avalonia.Controls/RowDefinition.cs +++ b/src/Avalonia.Controls/RowDefinition.cs @@ -89,10 +89,10 @@ namespace Avalonia.Controls } - internal override GridLength UserSizeValueCache => this.Height; + internal override GridLength UserSize => this.Height; - internal override double UserMinSizeValueCache => this.MinHeight; + internal override double UserMinSize => this.MinHeight; - internal override double UserMaxSizeValueCache => this.MaxHeight; + internal override double UserMaxSize => this.MaxHeight; } } \ No newline at end of file From 998149c9e5732befb39c622ca2b599b66db28975 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 13:31:38 +0800 Subject: [PATCH 034/130] Calculate ActualWidth/Height for Row/ColDefinitions so that GridSplitter can work. --- src/Avalonia.Controls/GridWPF.cs | 78 ++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index e18d70c840..aa08dd8469 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -32,8 +32,8 @@ namespace Avalonia.Controls internal bool HasStarCellsU; internal bool HasStarCellsV; internal bool HasGroup3CellsInAutoRows; - internal bool ColumnDefinitionsDirty; - internal bool RowDefinitionsDirty; + internal bool ColumnDefinitionsDirty = true; + internal bool RowDefinitionsDirty = true; // index of the first cell in first cell group internal int CellGroup1; @@ -284,13 +284,13 @@ namespace Avalonia.Controls if (_rowDefinitions.Count > 0) DefinitionsV = _rowDefinitions.Cast().ToArray(); else - DefinitionsV = new DefinitionBase[1] { new ColumnDefinition() }; + DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; _rowDefinitions.CollectionChanged += (_, e) => { if (_rowDefinitions.Count == 0) { - DefinitionsV = new DefinitionBase[1] { new ColumnDefinition() }; + DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; } else { @@ -451,14 +451,23 @@ namespace Avalonia.Controls private void ValidateColumnDefinitionsStructure() { - if (DefinitionsU == null || DefinitionsU?.Count() == 0) - DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + if (ColumnDefinitionsDirty) + { + if (DefinitionsU == null) + DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + ColumnDefinitionsDirty = false; + } } private void ValidateRowDefinitionsStructure() { - if (DefinitionsV == null || DefinitionsV?.Count() == 0) - DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; + if (RowDefinitionsDirty) + { + if (DefinitionsV == null) + DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; + + RowDefinitionsDirty = false; + } } /// @@ -521,11 +530,60 @@ namespace Avalonia.Controls { SetValid(); } + + for (var i = 0; i < ColumnDefinitions.Count; i++) + { + ColumnDefinitions[i].ActualWidth = GetFinalColumnDefinitionWidth(i); + } + + for (var i = 0; i < RowDefinitions.Count; i++) + { + RowDefinitions[i].ActualHeight = GetFinalRowDefinitionHeight(i); + } + return (arrangeSize); } /// - /// Invalidates grid caches and makes the grid dirty for measure. + /// Returns final width for a column. + /// + /// + /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data. + /// + internal double GetFinalColumnDefinitionWidth(int columnIndex) + { + double value = 0.0; + + // actual value calculations require structure to be up-to-date + if (!ColumnDefinitionsDirty) + { + value = DefinitionsU[(columnIndex + 1) % DefinitionsU.Length].FinalOffset; + if (columnIndex != 0) { value -= DefinitionsU[columnIndex].FinalOffset; } + } + return (value); + } + + /// + /// Returns final height for a row. + /// + /// + /// Used from public RowDefinition ActualHeight. Calculates final height using offset data. + /// + internal double GetFinalRowDefinitionHeight(int rowIndex) + { + double value = 0.0; + + // actual value calculations require structure to be up-to-date + if (!RowDefinitionsDirty) + { + value = DefinitionsV[(rowIndex + 1) % DefinitionsV.Length].FinalOffset; + if (rowIndex != 0) { value -= DefinitionsV[rowIndex].FinalOffset; } + } + return (value); + } + + /// + /// Invalidates grid caches and makes the grid dirty for measure. /// internal void Invalidate() { @@ -2695,7 +2753,7 @@ namespace Avalonia.Controls var grid = this.GetVisualParent(); if (grid == null - || !grid.ShowGridLines + || !grid.ShowGridLines || grid.IsTrivialGrid) { return; From cc3422b4c39ea2cf52e14bf23ec02664a41fcf2a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 15:08:40 +0800 Subject: [PATCH 035/130] Reset MinSize on DefinitionsLayout validation. --- src/Avalonia.Controls/GridWPF.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index aa08dd8469..d86ec4c1c0 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -707,7 +707,8 @@ namespace Avalonia.Controls { for (int i = 0; i < definitions.Length; ++i) { - // definitions[i].OnBeforeLayout(this); + // Reset minimum size. + definitions[i].SetMinSize(0); double userMinSize = definitions[i].UserMinSize; double userMaxSize = definitions[i].UserMaxSize; @@ -718,6 +719,7 @@ namespace Avalonia.Controls case (GridUnitType.Pixel): definitions[i].SizeType = LayoutTimeSizeType.Pixel; userSize = definitions[i].UserSize.Value; + // this was brought with NewLayout and defeats squishy behavior userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); break; From b6d01fcaeb7fefc2fd88dd50304beb398b4c1274 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 15:11:21 +0800 Subject: [PATCH 036/130] Cleanup MinSize. --- src/Avalonia.Controls/DefinitionBase.cs | 39 +++---------------------- src/Avalonia.Controls/GridWPF.cs | 28 +++++++++--------- 2 files changed, 18 insertions(+), 49 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 38fc375631..3301d23f61 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -43,13 +43,11 @@ namespace Avalonia.Controls /// Internal helper to access up-to-date UserMaxSize property value. /// internal abstract double UserMaxSize { get; } - - private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's /// /// Layout-time user size type. /// - internal Grid.LayoutTimeSizeType SizeType {get; set;} + internal Grid.LayoutTimeSizeType SizeType { get; set; } /// /// Returns or sets measure size for the definition. /// @@ -81,17 +79,9 @@ namespace Avalonia.Controls internal double SizeCache { get; set; } /// - /// Returns min size. + /// Used during measure to accumulate size for "Auto" and "Star" DefinitionBase's /// - internal double MinSize - { - get - { - double minSize = _minSize; - return (minSize); - } - - } + internal double MinSize { get; set; } /// /// Updates min size. @@ -99,28 +89,7 @@ namespace Avalonia.Controls /// New size. internal void UpdateMinSize(double minSize) { - _minSize = Math.Max(_minSize, minSize); - } - - /// - /// Sets min size. - /// - /// New size. - internal void SetMinSize(double minSize) - { - _minSize = minSize; - } - - /// - /// Returns min size, always taking into account shared state. - /// - internal double MinSizeForArrange - { - get - { - double minSize = _minSize; - return (minSize); - } + MinSize = Math.Max(MinSize, minSize); } /// diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index d86ec4c1c0..970b4de865 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -708,7 +708,7 @@ namespace Avalonia.Controls for (int i = 0; i < definitions.Length; ++i) { // Reset minimum size. - definitions[i].SetMinSize(0); + definitions[i].MinSize = 0; double userMinSize = definitions[i].UserMinSize; double userMaxSize = definitions[i].UserMaxSize; @@ -719,7 +719,7 @@ namespace Avalonia.Controls case (GridUnitType.Pixel): definitions[i].SizeType = LayoutTimeSizeType.Pixel; userSize = definitions[i].UserSize.Value; - + // this was brought with NewLayout and defeats squishy behavior userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); break; @@ -784,11 +784,11 @@ namespace Avalonia.Controls { if (isRows) { - DefinitionsV[i].SetMinSize(minSizes[i]); + DefinitionsV[i].MinSize = minSizes[i]; } else { - DefinitionsU[i].SetMinSize(minSizes[i]); + DefinitionsU[i].MinSize = minSizes[i]; } } } @@ -1642,14 +1642,14 @@ namespace Avalonia.Controls double starWeight = StarWeight(def, scale); totalStarWeight += starWeight; - if (def.MinSizeForArrange > 0.0) + if (def.MinSize > 0.0) { // store ratio w/min in MeasureSize (for now) definitionIndices[minCount++] = i; - def.MeasureSize = starWeight / def.MinSizeForArrange; + def.MeasureSize = starWeight / def.MinSize; } - double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize); + double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); if (!double.IsPositiveInfinity(effectiveMaxSize)) { // store ratio w/max in SizeCache (for now) @@ -1669,7 +1669,7 @@ namespace Avalonia.Controls break; case (GridUnitType.Auto): - userSize = def.MinSizeForArrange; + userSize = def.MinSize; break; } @@ -1688,7 +1688,7 @@ namespace Avalonia.Controls userMaxSize = def.UserMaxSize; } - def.SizeCache = Math.Max(def.MinSizeForArrange, Math.Min(userSize, userMaxSize)); + def.SizeCache = Math.Max(def.MinSize, Math.Min(userSize, userMaxSize)); takenSize += def.SizeCache; } } @@ -1752,14 +1752,14 @@ namespace Avalonia.Controls { resolvedIndex = definitionIndices[minCount - 1]; resolvedDef = definitions[resolvedIndex]; - resolvedSize = resolvedDef.MinSizeForArrange; + resolvedSize = resolvedDef.MinSize; --minCount; } else { resolvedIndex = definitionIndices[defCount + maxCount - 1]; resolvedDef = definitions[resolvedIndex]; - resolvedSize = Math.Max(resolvedDef.MinSizeForArrange, resolvedDef.UserMaxSize); + resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); --maxCount; } @@ -1882,7 +1882,7 @@ namespace Avalonia.Controls // min and max should have no effect by now, but just in case... resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); - resolvedSize = Math.Max(def.MinSizeForArrange, resolvedSize); + resolvedSize = Math.Max(def.MinSize, resolvedSize); // Use the raw (unrounded) sizes to update takenSize, so that // proportions are computed in the same terms as in phase 3; @@ -1974,7 +1974,7 @@ namespace Avalonia.Controls { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache - dpiIncrement; - final = Math.Max(final, definition.MinSizeForArrange); + final = Math.Max(final, definition.MinSize); if (final < definition.SizeCache) { adjustedSize -= dpiIncrement; @@ -1990,7 +1990,7 @@ namespace Avalonia.Controls { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache + dpiIncrement; - final = Math.Max(final, definition.MinSizeForArrange); + final = Math.Max(final, definition.MinSize); if (final > definition.SizeCache) { adjustedSize += dpiIncrement; From 6d2601b54c581fa5bd5a332c84270d77ea333c78 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 17:16:45 +0800 Subject: [PATCH 037/130] Some refactoring... --- src/Avalonia.Controls/DefinitionBase.cs | 8 +- src/Avalonia.Controls/GridWPF.cs | 182 +++++++++++++----------- 2 files changed, 100 insertions(+), 90 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 3301d23f61..3dd4819ca7 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -48,6 +48,7 @@ namespace Avalonia.Controls /// Layout-time user size type. /// internal Grid.LayoutTimeSizeType SizeType { get; set; } + /// /// Returns or sets measure size for the definition. /// @@ -79,7 +80,7 @@ namespace Avalonia.Controls internal double SizeCache { get; set; } /// - /// Used during measure to accumulate size for "Auto" and "Star" DefinitionBase's + /// Used during measure and arrange to accumulate size for "Auto" and "Star" DefinitionBase's /// internal double MinSize { get; set; } @@ -96,10 +97,5 @@ namespace Avalonia.Controls /// Offset. /// internal double FinalOffset { get; set; } - - /// - /// Returns true if this definition is a part of shared group. - /// - internal bool IsShared { get; set; } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs index 970b4de865..35a38a6423 100644 --- a/src/Avalonia.Controls/GridWPF.cs +++ b/src/Avalonia.Controls/GridWPF.cs @@ -60,22 +60,29 @@ namespace Avalonia.Controls // Stores unrounded values and rounding errors during layout rounding. private double[] _roundingErrors; - private DefinitionBase[] DefinitionsU; - private DefinitionBase[] DefinitionsV; + private DefinitionBase[] _definitionsU; + private DefinitionBase[] _definitionsV; // 5 is an arbitrary constant chosen to end the measure loop private const int _layoutLoopMaxCount = 5; - private static readonly LocalDataStoreSlot _tempDefinitionsDataSlot = Thread.AllocateDataSlot(); - private static readonly IComparer _spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); - private static readonly IComparer _spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); - private static readonly IComparer _minRatioComparer = new MinRatioComparer(); - private static readonly IComparer _maxRatioComparer = new MaxRatioComparer(); - private static readonly IComparer _starWeightComparer = new StarWeightComparer(); + private static readonly LocalDataStoreSlot _tempDefinitionsDataSlot; + private static readonly IComparer _spanPreferredDistributionOrderComparer; + private static readonly IComparer _spanMaxDistributionOrderComparer; + private static readonly IComparer _minRatioComparer; + private static readonly IComparer _maxRatioComparer; + private static readonly IComparer _starWeightComparer; static Grid() { ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); + + _tempDefinitionsDataSlot = Thread.AllocateDataSlot(); + _spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); + _spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); + _minRatioComparer = new MinRatioComparer(); + _maxRatioComparer = new MaxRatioComparer(); + _starWeightComparer = new StarWeightComparer(); } /// @@ -240,19 +247,19 @@ namespace Avalonia.Controls ColumnDefinitionsDirty = true; if (_columnDefinitions.Count > 0) - DefinitionsU = _columnDefinitions.Cast().ToArray(); + _definitionsU = _columnDefinitions.Cast().ToArray(); else - DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; _columnDefinitions.CollectionChanged += (_, e) => { if (_columnDefinitions.Count == 0) { - DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; } else { - DefinitionsU = _columnDefinitions.Cast().ToArray(); + _definitionsU = _columnDefinitions.Cast().ToArray(); ColumnDefinitionsDirty = true; } Invalidate(); @@ -282,19 +289,19 @@ namespace Avalonia.Controls RowDefinitionsDirty = true; if (_rowDefinitions.Count > 0) - DefinitionsV = _rowDefinitions.Cast().ToArray(); + _definitionsV = _rowDefinitions.Cast().ToArray(); else - DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; + _definitionsV = new DefinitionBase[1] { new RowDefinition() }; _rowDefinitions.CollectionChanged += (_, e) => { if (_rowDefinitions.Count == 0) { - DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; + _definitionsV = new DefinitionBase[1] { new RowDefinition() }; } else { - DefinitionsV = _rowDefinitions.Cast().ToArray(); + _definitionsV = _rowDefinitions.Cast().ToArray(); RowDefinitionsDirty = true; } Invalidate(); @@ -302,8 +309,8 @@ namespace Avalonia.Controls } } - private bool IsTrivialGrid => (DefinitionsU?.Length <= 1) && - (DefinitionsV?.Length <= 1); + private bool IsTrivialGrid => (_definitionsU?.Length <= 1) && + (_definitionsV?.Length <= 1); /// /// Content measurement. @@ -358,12 +365,13 @@ namespace Avalonia.Controls } ValidateColumnDefinitionsStructure(); - ValidateDefinitionsLayout(DefinitionsU, sizeToContentU); + ValidateDefinitionsLayout(_definitionsU, sizeToContentU); ValidateRowDefinitionsStructure(); - ValidateDefinitionsLayout(DefinitionsV, sizeToContentV); + ValidateDefinitionsLayout(_definitionsV, sizeToContentV); - CellsStructureDirty |= (SizeToContentU != sizeToContentU) || (SizeToContentV != sizeToContentV); + CellsStructureDirty |= (SizeToContentU != sizeToContentU) + || (SizeToContentV != sizeToContentV); SizeToContentU = sizeToContentU; SizeToContentV = sizeToContentV; @@ -371,7 +379,7 @@ namespace Avalonia.Controls ValidateCells(); - Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); + Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0); MeasureCellsGroup(CellGroup1, constraint, false, false); @@ -381,9 +389,9 @@ namespace Avalonia.Controls if (canResolveStarsV) { - if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } + if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } MeasureCellsGroup(CellGroup2, constraint, false, false); - if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } + if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } MeasureCellsGroup(CellGroup3, constraint, false, false); } else @@ -393,9 +401,9 @@ namespace Avalonia.Controls bool canResolveStarsU = CellGroup2 > _cellCache.Length; if (canResolveStarsU) { - if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } + if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } MeasureCellsGroup(CellGroup3, constraint, false, false); - if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } + if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } } else { @@ -421,14 +429,15 @@ namespace Avalonia.Controls ApplyCachedMinSizes(group3MinSizes, true); } - if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } + if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } MeasureCellsGroup(CellGroup3, constraint, false, false); // Reset cached Group2Widths ApplyCachedMinSizes(group2MinSizes, false); - if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } - MeasureCellsGroup(CellGroup2, constraint, cnt == _layoutLoopMaxCount, false, out hasDesiredSizeUChanged); + if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } + MeasureCellsGroup(CellGroup2, constraint, + cnt == _layoutLoopMaxCount, false, out hasDesiredSizeUChanged); } while (hasDesiredSizeUChanged && ++cnt <= _layoutLoopMaxCount); } @@ -438,8 +447,8 @@ namespace Avalonia.Controls MeasureCellsGroup(CellGroup4, constraint, false, false); gridDesiredSize = new Size( - CalculateDesiredSize(DefinitionsU), - CalculateDesiredSize(DefinitionsV)); + CalculateDesiredSize(_definitionsU), + CalculateDesiredSize(_definitionsV)); } } finally @@ -453,8 +462,8 @@ namespace Avalonia.Controls { if (ColumnDefinitionsDirty) { - if (DefinitionsU == null) - DefinitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + if (_definitionsU == null) + _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; ColumnDefinitionsDirty = false; } } @@ -463,8 +472,8 @@ namespace Avalonia.Controls { if (RowDefinitionsDirty) { - if (DefinitionsV == null) - DefinitionsV = new DefinitionBase[1] { new RowDefinition() }; + if (_definitionsV == null) + _definitionsV = new DefinitionBase[1] { new RowDefinition() }; RowDefinitionsDirty = false; } @@ -491,10 +500,10 @@ namespace Avalonia.Controls } else { - Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); + Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0); - SetFinalSize(DefinitionsU, arrangeSize.Width, true); - SetFinalSize(DefinitionsV, arrangeSize.Height, false); + SetFinalSize(_definitionsU, arrangeSize.Width, true); + SetFinalSize(_definitionsV, arrangeSize.Height, false); for (int currentCell = 0; currentCell < _cellCache.Length; ++currentCell) { @@ -510,10 +519,10 @@ namespace Avalonia.Controls int rowSpan = _cellCache[currentCell].RowSpan; Rect cellRect = new Rect( - columnIndex == 0 ? 0.0 : DefinitionsU[columnIndex].FinalOffset, - rowIndex == 0 ? 0.0 : DefinitionsV[rowIndex].FinalOffset, - GetFinalSizeForRange(DefinitionsU, columnIndex, columnSpan), - GetFinalSizeForRange(DefinitionsV, rowIndex, rowSpan)); + columnIndex == 0 ? 0.0 : _definitionsU[columnIndex].FinalOffset, + rowIndex == 0 ? 0.0 : _definitionsV[rowIndex].FinalOffset, + GetFinalSizeForRange(_definitionsU, columnIndex, columnSpan), + GetFinalSizeForRange(_definitionsV, rowIndex, rowSpan)); cell.Arrange(cellRect); } @@ -557,8 +566,8 @@ namespace Avalonia.Controls // actual value calculations require structure to be up-to-date if (!ColumnDefinitionsDirty) { - value = DefinitionsU[(columnIndex + 1) % DefinitionsU.Length].FinalOffset; - if (columnIndex != 0) { value -= DefinitionsU[columnIndex].FinalOffset; } + value = _definitionsU[(columnIndex + 1) % _definitionsU.Length].FinalOffset; + if (columnIndex != 0) { value -= _definitionsU[columnIndex].FinalOffset; } } return (value); } @@ -576,8 +585,8 @@ namespace Avalonia.Controls // actual value calculations require structure to be up-to-date if (!RowDefinitionsDirty) { - value = DefinitionsV[(rowIndex + 1) % DefinitionsV.Length].FinalOffset; - if (rowIndex != 0) { value -= DefinitionsV[rowIndex].FinalOffset; } + value = _definitionsV[(rowIndex + 1) % _definitionsV.Length].FinalOffset; + if (rowIndex != 0) { value -= _definitionsV[rowIndex].FinalOffset; } } return (value); } @@ -622,29 +631,29 @@ namespace Avalonia.Controls // read indices from the corresponding properties // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Length - 1); + cell.ColumnIndex = Math.Min(GetColumn(child), _definitionsU.Length - 1); + // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Length - 1); + cell.RowIndex = Math.Min(GetRow(child), _definitionsV.Length - 1); // read span properties // clamp to not exceed beyond right side of the grid // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Length - cell.ColumnIndex); + cell.ColumnSpan = Math.Min(GetColumnSpan(child), _definitionsU.Length - cell.ColumnIndex); // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Length - cell.RowIndex); + cell.RowSpan = Math.Min(GetRowSpan(child), _definitionsV.Length - cell.RowIndex); - Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Length); - Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Length); + Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < _definitionsU.Length); + Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < _definitionsV.Length); // // calculate and cache length types for the child // - - cell.SizeTypeU = GetLengthTypeForRange(DefinitionsU, cell.ColumnIndex, cell.ColumnSpan); - cell.SizeTypeV = GetLengthTypeForRange(DefinitionsV, cell.RowIndex, cell.RowSpan); + cell.SizeTypeU = GetLengthTypeForRange(_definitionsU, cell.ColumnIndex, cell.ColumnSpan); + cell.SizeTypeV = GetLengthTypeForRange(_definitionsV, cell.RowIndex, cell.RowSpan); hasStarCellsU |= cell.IsStarU; hasStarCellsV |= cell.IsStarV; @@ -652,7 +661,6 @@ namespace Avalonia.Controls // // distribute cells into four groups. // - if (!cell.IsStarV) { if (!cell.IsStarU) @@ -751,7 +759,8 @@ namespace Avalonia.Controls private double[] CacheMinSizes(int cellsHead, bool isRows) { - double[] minSizes = isRows ? new double[DefinitionsV.Length] : new double[DefinitionsU.Length]; + double[] minSizes = isRows ? new double[_definitionsV.Length] + : new double[_definitionsU.Length]; for (int j = 0; j < minSizes.Length; j++) { @@ -763,11 +772,11 @@ namespace Avalonia.Controls { if (isRows) { - minSizes[_cellCache[i].RowIndex] = DefinitionsV[_cellCache[i].RowIndex].MinSize; + minSizes[_cellCache[i].RowIndex] = _definitionsV[_cellCache[i].RowIndex].MinSize; } else { - minSizes[_cellCache[i].ColumnIndex] = DefinitionsU[_cellCache[i].ColumnIndex].MinSize; + minSizes[_cellCache[i].ColumnIndex] = _definitionsU[_cellCache[i].ColumnIndex].MinSize; } i = _cellCache[i].Next; @@ -784,11 +793,11 @@ namespace Avalonia.Controls { if (isRows) { - DefinitionsV[i].MinSize = minSizes[i]; + _definitionsV[i].MinSize = minSizes[i]; } else { - DefinitionsU[i].MinSize = minSizes[i]; + _definitionsU[i].MinSize = minSizes[i]; } } } @@ -801,7 +810,8 @@ namespace Avalonia.Controls bool forceInfinityV) { bool unusedHasDesiredSizeUChanged; - MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out unusedHasDesiredSizeUChanged); + MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, + forceInfinityV, out unusedHasDesiredSizeUChanged); } /// @@ -844,7 +854,9 @@ namespace Avalonia.Controls { if (_cellCache[i].ColumnSpan == 1) { - DefinitionsU[_cellCache[i].ColumnIndex].UpdateMinSize(Math.Min(Children[i].DesiredSize.Width, DefinitionsU[_cellCache[i].ColumnIndex].UserMaxSize)); + _definitionsU[_cellCache[i].ColumnIndex] + .UpdateMinSize(Math.Min(Children[i].DesiredSize.Width, + _definitionsU[_cellCache[i].ColumnIndex].UserMaxSize)); } else { @@ -861,7 +873,9 @@ namespace Avalonia.Controls { if (_cellCache[i].RowSpan == 1) { - DefinitionsV[_cellCache[i].RowIndex].UpdateMinSize(Math.Min(Children[i].DesiredSize.Height, DefinitionsV[_cellCache[i].RowIndex].UserMaxSize)); + _definitionsV[_cellCache[i].RowIndex] + .UpdateMinSize(Math.Min(Children[i].DesiredSize.Height, + _definitionsV[_cellCache[i].RowIndex].UserMaxSize)); } else { @@ -885,7 +899,7 @@ namespace Avalonia.Controls double requestedSize = (double)e.Value; EnsureMinSizeInDefinitionRange( - key.U ? DefinitionsU : DefinitionsV, + key.U ? _definitionsU : _definitionsV, key.Start, key.Count, requestedSize, @@ -949,7 +963,7 @@ namespace Avalonia.Controls { // otherwise... cellMeasureWidth = GetMeasureSizeForRange( - DefinitionsU, + _definitionsU, _cellCache[cell].ColumnIndex, _cellCache[cell].ColumnSpan); } @@ -969,7 +983,7 @@ namespace Avalonia.Controls else { cellMeasureHeight = GetMeasureSizeForRange( - DefinitionsV, + _definitionsV, _cellCache[cell].RowIndex, _cellCache[cell].RowSpan); } @@ -1631,7 +1645,7 @@ namespace Avalonia.Controls if (def.UserSize.IsStar) { - Debug.Assert(!def.IsShared, "*-defs cannot be shared"); + // Debug.Assert(!def.IsShared, "*-defs cannot be shared"); if (def.MeasureSize < 0.0) { @@ -1675,18 +1689,18 @@ namespace Avalonia.Controls double userMaxSize; - if (def.IsShared) - { - // overriding userMaxSize effectively prevents squishy-ness. - // this is a "solution" to avoid shared definitions from been sized to - // different final size at arrange time, if / when different grids receive - // different final sizes. - userMaxSize = userSize; - } - else - { - userMaxSize = def.UserMaxSize; - } + // if (def.IsShared) + // { + // // overriding userMaxSize effectively prevents squishy-ness. + // // this is a "solution" to avoid shared definitions from been sized to + // // different final size at arrange time, if / when different grids receive + // // different final sizes. + // userMaxSize = userSize; + // } + // else + // { + userMaxSize = def.UserMaxSize; + // } def.SizeCache = Math.Max(def.MinSize, Math.Min(userSize, userMaxSize)); takenSize += def.SizeCache; @@ -2107,7 +2121,7 @@ namespace Avalonia.Controls if (_tempDefinitions != null) { // TempDefinitions has to be cleared to avoid "memory leaks" - Array.Clear(_tempDefinitions, 0, Math.Max(DefinitionsU.Length, DefinitionsV.Length)); + Array.Clear(_tempDefinitions, 0, Math.Max(_definitionsU.Length, _definitionsV.Length)); _tempDefinitions = null; } } @@ -2232,7 +2246,7 @@ namespace Avalonia.Controls { get { - int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length) * 2; + int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length) * 2; if (_tempDefinitions == null || _tempDefinitions.Length < requiredLength) @@ -2265,7 +2279,7 @@ namespace Avalonia.Controls { get { - int requiredLength = Math.Max(Math.Max(DefinitionsU.Length, DefinitionsV.Length), 1) * 2; + int requiredLength = Math.Max(Math.Max(_definitionsU.Length, _definitionsV.Length), 1) * 2; if (_definitionIndices == null || _definitionIndices.Length < requiredLength) { @@ -2283,7 +2297,7 @@ namespace Avalonia.Controls { get { - int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length); + int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length); if (_roundingErrors == null && requiredLength == 0) { From 49bcea0e33ec975763c5399700b343ed4461371e Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 18:01:31 +0800 Subject: [PATCH 038/130] Disable SharedSize tests for now. --- .../Avalonia.Controls.UnitTests/GridTests.cs | 1 - .../SharedSizeScopeTests.cs | 88 +++++++++---------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index 5799cb91c4..50335c44ee 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -179,6 +179,5 @@ namespace Avalonia.Controls.UnitTests Assert.False(target.IsMeasureValid); } - } } diff --git a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs b/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs index 715e9da880..c49d587a08 100644 --- a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs @@ -17,50 +17,50 @@ namespace Avalonia.Controls.UnitTests { } - [Fact] - public void All_Descendant_Grids_Are_Registered_When_Added_After_Setting_Scope() - { - var grids = new[] { new Grid(), new Grid(), new Grid() }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); - } - - [Fact] - public void All_Descendant_Grids_Are_Registered_When_Setting_Scope() - { - var grids = new[] { new Grid(), new Grid(), new Grid() }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.Child = scope; - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - - Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); - } - - [Fact] - public void All_Descendant_Grids_Are_Unregistered_When_Resetting_Scope() - { - var grids = new[] { new Grid(), new Grid(), new Grid() }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); - root.SetValue(Grid.IsSharedSizeScopeProperty, false); - Assert.All(grids, g => Assert.False(g.HasSharedSizeScope())); - Assert.Equal(null, root.GetValue(Grid.s_sharedSizeScopeHostProperty)); - } + // [Fact] + // public void All_Descendant_Grids_Are_Registered_When_Added_After_Setting_Scope() + // { + // var grids = new[] { new Grid(), new Grid(), new Grid() }; + // var scope = new Panel(); + // scope.Children.AddRange(grids); + + // var root = new TestRoot(); + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + // root.Child = scope; + + // Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); + // } + + // [Fact] + // public void All_Descendant_Grids_Are_Registered_When_Setting_Scope() + // { + // var grids = new[] { new Grid(), new Grid(), new Grid() }; + // var scope = new Panel(); + // scope.Children.AddRange(grids); + + // var root = new TestRoot(); + // root.Child = scope; + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + + // Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); + // } + + // [Fact] + // public void All_Descendant_Grids_Are_Unregistered_When_Resetting_Scope() + // { + // var grids = new[] { new Grid(), new Grid(), new Grid() }; + // var scope = new Panel(); + // scope.Children.AddRange(grids); + + // var root = new TestRoot(); + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + // root.Child = scope; + + // Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); + // root.SetValue(Grid.IsSharedSizeScopeProperty, false); + // Assert.All(grids, g => Assert.False(g.HasSharedSizeScope())); + // Assert.Equal(null, root.GetValue(Grid.s_sharedSizeScopeHostProperty)); + // } [Fact] public void Size_Is_Propagated_Between_Grids() From 973e82386efb6e10f165d399e3cf928a292e0faf Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 19:10:38 +0800 Subject: [PATCH 039/130] Disable Shared Size Tests pt. 2 --- .../SharedSizeScopeTests.cs | 378 +++++++++--------- 1 file changed, 189 insertions(+), 189 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs b/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs index c49d587a08..03eceb17f5 100644 --- a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs @@ -62,223 +62,223 @@ namespace Avalonia.Controls.UnitTests // Assert.Equal(null, root.GetValue(Grid.s_sharedSizeScopeHostProperty)); // } - [Fact] - public void Size_Is_Propagated_Between_Grids() - { - var grids = new[] { CreateGrid("A", null),CreateGrid(("A",new GridLength(30)), (null, new GridLength()))}; - var scope = new Panel(); - scope.Children.AddRange(grids); + // [Fact] + // public void Size_Is_Propagated_Between_Grids() + // { + // var grids = new[] { CreateGrid("A", null),CreateGrid(("A",new GridLength(30)), (null, new GridLength()))}; + // var scope = new Panel(); + // scope.Children.AddRange(grids); - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; + // var root = new TestRoot(); + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + // root.Child = scope; - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); - } + // root.Measure(new Size(50, 50)); + // root.Arrange(new Rect(new Point(), new Point(50, 50))); + // Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); + // } - [Fact] - public void Size_Propagation_Is_Constrained_To_Innermost_Scope() - { - var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - var innerScope = new Panel(); - innerScope.Children.AddRange(grids); - innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); - - var outerGrid = CreateGrid(("A", new GridLength(0))); - var outerScope = new Panel(); - outerScope.Children.AddRange(new[] { outerGrid, innerScope }); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = outerScope; - - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(0, outerGrid.ColumnDefinitions[0].ActualWidth); - } + // [Fact] + // public void Size_Propagation_Is_Constrained_To_Innermost_Scope() + // { + // var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + // var innerScope = new Panel(); + // innerScope.Children.AddRange(grids); + // innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); - [Fact] - public void Size_Is_Propagated_Between_Rows_And_Columns() - { - var grid = new Grid - { - ColumnDefinitions = new ColumnDefinitions("*,30"), - RowDefinitions = new RowDefinitions("*,10") - }; - - grid.ColumnDefinitions[1].SharedSizeGroup = "A"; - grid.RowDefinitions[1].SharedSizeGroup = "A"; - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = grid; - - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(30, grid.RowDefinitions[1].ActualHeight); - } + // var outerGrid = CreateGrid(("A", new GridLength(0))); + // var outerScope = new Panel(); + // outerScope.Children.AddRange(new[] { outerGrid, innerScope }); - [Fact] - public void Size_Group_Changes_Are_Tracked() - { - var grids = new[] { - CreateGrid((null, new GridLength(0, GridUnitType.Auto)), (null, new GridLength())), - CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - var scope = new Panel(); - scope.Children.AddRange(grids); + // var root = new TestRoot(); + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + // root.Child = outerScope; - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; + // root.Measure(new Size(50, 50)); + // root.Arrange(new Rect(new Point(), new Point(50, 50))); + // Assert.Equal(0, outerGrid.ColumnDefinitions[0].ActualWidth); + // } - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); + // [Fact] + // public void Size_Is_Propagated_Between_Rows_And_Columns() + // { + // var grid = new Grid + // { + // ColumnDefinitions = new ColumnDefinitions("*,30"), + // RowDefinitions = new RowDefinitions("*,10") + // }; - grids[0].ColumnDefinitions[0].SharedSizeGroup = "A"; + // grid.ColumnDefinitions[1].SharedSizeGroup = "A"; + // grid.RowDefinitions[1].SharedSizeGroup = "A"; - root.Measure(new Size(51, 51)); - root.Arrange(new Rect(new Point(), new Point(51, 51))); - Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); + // var root = new TestRoot(); + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + // root.Child = grid; - grids[0].ColumnDefinitions[0].SharedSizeGroup = null; + // root.Measure(new Size(50, 50)); + // root.Arrange(new Rect(new Point(), new Point(50, 50))); + // Assert.Equal(30, grid.RowDefinitions[1].ActualHeight); + // } - root.Measure(new Size(52, 52)); - root.Arrange(new Rect(new Point(), new Point(52, 52))); - Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); - } + // [Fact] + // public void Size_Group_Changes_Are_Tracked() + // { + // var grids = new[] { + // CreateGrid((null, new GridLength(0, GridUnitType.Auto)), (null, new GridLength())), + // CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + // var scope = new Panel(); + // scope.Children.AddRange(grids); - [Fact] - public void Collection_Changes_Are_Tracked() - { - var grid = CreateGrid( - ("A", new GridLength(20)), - ("A", new GridLength(30)), - ("A", new GridLength(40)), - (null, new GridLength())); + // var root = new TestRoot(); + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + // root.Child = scope; - var scope = new Panel(); - scope.Children.Add(grid); + // root.Measure(new Size(50, 50)); + // root.Arrange(new Rect(new Point(), new Point(50, 50))); + // Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; + // grids[0].ColumnDefinitions[0].SharedSizeGroup = "A"; - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(40, cd.ActualWidth)); + // root.Measure(new Size(51, 51)); + // root.Arrange(new Rect(new Point(), new Point(51, 51))); + // Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); - grid.ColumnDefinitions.RemoveAt(2); + // grids[0].ColumnDefinitions[0].SharedSizeGroup = null; - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); + // root.Measure(new Size(52, 52)); + // root.Arrange(new Rect(new Point(), new Point(52, 52))); + // Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); + // } - grid.ColumnDefinitions.Insert(1, new ColumnDefinition { Width = new GridLength(35), SharedSizeGroup = "A" }); + // [Fact] + // public void Collection_Changes_Are_Tracked() + // { + // var grid = CreateGrid( + // ("A", new GridLength(20)), + // ("A", new GridLength(30)), + // ("A", new GridLength(40)), + // (null, new GridLength())); - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(35, cd.ActualWidth)); + // var scope = new Panel(); + // scope.Children.Add(grid); - grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(10), SharedSizeGroup = "A" }; + // var root = new TestRoot(); + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + // root.Child = scope; - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); + // grid.Measure(new Size(200, 200)); + // grid.Arrange(new Rect(new Point(), new Point(200, 200))); + // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(40, cd.ActualWidth)); - grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(50), SharedSizeGroup = "A" }; + // grid.ColumnDefinitions.RemoveAt(2); - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); - } + // grid.Measure(new Size(200, 200)); + // grid.Arrange(new Rect(new Point(), new Point(200, 200))); + // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); - [Fact] - public void Size_Priorities_Are_Maintained() - { - var sizers = new List(); - var grid = CreateGrid( - ("A", new GridLength(20)), - ("A", new GridLength(20, GridUnitType.Auto)), - ("A", new GridLength(1, GridUnitType.Star)), - ("A", new GridLength(1, GridUnitType.Star)), - (null, new GridLength())); - for (int i = 0; i < 3; i++) - sizers.Add(AddSizer(grid, i, 6 + i * 6)); - var scope = new Panel(); - scope.Children.Add(grid); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - grid.Measure(new Size(100, 100)); - grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // all in group are equal to the first fixed column - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(20, cd.ActualWidth)); - - grid.ColumnDefinitions[0].SharedSizeGroup = null; - - grid.Measure(new Size(100, 100)); - grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // all in group are equal to width (MinWidth) of the sizer in the second column - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth)); - - grid.ColumnDefinitions[1].SharedSizeGroup = null; - - grid.Measure(new Size(double.PositiveInfinity, 100)); - grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // with no constraint star columns default to the MinWidth of the sizer in the column - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 2 * 6, cd.ActualWidth)); - } + // grid.ColumnDefinitions.Insert(1, new ColumnDefinition { Width = new GridLength(35), SharedSizeGroup = "A" }); - // grid creators - private Grid CreateGrid(params string[] columnGroups) - { - return CreateGrid(columnGroups.Select(s => (s, ColumnDefinition.WidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - } + // grid.Measure(new Size(200, 200)); + // grid.Arrange(new Rect(new Point(), new Point(200, 200))); + // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(35, cd.ActualWidth)); - private Grid CreateGrid(params (string name, GridLength width)[] columns) - { - return CreateGrid(columns.Select(c => - (c.name, c.width, ColumnDefinition.MinWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - } + // grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(10), SharedSizeGroup = "A" }; - private Grid CreateGrid(params (string name, GridLength width, double minWidth)[] columns) - { - return CreateGrid(columns.Select(c => - (c.name, c.width, c.minWidth, ColumnDefinition.MaxWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - } + // grid.Measure(new Size(200, 200)); + // grid.Arrange(new Rect(new Point(), new Point(200, 200))); + // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); - private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns) - { - var columnDefinitions = new ColumnDefinitions(); - - columnDefinitions.AddRange( - columns.Select(c => new ColumnDefinition - { - SharedSizeGroup = c.name, - Width = c.width, - MinWidth = c.minWidth, - MaxWidth = c.maxWidth - }) - ); - var grid = new Grid - { - ColumnDefinitions = columnDefinitions - }; - - return grid; - } + // grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(50), SharedSizeGroup = "A" }; - private Control AddSizer(Grid grid, int column, double size = 30) - { - var ctrl = new Control { MinWidth = size, MinHeight = size }; - ctrl.SetValue(Grid.ColumnProperty,column); - grid.Children.Add(ctrl); - return ctrl; - } + // grid.Measure(new Size(200, 200)); + // grid.Arrange(new Rect(new Point(), new Point(200, 200))); + // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); + // } + + // [Fact] + // public void Size_Priorities_Are_Maintained() + // { + // var sizers = new List(); + // var grid = CreateGrid( + // ("A", new GridLength(20)), + // ("A", new GridLength(20, GridUnitType.Auto)), + // ("A", new GridLength(1, GridUnitType.Star)), + // ("A", new GridLength(1, GridUnitType.Star)), + // (null, new GridLength())); + // for (int i = 0; i < 3; i++) + // sizers.Add(AddSizer(grid, i, 6 + i * 6)); + // var scope = new Panel(); + // scope.Children.Add(grid); + + // var root = new TestRoot(); + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + // root.Child = scope; + + // grid.Measure(new Size(100, 100)); + // grid.Arrange(new Rect(new Point(), new Point(100, 100))); + // // all in group are equal to the first fixed column + // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(20, cd.ActualWidth)); + + // grid.ColumnDefinitions[0].SharedSizeGroup = null; + + // grid.Measure(new Size(100, 100)); + // grid.Arrange(new Rect(new Point(), new Point(100, 100))); + // // all in group are equal to width (MinWidth) of the sizer in the second column + // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth)); + + // grid.ColumnDefinitions[1].SharedSizeGroup = null; + + // grid.Measure(new Size(double.PositiveInfinity, 100)); + // grid.Arrange(new Rect(new Point(), new Point(100, 100))); + // // with no constraint star columns default to the MinWidth of the sizer in the column + // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 2 * 6, cd.ActualWidth)); + // } + + // // grid creators + // private Grid CreateGrid(params string[] columnGroups) + // { + // return CreateGrid(columnGroups.Select(s => (s, ColumnDefinition.WidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + // } + + // private Grid CreateGrid(params (string name, GridLength width)[] columns) + // { + // return CreateGrid(columns.Select(c => + // (c.name, c.width, ColumnDefinition.MinWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + // } + + // private Grid CreateGrid(params (string name, GridLength width, double minWidth)[] columns) + // { + // return CreateGrid(columns.Select(c => + // (c.name, c.width, c.minWidth, ColumnDefinition.MaxWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + // } + + // private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns) + // { + // var columnDefinitions = new ColumnDefinitions(); + + // columnDefinitions.AddRange( + // columns.Select(c => new ColumnDefinition + // { + // SharedSizeGroup = c.name, + // Width = c.width, + // MinWidth = c.minWidth, + // MaxWidth = c.maxWidth + // }) + // ); + // var grid = new Grid + // { + // ColumnDefinitions = columnDefinitions + // }; + + // return grid; + // } + + // private Control AddSizer(Grid grid, int column, double size = 30) + // { + // var ctrl = new Control { MinWidth = size, MinHeight = size }; + // ctrl.SetValue(Grid.ColumnProperty,column); + // grid.Children.Add(ctrl); + // return ctrl; + // } } } From 52c562bffd2f67b9e79b39a9d9285c0c2bb8693b Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 19:17:23 +0800 Subject: [PATCH 040/130] Remove old Grid classes. --- .../Avalonia.Controls.csproj | 1 - src/Avalonia.Controls/Grid.cs | 2886 +++++++++++++++-- src/Avalonia.Controls/GridWPF.cs | 2824 ---------------- src/Avalonia.Controls/Utils/GridLayout.cs | 705 ---- .../Utils/SharedSizeScopeHost.cs | 651 ---- .../GridLayoutTests.cs | 184 -- 6 files changed, 2555 insertions(+), 4696 deletions(-) delete mode 100644 src/Avalonia.Controls/GridWPF.cs delete mode 100644 src/Avalonia.Controls/Utils/GridLayout.cs delete mode 100644 src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs delete mode 100644 tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index c321d3a2f1..32331d29ab 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -11,7 +11,6 @@ - diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index ad5e96376d..236b478f95 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1,5 +1,5 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; @@ -10,15 +10,81 @@ using System.Runtime.CompilerServices; using Avalonia.Collections; using Avalonia.Controls.Utils; using Avalonia.VisualTree; +using System.Threading; using JetBrains.Annotations; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia; +using System.Collections; +using Avalonia.Utilities; +using Avalonia.Layout; namespace Avalonia.Controls { /// - /// Lays out child controls according to a grid. + /// Grid /// public class Grid : Panel { + internal bool CellsStructureDirty = true; + internal bool SizeToContentU; + internal bool SizeToContentV; + internal bool HasStarCellsU; + internal bool HasStarCellsV; + internal bool HasGroup3CellsInAutoRows; + internal bool ColumnDefinitionsDirty = true; + internal bool RowDefinitionsDirty = true; + + // index of the first cell in first cell group + internal int CellGroup1; + + // index of the first cell in second cell group + internal int CellGroup2; + + // index of the first cell in third cell group + internal int CellGroup3; + + // index of the first cell in fourth cell group + internal int CellGroup4; + + // temporary array used during layout for various purposes + // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length) + private DefinitionBase[] _tempDefinitions; + private GridLinesRenderer _gridLinesRenderer; + + // Keeps track of definition indices. + private int[] _definitionIndices; + + private CellCache[] _cellCache; + + + // Stores unrounded values and rounding errors during layout rounding. + private double[] _roundingErrors; + private DefinitionBase[] _definitionsU; + private DefinitionBase[] _definitionsV; + + // 5 is an arbitrary constant chosen to end the measure loop + private const int _layoutLoopMaxCount = 5; + private static readonly LocalDataStoreSlot _tempDefinitionsDataSlot; + private static readonly IComparer _spanPreferredDistributionOrderComparer; + private static readonly IComparer _spanMaxDistributionOrderComparer; + private static readonly IComparer _minRatioComparer; + private static readonly IComparer _maxRatioComparer; + private static readonly IComparer _starWeightComparer; + + static Grid() + { + ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); + AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); + + _tempDefinitionsDataSlot = Thread.AllocateDataSlot(); + _spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); + _spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); + _minRatioComparer = new MinRatioComparer(); + _maxRatioComparer = new MaxRatioComparer(); + _starWeightComparer = new StarWeightComparer(); + } + /// /// Defines the Column attached property. /// @@ -50,91 +116,21 @@ namespace Avalonia.Controls public static readonly AttachedProperty IsSharedSizeScopeProperty = AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); - protected override void OnMeasureInvalidated() - { - base.OnMeasureInvalidated(); - _sharedSizeHost?.InvalidateMeasure(this); - } - - private SharedSizeScopeHost _sharedSizeHost; - - /// - /// Defines the SharedSizeScopeHost private property. - /// The ampersands are used to make accessing the property via xaml inconvenient. - /// - internal static readonly AttachedProperty s_sharedSizeScopeHostProperty = - AvaloniaProperty.RegisterAttached("&&SharedSizeScopeHost"); - - private ColumnDefinitions _columnDefinitions; - - private RowDefinitions _rowDefinitions; - - static Grid() - { - AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); - IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopeChanged); - } - - public Grid() - { - this.AttachedToVisualTree += Grid_AttachedToVisualTree; - this.DetachedFromVisualTree += Grid_DetachedFromVisualTree; - } - /// - /// Gets or sets the columns definitions for the grid. + /// Defines the property. /// - public ColumnDefinitions ColumnDefinitions - { - get - { - if (_columnDefinitions == null) - { - ColumnDefinitions = new ColumnDefinitions(); - } - - return _columnDefinitions; - } - - set - { - if (_columnDefinitions != null) - { - throw new NotSupportedException("Reassigning ColumnDefinitions not yet implemented."); - } - - _columnDefinitions = value; - _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); - _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); - } - } + public static readonly StyledProperty ShowGridLinesProperty = + AvaloniaProperty.Register( + nameof(ShowGridLines), + defaultValue: true); /// - /// Gets or sets the row definitions for the grid. + /// ShowGridLines property. /// - public RowDefinitions RowDefinitions + public bool ShowGridLines { - get - { - if (_rowDefinitions == null) - { - RowDefinitions = new RowDefinitions(); - } - - return _rowDefinitions; - } - - set - { - if (_rowDefinitions != null) - { - throw new NotSupportedException("Reassigning RowDefinitions not yet implemented."); - } - - _rowDefinitions = value; - _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure()); - _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure(); - } + get { return GetValue(ShowGridLinesProperty); } + set { SetValue(ShowGridLinesProperty, value); } } /// @@ -177,7 +173,6 @@ namespace Avalonia.Controls return element.GetValue(RowSpanProperty); } - /// /// Gets the value of the IsSharedSizeScope attached property for a control. /// @@ -228,373 +223,2602 @@ namespace Avalonia.Controls element.SetValue(RowSpanProperty, value); } + private ColumnDefinitions _columnDefinitions; + private RowDefinitions _rowDefinitions; + /// - /// Sets the value of IsSharedSizeScope property for a control. + /// Gets or sets the columns definitions for the grid. /// - /// The control. - /// The IsSharedSizeScope value. - public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) + public ColumnDefinitions ColumnDefinitions { - element.SetValue(IsSharedSizeScopeProperty, value); + get + { + if (_columnDefinitions == null) + { + ColumnDefinitions = new ColumnDefinitions(); + } + + return _columnDefinitions; + } + set + { + _columnDefinitions = value; + _columnDefinitions.TrackItemPropertyChanged(_ => Invalidate()); + ColumnDefinitionsDirty = true; + + if (_columnDefinitions.Count > 0) + _definitionsU = _columnDefinitions.Cast().ToArray(); + else + _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + + _columnDefinitions.CollectionChanged += (_, e) => + { + if (_columnDefinitions.Count == 0) + { + _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + } + else + { + _definitionsU = _columnDefinitions.Cast().ToArray(); + ColumnDefinitionsDirty = true; + } + Invalidate(); + }; + } } /// - /// Gets the result of the last column measurement. - /// Use this result to reduce the arrange calculation. + /// Gets or sets the row definitions for the grid. /// - private GridLayout.MeasureResult _columnMeasureCache; + public RowDefinitions RowDefinitions + { + get + { + if (_rowDefinitions == null) + { + RowDefinitions = new RowDefinitions(); + } - /// - /// Gets the result of the last row measurement. - /// Use this result to reduce the arrange calculation. - /// - private GridLayout.MeasureResult _rowMeasureCache; + return _rowDefinitions; + } + set + { + _rowDefinitions = value; + _rowDefinitions.TrackItemPropertyChanged(_ => Invalidate()); - /// - /// Gets the row layout as of the last measure. - /// - private GridLayout _rowLayoutCache; + RowDefinitionsDirty = true; - /// - /// Gets the column layout as of the last measure. - /// - private GridLayout _columnLayoutCache; + if (_rowDefinitions.Count > 0) + _definitionsV = _rowDefinitions.Cast().ToArray(); + else + _definitionsV = new DefinitionBase[1] { new RowDefinition() }; + + _rowDefinitions.CollectionChanged += (_, e) => + { + if (_rowDefinitions.Count == 0) + { + _definitionsV = new DefinitionBase[1] { new RowDefinition() }; + } + else + { + _definitionsV = _rowDefinitions.Cast().ToArray(); + RowDefinitionsDirty = true; + } + Invalidate(); + }; + } + } + + private bool IsTrivialGrid => (_definitionsU?.Length <= 1) && + (_definitionsV?.Length <= 1); /// - /// Measures the grid. + /// Content measurement. /// - /// The available size. - /// The desired size of the control. + /// Constraint + /// Desired size protected override Size MeasureOverride(Size constraint) { - // Situation 1/2: - // If the grid doesn't have any column/row definitions, it behaves like a normal panel. - // GridLayout supports this situation but we handle this separately for performance. + Size gridDesiredSize; - if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) + try { - var maxWidth = 0.0; - var maxHeight = 0.0; - foreach (var child in Children.OfType()) + if (IsTrivialGrid) { - child.Measure(constraint); - maxWidth = Math.Max(maxWidth, child.DesiredSize.Width); - maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); + gridDesiredSize = new Size(); + + for (int i = 0, count = Children.Count; i < count; ++i) + { + var child = Children[i]; + if (child != null) + { + child.Measure(constraint); + gridDesiredSize = new Size( + Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), + Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); + } + } + } + else + { + { + bool sizeToContentU = double.IsPositiveInfinity(constraint.Width); + bool sizeToContentV = double.IsPositiveInfinity(constraint.Height); + + // Clear index information and rounding errors + if (RowDefinitionsDirty || ColumnDefinitionsDirty) + { + if (_definitionIndices != null) + { + Array.Clear(_definitionIndices, 0, _definitionIndices.Length); + _definitionIndices = null; + } + + if (UseLayoutRounding) + { + if (_roundingErrors != null) + { + Array.Clear(_roundingErrors, 0, _roundingErrors.Length); + _roundingErrors = null; + } + } + } + + ValidateColumnDefinitionsStructure(); + ValidateDefinitionsLayout(_definitionsU, sizeToContentU); + + ValidateRowDefinitionsStructure(); + ValidateDefinitionsLayout(_definitionsV, sizeToContentV); + + CellsStructureDirty |= (SizeToContentU != sizeToContentU) + || (SizeToContentV != sizeToContentV); + + SizeToContentU = sizeToContentU; + SizeToContentV = sizeToContentV; + } + + ValidateCells(); + + Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0); + + MeasureCellsGroup(CellGroup1, constraint, false, false); + + { + // after Group1 is measured, only Group3 may have cells belonging to Auto rows. + bool canResolveStarsV = !HasGroup3CellsInAutoRows; + + if (canResolveStarsV) + { + if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } + MeasureCellsGroup(CellGroup2, constraint, false, false); + if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } + MeasureCellsGroup(CellGroup3, constraint, false, false); + } + else + { + // if at least one cell exists in Group2, it must be measured before + // StarsU can be resolved. + bool canResolveStarsU = CellGroup2 > _cellCache.Length; + if (canResolveStarsU) + { + if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } + MeasureCellsGroup(CellGroup3, constraint, false, false); + if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } + } + else + { + // This is a revision to the algorithm employed for the cyclic + // dependency case described above. We now repeatedly + // measure Group3 and Group2 until their sizes settle. We + // also use a count heuristic to break a loop in case of one. + + bool hasDesiredSizeUChanged = false; + int cnt = 0; + + // Cache Group2MinWidths & Group3MinHeights + double[] group2MinSizes = CacheMinSizes(CellGroup2, false); + double[] group3MinSizes = CacheMinSizes(CellGroup3, true); + + MeasureCellsGroup(CellGroup2, constraint, false, true); + + do + { + if (hasDesiredSizeUChanged) + { + // Reset cached Group3Heights + ApplyCachedMinSizes(group3MinSizes, true); + } + + if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } + MeasureCellsGroup(CellGroup3, constraint, false, false); + + // Reset cached Group2Widths + ApplyCachedMinSizes(group2MinSizes, false); + + if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } + MeasureCellsGroup(CellGroup2, constraint, + cnt == _layoutLoopMaxCount, false, out hasDesiredSizeUChanged); + } + while (hasDesiredSizeUChanged && ++cnt <= _layoutLoopMaxCount); + } + } + } + + MeasureCellsGroup(CellGroup4, constraint, false, false); + + gridDesiredSize = new Size( + CalculateDesiredSize(_definitionsU), + CalculateDesiredSize(_definitionsV)); } - - maxWidth = Math.Min(maxWidth, constraint.Width); - maxHeight = Math.Min(maxHeight, constraint.Height); - return new Size(maxWidth, maxHeight); } - - // Situation 2/2: - // If the grid defines some columns or rows. - // Debug Tip: - // - GridLayout doesn't hold any state, so you can drag the debugger execution - // arrow back to any statements and re-run them without any side-effect. - - var measureCache = new Dictionary(); - var (safeColumns, safeRows) = GetSafeColumnRows(); - var columnLayout = new GridLayout(ColumnDefinitions); - var rowLayout = new GridLayout(RowDefinitions); - // Note: If a child stays in a * or Auto column/row, use constraint to measure it. - columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width); - rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height); - - // Calculate measurement. - var columnResult = columnLayout.Measure(constraint.Width); - var rowResult = rowLayout.Measure(constraint.Height); - - // Use the results of the measurement to measure the rest of the children. - foreach (var child in Children.OfType()) + finally { - var (column, columnSpan) = safeColumns[child]; - var (row, rowSpan) = safeRows[child]; - var width = Enumerable.Range(column, columnSpan).Select(x => columnResult.LengthList[x]).Sum(); - var height = Enumerable.Range(row, rowSpan).Select(x => rowResult.LengthList[x]).Sum(); - - MeasureOnce(child, new Size(width, height)); } - // Cache the measure result and return the desired size. - _columnMeasureCache = columnResult; - _rowMeasureCache = rowResult; - _rowLayoutCache = rowLayout; - _columnLayoutCache = columnLayout; + return (gridDesiredSize); + } - if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) + private void ValidateColumnDefinitionsStructure() + { + if (ColumnDefinitionsDirty) { - _sharedSizeHost.UpdateMeasureStatus(this, rowResult, columnResult); + if (_definitionsU == null) + _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + ColumnDefinitionsDirty = false; } + } - return new Size(columnResult.DesiredLength, rowResult.DesiredLength); - - // Measure each child only once. - // If a child has been measured, it will just return the desired size. - Size MeasureOnce(Control child, Size size) + private void ValidateRowDefinitionsStructure() + { + if (RowDefinitionsDirty) { - if (measureCache.TryGetValue(child, out var desiredSize)) - { - return desiredSize; - } + if (_definitionsV == null) + _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - child.Measure(size); - desiredSize = child.DesiredSize; - measureCache[child] = desiredSize; - return desiredSize; + RowDefinitionsDirty = false; } } /// - /// Arranges the grid's children. + /// Content arrangement. /// - /// The size allocated to the control. - /// The space taken. - protected override Size ArrangeOverride(Size finalSize) + /// Arrange size + protected override Size ArrangeOverride(Size arrangeSize) { - // Situation 1/2: - // If the grid doesn't have any column/row definitions, it behaves like a normal panel. - // GridLayout supports this situation but we handle this separately for performance. - - if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) + try { - foreach (var child in Children.OfType()) + if (IsTrivialGrid) { - child.Arrange(new Rect(finalSize)); + for (int i = 0, count = Children.Count; i < count; ++i) + { + var child = Children[i]; + if (child != null) + { + child.Arrange(new Rect(arrangeSize)); + } + } + } + else + { + Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0); + + SetFinalSize(_definitionsU, arrangeSize.Width, true); + SetFinalSize(_definitionsV, arrangeSize.Height, false); + + for (int currentCell = 0; currentCell < _cellCache.Length; ++currentCell) + { + IControl cell = Children[currentCell]; + if (cell == null) + { + continue; + } + + int columnIndex = _cellCache[currentCell].ColumnIndex; + int rowIndex = _cellCache[currentCell].RowIndex; + int columnSpan = _cellCache[currentCell].ColumnSpan; + int rowSpan = _cellCache[currentCell].RowSpan; + + Rect cellRect = new Rect( + columnIndex == 0 ? 0.0 : _definitionsU[columnIndex].FinalOffset, + rowIndex == 0 ? 0.0 : _definitionsV[rowIndex].FinalOffset, + GetFinalSizeForRange(_definitionsU, columnIndex, columnSpan), + GetFinalSizeForRange(_definitionsV, rowIndex, rowSpan)); + + cell.Arrange(cellRect); + } + + // update render bound on grid lines renderer visual + var gridLinesRenderer = EnsureGridLinesRenderer(); + if (gridLinesRenderer != null) + { + gridLinesRenderer.UpdateRenderBounds(arrangeSize); + } } - - return finalSize; - } - - // Situation 2/2: - // If the grid defines some columns or rows. - // Debug Tip: - // - GridLayout doesn't hold any state, so you can drag the debugger execution - // arrow back to any statements and re-run them without any side-effect. - - var (safeColumns, safeRows) = GetSafeColumnRows(); - var columnLayout = _columnLayoutCache; - var rowLayout = _rowLayoutCache; - - var rowCache = _rowMeasureCache; - var columnCache = _columnMeasureCache; - - if (_sharedSizeHost?.ParticipatesInScope(this) ?? false) - { - (rowCache, columnCache) = _sharedSizeHost.HandleArrange(this, _rowMeasureCache, _columnMeasureCache); - - rowCache = rowLayout.Measure(finalSize.Height, rowCache.LeanLengthList); - columnCache = columnLayout.Measure(finalSize.Width, columnCache.LeanLengthList); } - - // Calculate for arrange result. - var columnResult = columnLayout.Arrange(finalSize.Width, columnCache); - var rowResult = rowLayout.Arrange(finalSize.Height, rowCache); - // Arrange the children. - foreach (var child in Children.OfType()) + finally { - var (column, columnSpan) = safeColumns[child]; - var (row, rowSpan) = safeRows[child]; - var x = Enumerable.Range(0, column).Sum(c => columnResult.LengthList[c]); - var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]); - var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]); - var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]); - child.Arrange(new Rect(x, y, width, height)); + SetValid(); } - // Assign the actual width. for (var i = 0; i < ColumnDefinitions.Count; i++) { - ColumnDefinitions[i].ActualWidth = columnResult.LengthList[i]; + ColumnDefinitions[i].ActualWidth = GetFinalColumnDefinitionWidth(i); } - // Assign the actual height. for (var i = 0; i < RowDefinitions.Count; i++) { - RowDefinitions[i].ActualHeight = rowResult.LengthList[i]; + RowDefinitions[i].ActualHeight = GetFinalRowDefinitionHeight(i); } - // Return the render size. - return finalSize; + return (arrangeSize); } /// - /// Tests whether this grid belongs to a shared size scope. + /// Returns final width for a column. /// - /// True if the grid is registered in a shared size scope. - internal bool HasSharedSizeScope() + /// + /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data. + /// + internal double GetFinalColumnDefinitionWidth(int columnIndex) { - return _sharedSizeHost != null; + double value = 0.0; + + // actual value calculations require structure to be up-to-date + if (!ColumnDefinitionsDirty) + { + value = _definitionsU[(columnIndex + 1) % _definitionsU.Length].FinalOffset; + if (columnIndex != 0) { value -= _definitionsU[columnIndex].FinalOffset; } + } + return (value); } /// - /// Called when the SharedSizeScope for a given grid has changed. - /// Unregisters the grid from it's current scope and finds a new one (if any) + /// Returns final height for a row. /// /// - /// This method, while not efficient, correctly handles nested scopes, with any order of scope changes. + /// Used from public RowDefinition ActualHeight. Calculates final height using offset data. /// - internal void SharedScopeChanged() + internal double GetFinalRowDefinitionHeight(int rowIndex) { - _sharedSizeHost?.UnegisterGrid(this); - - _sharedSizeHost = null; - var scope = this.GetVisualAncestors().OfType() - .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); + double value = 0.0; - if (scope != null) + // actual value calculations require structure to be up-to-date + if (!RowDefinitionsDirty) { - _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); - _sharedSizeHost.RegisterGrid(this); + value = _definitionsV[(rowIndex + 1) % _definitionsV.Length].FinalOffset; + if (rowIndex != 0) { value -= _definitionsV[rowIndex].FinalOffset; } } + return (value); + } + /// + /// Invalidates grid caches and makes the grid dirty for measure. + /// + internal void Invalidate() + { + CellsStructureDirty = true; InvalidateMeasure(); } /// - /// Callback when a grid is attached to the visual tree. Finds the innermost SharedSizeScope and registers the grid - /// in it. + /// Lays out cells according to rows and columns, and creates lookup grids. /// - /// The source of the event. - /// The event arguments. - private void Grid_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) + private void ValidateCells() { - var scope = - new Control[] { this }.Concat(this.GetVisualAncestors().OfType()) - .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty)); + if (!CellsStructureDirty) return; - if (_sharedSizeHost != null) - throw new AvaloniaInternalException("Shared size scope already present when attaching to visual tree!"); + _cellCache = new CellCache[Children.Count]; + CellGroup1 = int.MaxValue; + CellGroup2 = int.MaxValue; + CellGroup3 = int.MaxValue; + CellGroup4 = int.MaxValue; - if (scope != null) + bool hasStarCellsU = false; + bool hasStarCellsV = false; + bool hasGroup3CellsInAutoRows = false; + + for (int i = _cellCache.Length - 1; i >= 0; --i) { - _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty); - _sharedSizeHost.RegisterGrid(this); + var child = Children[i] as Control; + + if (child == null) + { + continue; + } + + var cell = new CellCache(); + + // read indices from the corresponding properties + // clamp to value < number_of_columns + // column >= 0 is guaranteed by property value validation callback + cell.ColumnIndex = Math.Min(GetColumn(child), _definitionsU.Length - 1); + + // clamp to value < number_of_rows + // row >= 0 is guaranteed by property value validation callback + cell.RowIndex = Math.Min(GetRow(child), _definitionsV.Length - 1); + + // read span properties + // clamp to not exceed beyond right side of the grid + // column_span > 0 is guaranteed by property value validation callback + cell.ColumnSpan = Math.Min(GetColumnSpan(child), _definitionsU.Length - cell.ColumnIndex); + + // clamp to not exceed beyond bottom side of the grid + // row_span > 0 is guaranteed by property value validation callback + cell.RowSpan = Math.Min(GetRowSpan(child), _definitionsV.Length - cell.RowIndex); + + Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < _definitionsU.Length); + Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < _definitionsV.Length); + + // + // calculate and cache length types for the child + // + cell.SizeTypeU = GetLengthTypeForRange(_definitionsU, cell.ColumnIndex, cell.ColumnSpan); + cell.SizeTypeV = GetLengthTypeForRange(_definitionsV, cell.RowIndex, cell.RowSpan); + + hasStarCellsU |= cell.IsStarU; + hasStarCellsV |= cell.IsStarV; + + // + // distribute cells into four groups. + // + if (!cell.IsStarV) + { + if (!cell.IsStarU) + { + cell.Next = CellGroup1; + CellGroup1 = i; + } + else + { + cell.Next = CellGroup3; + CellGroup3 = i; + + // remember if this cell belongs to auto row + hasGroup3CellsInAutoRows |= cell.IsAutoV; + } + } + else + { + if (cell.IsAutoU + // note below: if spans through Star column it is NOT Auto + && !cell.IsStarU) + { + cell.Next = CellGroup2; + CellGroup2 = i; + } + else + { + cell.Next = CellGroup4; + CellGroup4 = i; + } + } + + _cellCache[i] = cell; } + + HasStarCellsU = hasStarCellsU; + HasStarCellsV = hasStarCellsV; + HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows; + + CellsStructureDirty = false; } /// - /// Callback when a grid is detached from the visual tree. Unregisters the grid from its SharedSizeScope if any. + /// Validates layout time size type information on given array of definitions. + /// Sets MinSize and MeasureSizes. /// - /// The source of the event. - /// The event arguments. - private void Grid_DetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) + /// Array of definitions to update. + /// if "true" then star definitions are treated as Auto. + private void ValidateDefinitionsLayout( + DefinitionBase[] definitions, + bool treatStarAsAuto) { - _sharedSizeHost?.UnegisterGrid(this); - _sharedSizeHost = null; - } + for (int i = 0; i < definitions.Length; ++i) + { + // Reset minimum size. + definitions[i].MinSize = 0; + double userMinSize = definitions[i].UserMinSize; + double userMaxSize = definitions[i].UserMaxSize; + double userSize = 0; - /// - /// Get the safe column/columnspan and safe row/rowspan. - /// This method ensures that none of the children has a column/row outside the bounds of the definitions. - /// - [Pure] - private (Dictionary safeColumns, - Dictionary safeRows) GetSafeColumnRows() - { - var columnCount = ColumnDefinitions.Count; - var rowCount = RowDefinitions.Count; - columnCount = columnCount == 0 ? 1 : columnCount; - rowCount = rowCount == 0 ? 1 : rowCount; - var safeColumns = Children.OfType().ToDictionary(child => child, - child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child))); - var safeRows = Children.OfType().ToDictionary(child => child, - child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child))); - return (safeColumns, safeRows); + switch (definitions[i].UserSize.GridUnitType) + { + case (GridUnitType.Pixel): + definitions[i].SizeType = LayoutTimeSizeType.Pixel; + userSize = definitions[i].UserSize.Value; + + // this was brought with NewLayout and defeats squishy behavior + userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); + break; + case (GridUnitType.Auto): + definitions[i].SizeType = LayoutTimeSizeType.Auto; + userSize = double.PositiveInfinity; + break; + case (GridUnitType.Star): + if (treatStarAsAuto) + { + definitions[i].SizeType = LayoutTimeSizeType.Auto; + userSize = double.PositiveInfinity; + } + else + { + definitions[i].SizeType = LayoutTimeSizeType.Star; + userSize = double.PositiveInfinity; + } + break; + default: + Debug.Assert(false); + break; + } + + definitions[i].UpdateMinSize(userMinSize); + definitions[i].MeasureSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); + } } - /// - /// Gets the safe row/column and rowspan/columnspan for a specified range. - /// The user may assign row/column properties outside the bounds of the row/column count, this method coerces them inside. - /// - /// The row or column count. - /// The row or column that the user assigned. - /// The rowspan or columnspan that the user assigned. - /// The safe row/column and rowspan/columnspan. - [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] - private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan) + private double[] CacheMinSizes(int cellsHead, bool isRows) { - var index = userIndex; - var span = userSpan; + double[] minSizes = isRows ? new double[_definitionsV.Length] + : new double[_definitionsU.Length]; - if (index < 0) + for (int j = 0; j < minSizes.Length; j++) { - span = index + span; - index = 0; + minSizes[j] = -1; } - if (span <= 0) + int i = cellsHead; + do { - span = 1; - } + if (isRows) + { + minSizes[_cellCache[i].RowIndex] = _definitionsV[_cellCache[i].RowIndex].MinSize; + } + else + { + minSizes[_cellCache[i].ColumnIndex] = _definitionsU[_cellCache[i].ColumnIndex].MinSize; + } - if (userIndex >= length) - { - index = length - 1; - span = 1; - } - else if (userIndex + userSpan > length) + i = _cellCache[i].Next; + } while (i < _cellCache.Length); + + return minSizes; + } + + private void ApplyCachedMinSizes(double[] minSizes, bool isRows) + { + for (int i = 0; i < minSizes.Length; i++) { - span = length - userIndex; + if (MathUtilities.GreaterThanOrClose(minSizes[i], 0)) + { + if (isRows) + { + _definitionsV[i].MinSize = minSizes[i]; + } + else + { + _definitionsU[i].MinSize = minSizes[i]; + } + } } + } - return (index, span); + private void MeasureCellsGroup( + int cellsHead, + Size referenceSize, + bool ignoreDesiredSizeU, + bool forceInfinityV) + { + bool unusedHasDesiredSizeUChanged; + MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, + forceInfinityV, out unusedHasDesiredSizeUChanged); } - private static int ValidateColumn(AvaloniaObject o, int value) + /// + /// Measures one group of cells. + /// + /// Head index of the cells chain. + /// Reference size for spanned cells + /// calculations. + /// When "true" cells' desired + /// width is not registered in columns. + /// Passed through to MeasureCell. + /// When "true" cells' desired height is not registered in rows. + private void MeasureCellsGroup( + int cellsHead, + Size referenceSize, + bool ignoreDesiredSizeU, + bool forceInfinityV, + out bool hasDesiredSizeUChanged) { - if (value < 0) + hasDesiredSizeUChanged = false; + + if (cellsHead >= _cellCache.Length) { - throw new ArgumentException("Invalid Grid.Column value."); + return; } - return value; + Hashtable spanStore = null; + bool ignoreDesiredSizeV = forceInfinityV; + + int i = cellsHead; + do + { + double oldWidth = Children[i].DesiredSize.Width; + + MeasureCell(i, forceInfinityV); + + hasDesiredSizeUChanged |= !MathUtilities.AreClose(oldWidth, Children[i].DesiredSize.Width); + + if (!ignoreDesiredSizeU) + { + if (_cellCache[i].ColumnSpan == 1) + { + _definitionsU[_cellCache[i].ColumnIndex] + .UpdateMinSize(Math.Min(Children[i].DesiredSize.Width, + _definitionsU[_cellCache[i].ColumnIndex].UserMaxSize)); + } + else + { + RegisterSpan( + ref spanStore, + _cellCache[i].ColumnIndex, + _cellCache[i].ColumnSpan, + true, + Children[i].DesiredSize.Width); + } + } + + if (!ignoreDesiredSizeV) + { + if (_cellCache[i].RowSpan == 1) + { + _definitionsV[_cellCache[i].RowIndex] + .UpdateMinSize(Math.Min(Children[i].DesiredSize.Height, + _definitionsV[_cellCache[i].RowIndex].UserMaxSize)); + } + else + { + RegisterSpan( + ref spanStore, + _cellCache[i].RowIndex, + _cellCache[i].RowSpan, + false, + Children[i].DesiredSize.Height); + } + } + + i = _cellCache[i].Next; + } while (i < _cellCache.Length); + + if (spanStore != null) + { + foreach (DictionaryEntry e in spanStore) + { + SpanKey key = (SpanKey)e.Key; + double requestedSize = (double)e.Value; + + EnsureMinSizeInDefinitionRange( + key.U ? _definitionsU : _definitionsV, + key.Start, + key.Count, + requestedSize, + key.U ? referenceSize.Width : referenceSize.Height); + } + } } - private static int ValidateRow(AvaloniaObject o, int value) + /// + /// Helper method to register a span information for delayed processing. + /// + /// Reference to a hashtable object used as storage. + /// Span starting index. + /// Span count. + /// true if this is a column span. false if this is a row span. + /// Value to store. If an entry already exists the biggest value is stored. + private static void RegisterSpan( + ref Hashtable store, + int start, + int count, + bool u, + double value) { - if (value < 0) + if (store == null) { - throw new ArgumentException("Invalid Grid.Row value."); + store = new Hashtable(); } - return value; + SpanKey key = new SpanKey(start, count, u); + object o = store[key]; + + if (o == null + || value > (double)o) + { + store[key] = value; + } } /// - /// Called when the value of changes for a control. + /// Takes care of measuring a single cell. /// - /// The control that triggered the change. - /// Change arguments. - private static void IsSharedSizeScopeChanged(Control source, AvaloniaPropertyChangedEventArgs arg2) + /// Index of the cell to measure. + /// If "true" then cell is always + /// calculated to infinite height. + private void MeasureCell( + int cell, + bool forceInfinityV) { - var shouldDispose = (arg2.OldValue is bool d) && d; - if (shouldDispose) + double cellMeasureWidth; + double cellMeasureHeight; + + if (_cellCache[cell].IsAutoU + && !_cellCache[cell].IsStarU) { - var host = source.GetValue(s_sharedSizeScopeHostProperty) as SharedSizeScopeHost; - if (host == null) - throw new AvaloniaInternalException("SharedScopeHost wasn't set when IsSharedSizeScope was true!"); - host.Dispose(); - source.ClearValue(s_sharedSizeScopeHostProperty); + // if cell belongs to at least one Auto column and not a single Star column + // then it should be calculated "to content", thus it is possible to "shortcut" + // calculations and simply assign PositiveInfinity here. + cellMeasureWidth = double.PositiveInfinity; } - - var shouldAssign = (arg2.NewValue is bool a) && a; - if (shouldAssign) + else { - if (source.GetValue(s_sharedSizeScopeHostProperty) != null) - throw new AvaloniaInternalException("SharedScopeHost was already set when IsSharedSizeScope is only now being set to true!"); - source.SetValue(s_sharedSizeScopeHostProperty, new SharedSizeScopeHost()); + // otherwise... + cellMeasureWidth = GetMeasureSizeForRange( + _definitionsU, + _cellCache[cell].ColumnIndex, + _cellCache[cell].ColumnSpan); } - // if the scope has changed, notify the descendant grids that they need to update. - if (source.GetVisualRoot() != null && shouldAssign || shouldDispose) + if (forceInfinityV) { - var participatingGrids = new[] { source }.Concat(source.GetVisualDescendants()).OfType(); + cellMeasureHeight = double.PositiveInfinity; + } + else if (_cellCache[cell].IsAutoV + && !_cellCache[cell].IsStarV) + { + // if cell belongs to at least one Auto row and not a single Star row + // then it should be calculated "to content", thus it is possible to "shortcut" + // calculations and simply assign PositiveInfinity here. + cellMeasureHeight = double.PositiveInfinity; + } + else + { + cellMeasureHeight = GetMeasureSizeForRange( + _definitionsV, + _cellCache[cell].RowIndex, + _cellCache[cell].RowSpan); + } + + var child = Children[cell]; - foreach (var grid in participatingGrids) - grid.SharedScopeChanged(); + if (child != null) + { + Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); + child.Measure(childConstraint); } } + + /// + /// Calculates one dimensional measure size for given definitions' range. + /// + /// Source array of definitions to read values from. + /// Starting index of the range. + /// Number of definitions included in the range. + /// Calculated measure size. + /// + /// For "Auto" definitions MinWidth is used in place of PreferredSize. + /// + private double GetMeasureSizeForRange( + DefinitionBase[] definitions, + int start, + int count) + { + Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); + + double measureSize = 0; + int i = start + count - 1; + + do + { + measureSize += (definitions[i].SizeType == LayoutTimeSizeType.Auto) + ? definitions[i].MinSize + : definitions[i].MeasureSize; + } while (--i >= start); + + return (measureSize); + } + + /// + /// Accumulates length type information for given definition's range. + /// + /// Source array of definitions to read values from. + /// Starting index of the range. + /// Number of definitions included in the range. + /// Length type for given range. + private LayoutTimeSizeType GetLengthTypeForRange( + DefinitionBase[] definitions, + int start, + int count) + { + Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); + + LayoutTimeSizeType lengthType = LayoutTimeSizeType.None; + int i = start + count - 1; + + do + { + lengthType |= definitions[i].SizeType; + } while (--i >= start); + + return (lengthType); + } + + /// + /// Distributes min size back to definition array's range. + /// + /// Start of the range. + /// Number of items in the range. + /// Minimum size that should "fit" into the definitions range. + /// Definition array receiving distribution. + /// Size used to resolve percentages. + private void EnsureMinSizeInDefinitionRange( + DefinitionBase[] definitions, + int start, + int count, + double requestedSize, + double percentReferenceSize) + { + Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length); + + // avoid processing when asked to distribute "0" + if (!MathUtilities.IsZero(requestedSize)) + { + DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting + int end = start + count; + int autoDefinitionsCount = 0; + double rangeMinSize = 0; + double rangePreferredSize = 0; + double rangeMaxSize = 0; + double maxMaxSize = 0; // maximum of maximum sizes + + // first accumulate the necessary information: + // a) sum up the sizes in the range; + // b) count the number of auto definitions in the range; + // c) initialize temp array + // d) cache the maximum size into SizeCache + // e) accumulate max of max sizes + for (int i = start; i < end; ++i) + { + double minSize = definitions[i].MinSize; + double preferredSize = definitions[i].PreferredSize; + double maxSize = Math.Max(definitions[i].UserMaxSize, minSize); + + rangeMinSize += minSize; + rangePreferredSize += preferredSize; + rangeMaxSize += maxSize; + + definitions[i].SizeCache = maxSize; + + // sanity check: no matter what, but min size must always be the smaller; + // max size must be the biggest; and preferred should be in between + Debug.Assert(minSize <= preferredSize + && preferredSize <= maxSize + && rangeMinSize <= rangePreferredSize + && rangePreferredSize <= rangeMaxSize); + + if (maxMaxSize < maxSize) maxMaxSize = maxSize; + if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++; + tempDefinitions[i - start] = definitions[i]; + } + + // avoid processing if the range already big enough + if (requestedSize > rangeMinSize) + { + if (requestedSize <= rangePreferredSize) + { + // + // requestedSize fits into preferred size of the range. + // distribute according to the following logic: + // * do not distribute into auto definitions - they should continue to stay "tight"; + // * for all non-auto definitions distribute to equi-size min sizes, without exceeding preferred size. + // + // in order to achieve that, definitions are sorted in a way that all auto definitions + // are first, then definitions follow ascending order with PreferredSize as the key of sorting. + // + double sizeToDistribute; + int i; + + Array.Sort(tempDefinitions, 0, count, _spanPreferredDistributionOrderComparer); + for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) + { + // sanity check: only auto definitions allowed in this loop + Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + + // adjust sizeToDistribute value by subtracting auto definition min size + sizeToDistribute -= (tempDefinitions[i].MinSize); + } + + for (; i < count; ++i) + { + // sanity check: no auto definitions allowed in this loop + Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + + double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize); + if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); } + sizeToDistribute -= newMinSize; + } + + // sanity check: requested size must all be distributed + Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); + } + else if (requestedSize <= rangeMaxSize) + { + // + // requestedSize bigger than preferred size, but fit into max size of the range. + // distribute according to the following logic: + // * do not distribute into auto definitions, if possible - they should continue to stay "tight"; + // * for all non-auto definitions distribute to euqi-size min sizes, without exceeding max size. + // + // in order to achieve that, definitions are sorted in a way that all non-auto definitions + // are last, then definitions follow ascending order with MaxSize as the key of sorting. + // + double sizeToDistribute; + int i; + + Array.Sort(tempDefinitions, 0, count, _spanMaxDistributionOrderComparer); + for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) + { + // sanity check: no auto definitions allowed in this loop + Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + + double preferredSize = tempDefinitions[i].PreferredSize; + double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i); + tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); + sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + } + + for (; i < count; ++i) + { + // sanity check: only auto definitions allowed in this loop + Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + + double preferredSize = tempDefinitions[i].MinSize; + double newMinSize = preferredSize + sizeToDistribute / (count - i); + tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); + sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + } + + // sanity check: requested size must all be distributed + Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); + } + else + { + // + // requestedSize bigger than max size of the range. + // distribute according to the following logic: + // * for all definitions distribute to equi-size min sizes. + // + double equalSize = requestedSize / count; + + if (equalSize < maxMaxSize + && !MathUtilities.AreClose(equalSize, maxMaxSize)) + { + // equi-size is less than maximum of maxSizes. + // in this case distribute so that smaller definitions grow faster than + // bigger ones. + double totalRemainingSize = maxMaxSize * count - rangeMaxSize; + double sizeToDistribute = requestedSize - rangeMaxSize; + + // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers + Debug.Assert(!double.IsInfinity(totalRemainingSize) + && !double.IsNaN(totalRemainingSize) + && totalRemainingSize > 0 + && !double.IsInfinity(sizeToDistribute) + && !double.IsNaN(sizeToDistribute) + && sizeToDistribute > 0); + + for (int i = 0; i < count; ++i) + { + double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize; + tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize); + } + } + else + { + // + // equi-size is greater or equal to maximum of max sizes. + // all definitions receive equalSize as their mim sizes. + // + for (int i = 0; i < count; ++i) + { + tempDefinitions[i].UpdateMinSize(equalSize); + } + } + } + } + } + } + + // new implementation as of 4.7. Several improvements: + // 1. Allocate to *-defs hitting their min or max constraints, before allocating + // to other *-defs. A def that hits its min uses more space than its + // proportional share, reducing the space available to everyone else. + // The legacy algorithm deducted this space only from defs processed + // after the min; the new algorithm deducts it proportionally from all + // defs. This avoids the "*-defs exceed available space" problem, + // and other related problems where *-defs don't receive proportional + // allocations even though no constraints are preventing it. + // 2. When multiple defs hit min or max, resolve the one with maximum + // discrepancy (defined below). This avoids discontinuities - small + // change in available space resulting in large change to one def's allocation. + // 3. Correct handling of large *-values, including Infinity. + + /// + /// Resolves Star's for given array of definitions. + /// + /// Array of definitions to resolve stars. + /// All available size. + /// + /// Must initialize LayoutSize for all Star entries in given array of definitions. + /// + private void ResolveStar( + DefinitionBase[] definitions, + double availableSize) + { + int defCount = definitions.Length; + DefinitionBase[] tempDefinitions = TempDefinitions; + int minCount = 0, maxCount = 0; + double takenSize = 0; + double totalStarWeight = 0.0; + int starCount = 0; // number of unresolved *-definitions + double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" + + // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights + double maxStar = 0.0; + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + + if (def.SizeType == LayoutTimeSizeType.Star) + { + ++starCount; + def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3" + if (def.UserSize.Value > maxStar) + { + maxStar = def.UserSize.Value; + } + } + } + + if (double.IsPositiveInfinity(maxStar)) + { + // negative scale means one or more of the weights was Infinity + scale = -1.0; + } + else if (starCount > 0) + { + // if maxStar * starCount > double.Max, summing all the weights could cause + // floating-point overflow. To avoid that, scale the weights by a factor to keep + // the sum within limits. Choose a power of 2, to preserve precision. + double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0)); + if (power < 0.0) + { + scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia + } + } + + // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights + // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. + // More explanation below... + for (bool runPhase2and3 = true; runPhase2and3;) + { + // Phase 2. Compute total *-weight W and available space S. + // For *-items that have Min or Max constraints, compute the ratios used to decide + // whether proportional space is too big or too small and add the item to the + // corresponding list. (The "min" list is in the first half of tempDefinitions, + // the "max" list in the second half. TempDefinitions has capacity at least + // 2*defCount, so there's room for both lists.) + totalStarWeight = 0.0; + takenSize = 0.0; + minCount = maxCount = 0; + + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + + switch (def.SizeType) + { + case (LayoutTimeSizeType.Auto): + takenSize += definitions[i].MinSize; + break; + case (LayoutTimeSizeType.Pixel): + takenSize += def.MeasureSize; + break; + case (LayoutTimeSizeType.Star): + if (def.MeasureSize < 0.0) + { + takenSize += -def.MeasureSize; // already resolved + } + else + { + double starWeight = StarWeight(def, scale); + totalStarWeight += starWeight; + + if (def.MinSize > 0.0) + { + // store ratio w/min in MeasureSize (for now) + tempDefinitions[minCount++] = def; + def.MeasureSize = starWeight / def.MinSize; + } + + double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); + if (!double.IsPositiveInfinity(effectiveMaxSize)) + { + // store ratio w/max in SizeCache (for now) + tempDefinitions[defCount + maxCount++] = def; + def.SizeCache = starWeight / effectiveMaxSize; + } + } + break; + } + } + + // Phase 3. Resolve *-items whose proportional sizes are too big or too small. + int minCountPhase2 = minCount, maxCountPhase2 = maxCount; + double takenStarWeight = 0.0; + double remainingAvailableSize = availableSize - takenSize; + double remainingStarWeight = totalStarWeight - takenStarWeight; + Array.Sort(tempDefinitions, 0, minCount, _minRatioComparer); + Array.Sort(tempDefinitions, defCount, maxCount, _maxRatioComparer); + + while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) + { + // the calculation + // remainingStarWeight = totalStarWeight - takenStarWeight + // is subject to catastrophic cancellation if the two terms are nearly equal, + // which leads to meaningless results. Check for that, and recompute from + // the remaining definitions. [This leads to quadratic behavior in really + // pathological cases - but they'd never arise in practice.] + const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate + if (remainingStarWeight < totalStarWeight * starFactor) + { + takenStarWeight = 0.0; + totalStarWeight = 0.0; + + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + if (def.SizeType == LayoutTimeSizeType.Star && def.MeasureSize > 0.0) + { + totalStarWeight += StarWeight(def, scale); + } + } + + remainingStarWeight = totalStarWeight - takenStarWeight; + } + + double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : double.PositiveInfinity; + double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; + + // choose the def with larger ratio to the current proportion ("max discrepancy") + double proportion = remainingStarWeight / remainingAvailableSize; + bool? chooseMin = Choose(minRatio, maxRatio, proportion); + + // if no def was chosen, advance to phase 4; the current proportion doesn't + // conflict with any min or max values. + if (!(chooseMin.HasValue)) + { + break; + } + + // get the chosen definition and its resolved size + DefinitionBase resolvedDef; + double resolvedSize; + if (chooseMin == true) + { + resolvedDef = tempDefinitions[minCount - 1]; + resolvedSize = resolvedDef.MinSize; + --minCount; + } + else + { + resolvedDef = tempDefinitions[defCount + maxCount - 1]; + resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); + --maxCount; + } + + // resolve the chosen def, deduct its contributions from W and S. + // Defs resolved in phase 3 are marked by storing the negative of their resolved + // size in MeasureSize, to distinguish them from a pending def. + takenSize += resolvedSize; + resolvedDef.MeasureSize = -resolvedSize; + takenStarWeight += StarWeight(resolvedDef, scale); + --starCount; + + remainingAvailableSize = availableSize - takenSize; + remainingStarWeight = totalStarWeight - takenStarWeight; + + // advance to the next candidate defs, removing ones that have been resolved. + // Both counts are advanced, as a def might appear in both lists. + while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0) + { + --minCount; + tempDefinitions[minCount] = null; + } + while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0) + { + --maxCount; + tempDefinitions[defCount + maxCount] = null; + } + } + + // decide whether to run Phase2 and Phase3 again. There are 3 cases: + // 1. There is space available, and *-defs remaining. This is the + // normal case - move on to Phase 4 to allocate the remaining + // space proportionally to the remaining *-defs. + // 2. There is space available, but no *-defs. This implies at least one + // def was resolved as 'max', taking less space than its proportion. + // If there are also 'min' defs, reconsider them - we can give + // them more space. If not, all the *-defs are 'max', so there's + // no way to use all the available space. + // 3. We allocated too much space. This implies at least one def was + // resolved as 'min'. If there are also 'max' defs, reconsider + // them, otherwise the over-allocation is an inevitable consequence + // of the given min constraints. + // Note that if we return to Phase2, at least one *-def will have been + // resolved. This guarantees we don't run Phase2+3 infinitely often. + runPhase2and3 = false; + if (starCount == 0 && takenSize < availableSize) + { + // if no *-defs remain and we haven't allocated all the space, reconsider the defs + // resolved as 'min'. Their allocation can be increased to make up the gap. + for (int i = minCount; i < minCountPhase2; ++i) + { + DefinitionBase def = tempDefinitions[i]; + if (def != null) + { + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + + if (takenSize > availableSize) + { + // if we've allocated too much space, reconsider the defs + // resolved as 'max'. Their allocation can be decreased to make up the gap. + for (int i = maxCount; i < maxCountPhase2; ++i) + { + DefinitionBase def = tempDefinitions[defCount + i]; + if (def != null) + { + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + } + + // Phase 4. Resolve the remaining defs proportionally. + starCount = 0; + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + + if (def.SizeType == LayoutTimeSizeType.Star) + { + if (def.MeasureSize < 0.0) + { + // this def was resolved in phase 3 - fix up its measure size + def.MeasureSize = -def.MeasureSize; + } + else + { + // this def needs resolution, add it to the list, sorted by *-weight + tempDefinitions[starCount++] = def; + def.MeasureSize = StarWeight(def, scale); + } + } + } + + if (starCount > 0) + { + Array.Sort(tempDefinitions, 0, starCount, _starWeightComparer); + + // compute the partial sums of *-weight, in increasing order of weight + // for minimal loss of precision. + totalStarWeight = 0.0; + for (int i = 0; i < starCount; ++i) + { + DefinitionBase def = tempDefinitions[i]; + totalStarWeight += def.MeasureSize; + def.SizeCache = totalStarWeight; + } + + // resolve the defs, in decreasing order of weight + for (int i = starCount - 1; i >= 0; --i) + { + DefinitionBase def = tempDefinitions[i]; + double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; + + // min and max should have no effect by now, but just in case... + resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); + resolvedSize = Math.Max(def.MinSize, resolvedSize); + + def.MeasureSize = resolvedSize; + takenSize += resolvedSize; + } + } + } + + /// + /// Calculates desired size for given array of definitions. + /// + /// Array of definitions to use for calculations. + /// Desired size. + private double CalculateDesiredSize( + DefinitionBase[] definitions) + { + double desiredSize = 0; + + for (int i = 0; i < definitions.Length; ++i) + { + desiredSize += definitions[i].MinSize; + } + + return (desiredSize); + } + + /// + /// Calculates and sets final size for all definitions in the given array. + /// + /// Array of definitions to process. + /// Final size to lay out to. + /// True if sizing row definitions, false for columns + private void SetFinalSize( + DefinitionBase[] definitions, + double finalSize, + bool columns) + { + int defCount = definitions.Length; + int[] definitionIndices = DefinitionIndices; + int minCount = 0, maxCount = 0; + double takenSize = 0.0; + double totalStarWeight = 0.0; + int starCount = 0; // number of unresolved *-definitions + double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" + + // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights + double maxStar = 0.0; + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + + if (def.UserSize.IsStar) + { + ++starCount; + def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3" + if (def.UserSize.Value > maxStar) + { + maxStar = def.UserSize.Value; + } + } + } + + if (double.IsPositiveInfinity(maxStar)) + { + // negative scale means one or more of the weights was Infinity + scale = -1.0; + } + else if (starCount > 0) + { + // if maxStar * starCount > double.Max, summing all the weights could cause + // floating-point overflow. To avoid that, scale the weights by a factor to keep + // the sum within limits. Choose a power of 2, to preserve precision. + double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0)); + if (power < 0.0) + { + scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia + } + } + + + // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights + // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. + // More explanation below... + for (bool runPhase2and3 = true; runPhase2and3;) + { + // Phase 2. Compute total *-weight W and available space S. + // For *-items that have Min or Max constraints, compute the ratios used to decide + // whether proportional space is too big or too small and add the item to the + // corresponding list. (The "min" list is in the first half of definitionIndices, + // the "max" list in the second half. DefinitionIndices has capacity at least + // 2*defCount, so there's room for both lists.) + totalStarWeight = 0.0; + takenSize = 0.0; + minCount = maxCount = 0; + + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + + if (def.UserSize.IsStar) + { + // Debug.Assert(!def.IsShared, "*-defs cannot be shared"); + + if (def.MeasureSize < 0.0) + { + takenSize += -def.MeasureSize; // already resolved + } + else + { + double starWeight = StarWeight(def, scale); + totalStarWeight += starWeight; + + if (def.MinSize > 0.0) + { + // store ratio w/min in MeasureSize (for now) + definitionIndices[minCount++] = i; + def.MeasureSize = starWeight / def.MinSize; + } + + double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); + if (!double.IsPositiveInfinity(effectiveMaxSize)) + { + // store ratio w/max in SizeCache (for now) + definitionIndices[defCount + maxCount++] = i; + def.SizeCache = starWeight / effectiveMaxSize; + } + } + } + else + { + double userSize = 0; + + switch (def.UserSize.GridUnitType) + { + case (GridUnitType.Pixel): + userSize = def.UserSize.Value; + break; + + case (GridUnitType.Auto): + userSize = def.MinSize; + break; + } + + double userMaxSize; + + // if (def.IsShared) + // { + // // overriding userMaxSize effectively prevents squishy-ness. + // // this is a "solution" to avoid shared definitions from been sized to + // // different final size at arrange time, if / when different grids receive + // // different final sizes. + // userMaxSize = userSize; + // } + // else + // { + userMaxSize = def.UserMaxSize; + // } + + def.SizeCache = Math.Max(def.MinSize, Math.Min(userSize, userMaxSize)); + takenSize += def.SizeCache; + } + } + + // Phase 3. Resolve *-items whose proportional sizes are too big or too small. + int minCountPhase2 = minCount, maxCountPhase2 = maxCount; + double takenStarWeight = 0.0; + double remainingAvailableSize = finalSize - takenSize; + double remainingStarWeight = totalStarWeight - takenStarWeight; + + MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions); + Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer); + MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions); + Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer); + + while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) + { + // the calculation + // remainingStarWeight = totalStarWeight - takenStarWeight + // is subject to catastrophic cancellation if the two terms are nearly equal, + // which leads to meaningless results. Check for that, and recompute from + // the remaining definitions. [This leads to quadratic behavior in really + // pathological cases - but they'd never arise in practice.] + const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate + if (remainingStarWeight < totalStarWeight * starFactor) + { + takenStarWeight = 0.0; + totalStarWeight = 0.0; + + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + if (def.UserSize.IsStar && def.MeasureSize > 0.0) + { + totalStarWeight += StarWeight(def, scale); + } + } + + remainingStarWeight = totalStarWeight - takenStarWeight; + } + + double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : double.PositiveInfinity; + double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0; + + // choose the def with larger ratio to the current proportion ("max discrepancy") + double proportion = remainingStarWeight / remainingAvailableSize; + bool? chooseMin = Choose(minRatio, maxRatio, proportion); + + // if no def was chosen, advance to phase 4; the current proportion doesn't + // conflict with any min or max values. + if (!(chooseMin.HasValue)) + { + break; + } + + // get the chosen definition and its resolved size + int resolvedIndex; + DefinitionBase resolvedDef; + double resolvedSize; + if (chooseMin == true) + { + resolvedIndex = definitionIndices[minCount - 1]; + resolvedDef = definitions[resolvedIndex]; + resolvedSize = resolvedDef.MinSize; + --minCount; + } + else + { + resolvedIndex = definitionIndices[defCount + maxCount - 1]; + resolvedDef = definitions[resolvedIndex]; + resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); + --maxCount; + } + + // resolve the chosen def, deduct its contributions from W and S. + // Defs resolved in phase 3 are marked by storing the negative of their resolved + // size in MeasureSize, to distinguish them from a pending def. + takenSize += resolvedSize; + resolvedDef.MeasureSize = -resolvedSize; + takenStarWeight += StarWeight(resolvedDef, scale); + --starCount; + + remainingAvailableSize = finalSize - takenSize; + remainingStarWeight = totalStarWeight - takenStarWeight; + + // advance to the next candidate defs, removing ones that have been resolved. + // Both counts are advanced, as a def might appear in both lists. + while (minCount > 0 && definitions[definitionIndices[minCount - 1]].MeasureSize < 0.0) + { + --minCount; + definitionIndices[minCount] = -1; + } + while (maxCount > 0 && definitions[definitionIndices[defCount + maxCount - 1]].MeasureSize < 0.0) + { + --maxCount; + definitionIndices[defCount + maxCount] = -1; + } + } + + // decide whether to run Phase2 and Phase3 again. There are 3 cases: + // 1. There is space available, and *-defs remaining. This is the + // normal case - move on to Phase 4 to allocate the remaining + // space proportionally to the remaining *-defs. + // 2. There is space available, but no *-defs. This implies at least one + // def was resolved as 'max', taking less space than its proportion. + // If there are also 'min' defs, reconsider them - we can give + // them more space. If not, all the *-defs are 'max', so there's + // no way to use all the available space. + // 3. We allocated too much space. This implies at least one def was + // resolved as 'min'. If there are also 'max' defs, reconsider + // them, otherwise the over-allocation is an inevitable consequence + // of the given min constraints. + // Note that if we return to Phase2, at least one *-def will have been + // resolved. This guarantees we don't run Phase2+3 infinitely often. + runPhase2and3 = false; + if (starCount == 0 && takenSize < finalSize) + { + // if no *-defs remain and we haven't allocated all the space, reconsider the defs + // resolved as 'min'. Their allocation can be increased to make up the gap. + for (int i = minCount; i < minCountPhase2; ++i) + { + if (definitionIndices[i] >= 0) + { + DefinitionBase def = definitions[definitionIndices[i]]; + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + + if (takenSize > finalSize) + { + // if we've allocated too much space, reconsider the defs + // resolved as 'max'. Their allocation can be decreased to make up the gap. + for (int i = maxCount; i < maxCountPhase2; ++i) + { + if (definitionIndices[defCount + i] >= 0) + { + DefinitionBase def = definitions[definitionIndices[defCount + i]]; + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + } + + // Phase 4. Resolve the remaining defs proportionally. + starCount = 0; + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + + if (def.UserSize.IsStar) + { + if (def.MeasureSize < 0.0) + { + // this def was resolved in phase 3 - fix up its size + def.SizeCache = -def.MeasureSize; + } + else + { + // this def needs resolution, add it to the list, sorted by *-weight + definitionIndices[starCount++] = i; + def.MeasureSize = StarWeight(def, scale); + } + } + } + + if (starCount > 0) + { + StarWeightIndexComparer starWeightIndexComparer = new StarWeightIndexComparer(definitions); + Array.Sort(definitionIndices, 0, starCount, starWeightIndexComparer); + + // compute the partial sums of *-weight, in increasing order of weight + // for minimal loss of precision. + totalStarWeight = 0.0; + for (int i = 0; i < starCount; ++i) + { + DefinitionBase def = definitions[definitionIndices[i]]; + totalStarWeight += def.MeasureSize; + def.SizeCache = totalStarWeight; + } + + // resolve the defs, in decreasing order of weight. + for (int i = starCount - 1; i >= 0; --i) + { + DefinitionBase def = definitions[definitionIndices[i]]; + double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(finalSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; + + // min and max should have no effect by now, but just in case... + resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); + resolvedSize = Math.Max(def.MinSize, resolvedSize); + + // Use the raw (unrounded) sizes to update takenSize, so that + // proportions are computed in the same terms as in phase 3; + // this avoids errors arising from min/max constraints. + takenSize += resolvedSize; + def.SizeCache = resolvedSize; + } + } + + // Phase 5. Apply layout rounding. We do this after fully allocating + // unrounded sizes, to avoid breaking assumptions in the previous phases + if (UseLayoutRounding) + { + var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; + + double[] roundingErrors = RoundingErrors; + double roundedTakenSize = 0.0; + + // round each of the allocated sizes, keeping track of the deltas + for (int i = 0; i < definitions.Length; ++i) + { + DefinitionBase def = definitions[i]; + double roundedSize = RoundLayoutValue(def.SizeCache, dpi); + roundingErrors[i] = (roundedSize - def.SizeCache); + def.SizeCache = roundedSize; + roundedTakenSize += roundedSize; + } + + // The total allocation might differ from finalSize due to rounding + // effects. Tweak the allocations accordingly. + + // Theoretical and historical note. The problem at hand - allocating + // space to columns (or rows) with *-weights, min and max constraints, + // and layout rounding - has a long history. Especially the special + // case of 50 columns with min=1 and available space=435 - allocating + // seats in the U.S. House of Representatives to the 50 states in + // proportion to their population. There are numerous algorithms + // and papers dating back to the 1700's, including the book: + // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982. + // + // One surprising result of all this research is that *any* algorithm + // will suffer from one or more undesirable features such as the + // "population paradox" or the "Alabama paradox", where (to use our terminology) + // increasing the available space by one pixel might actually decrease + // the space allocated to a given column, or increasing the weight of + // a column might decrease its allocation. This is worth knowing + // in case someone complains about this behavior; it's not a bug so + // much as something inherent to the problem. Cite the book mentioned + // above or one of the 100s of references, and resolve as WontFix. + // + // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer) + // each being allocated a large number of pixels (~50 or greater), and + // people don't even notice the kind of 1-pixel anomolies that are + // theoretically inevitable, or don't care if they do. At least they shouldn't + // care - no one should be using the results WPF's grid layout to make + // quantitative decisions; its job is to produce a reasonable display, not + // to allocate seats in Congress. + // + // Our algorithm is more susceptible to paradox than the one currently + // used for Congressional allocation ("Huntington-Hill" algorithm), but + // it is faster to run: O(N log N) vs. O(S * N), where N=number of + // definitions, S = number of available pixels. And it produces + // adequate results in practice, as mentioned above. + // + // To reiterate one point: all this only applies when layout rounding + // is in effect. When fractional sizes are allowed, the algorithm + // behaves as well as possible, subject to the min/max constraints + // and precision of floating-point computation. (However, the resulting + // display is subject to anti-aliasing problems. TANSTAAFL.) + + if (!MathUtilities.AreClose(roundedTakenSize, finalSize)) + { + // Compute deltas + for (int i = 0; i < definitions.Length; ++i) + { + definitionIndices[i] = i; + } + + // Sort rounding errors + RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); + Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); + double adjustedSize = roundedTakenSize; + double dpiIncrement = 1.0 / dpi; + + if (roundedTakenSize > finalSize) + { + int i = definitions.Length - 1; + while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache - dpiIncrement; + final = Math.Max(final, definition.MinSize); + if (final < definition.SizeCache) + { + adjustedSize -= dpiIncrement; + } + definition.SizeCache = final; + i--; + } + } + else if (roundedTakenSize < finalSize) + { + int i = 0; + while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Length) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache + dpiIncrement; + final = Math.Max(final, definition.MinSize); + if (final > definition.SizeCache) + { + adjustedSize += dpiIncrement; + } + definition.SizeCache = final; + i++; + } + } + } + } + + // Phase 6. Compute final offsets + definitions[0].FinalOffset = 0.0; + for (int i = 0; i < definitions.Length; ++i) + { + definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; + } + } + + // Choose the ratio with maximum discrepancy from the current proportion. + // Returns: + // true if proportion fails a min constraint but not a max, or + // if the min constraint has higher discrepancy + // false if proportion fails a max constraint but not a min, or + // if the max constraint has higher discrepancy + // null if proportion doesn't fail a min or max constraint + // The discrepancy is the ratio of the proportion to the max- or min-ratio. + // When both ratios hit the constraint, minRatio < proportion < maxRatio, + // and the minRatio has higher discrepancy if + // (proportion / minRatio) > (maxRatio / proportion) + private static bool? Choose(double minRatio, double maxRatio, double proportion) + { + if (minRatio < proportion) + { + if (maxRatio > proportion) + { + // compare proportion/minRatio : maxRatio/proportion, but + // do it carefully to avoid floating-point overflow or underflow + // and divide-by-0. + double minPower = Math.Floor(Math.Log(minRatio, 2.0)); + double maxPower = Math.Floor(Math.Log(maxRatio, 2.0)); + double f = Math.Pow(2.0, Math.Floor((minPower + maxPower) / 2.0)); + if ((proportion / f) * (proportion / f) > (minRatio / f) * (maxRatio / f)) + { + return true; + } + else + { + return false; + } + } + else + { + return true; + } + } + else if (maxRatio > proportion) + { + return false; + } + + return null; + } + + /// + /// Sorts row/column indices by rounding error if layout rounding is applied. + /// + /// Index, rounding error pair + /// Index, rounding error pair + /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise + private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y) + { + if (x.Value < y.Value) + { + return -1; + } + else if (x.Value > y.Value) + { + return 1; + } + return 0; + } + + /// + /// Calculates final (aka arrange) size for given range. + /// + /// Array of definitions to process. + /// Start of the range. + /// Number of items in the range. + /// Final size. + private double GetFinalSizeForRange( + DefinitionBase[] definitions, + int start, + int count) + { + double size = 0; + int i = start + count - 1; + + do + { + size += definitions[i].SizeCache; + } while (--i >= start); + + return (size); + } + + /// + /// Clears dirty state for the grid and its columns / rows + /// + private void SetValid() + { + if (IsTrivialGrid) + { + if (_tempDefinitions != null) + { + // TempDefinitions has to be cleared to avoid "memory leaks" + Array.Clear(_tempDefinitions, 0, Math.Max(_definitionsU.Length, _definitionsV.Length)); + _tempDefinitions = null; + } + } + } + + + /// + /// Synchronized ShowGridLines property with the state of the grid's visual collection + /// by adding / removing GridLinesRenderer visual. + /// Returns a reference to GridLinesRenderer visual or null. + /// + private GridLinesRenderer EnsureGridLinesRenderer() + { + // + // synchronize the state + // + if (ShowGridLines && (_gridLinesRenderer == null)) + { + _gridLinesRenderer = new GridLinesRenderer(); + this.VisualChildren.Add(_gridLinesRenderer); + } + + if ((!ShowGridLines) && (_gridLinesRenderer != null)) + { + this.VisualChildren.Remove(_gridLinesRenderer); + _gridLinesRenderer = null; + } + + return (_gridLinesRenderer); + } + + private double RoundLayoutValue(double value, double dpiScale) + { + double newValue; + + // If DPI == 1, don't use DPI-aware rounding. + if (!MathUtilities.AreClose(dpiScale, 1.0)) + { + newValue = Math.Round(value * dpiScale) / dpiScale; + // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. + if (double.IsNaN(newValue) || + double.IsInfinity(newValue) || + MathUtilities.AreClose(newValue, double.MaxValue)) + { + newValue = value; + } + } + else + { + newValue = Math.Round(value); + } + + return newValue; + } + + + private static int ValidateColumn(AvaloniaObject o, int value) + { + if (value < 0) + { + throw new ArgumentException("Invalid Grid.Column value."); + } + + return value; + } + + private static int ValidateRow(AvaloniaObject o, int value) + { + if (value < 0) + { + throw new ArgumentException("Invalid Grid.Row value."); + } + + return value; + } + + private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) + { + if (!grid.IsTrivialGrid) // trivial grid is 1 by 1. there is no grid lines anyway + { + grid.Invalidate(); + } + } + + /// + /// Helper for Comparer methods. + /// + /// + /// true if one or both of x and y are null, in which case result holds + /// the relative sort order. + /// + private static bool CompareNullRefs(object x, object y, out int result) + { + result = 2; + + if (x == null) + { + if (y == null) + { + result = 0; + } + else + { + result = -1; + } + } + else + { + if (y == null) + { + result = 1; + } + } + + return (result != 2); + } + + /// + /// Helper accessor to layout time array of definitions. + /// + private DefinitionBase[] TempDefinitions + { + get + { + int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length) * 2; + + if (_tempDefinitions == null + || _tempDefinitions.Length < requiredLength) + { + WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(_tempDefinitionsDataSlot); + if (tempDefinitionsWeakRef == null) + { + _tempDefinitions = new DefinitionBase[requiredLength]; + Thread.SetData(_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions)); + } + else + { + _tempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; + if (_tempDefinitions == null + || _tempDefinitions.Length < requiredLength) + { + _tempDefinitions = new DefinitionBase[requiredLength]; + tempDefinitionsWeakRef.Target = _tempDefinitions; + } + } + } + return (_tempDefinitions); + } + } + + /// + /// Helper accessor to definition indices. + /// + private int[] DefinitionIndices + { + get + { + int requiredLength = Math.Max(Math.Max(_definitionsU.Length, _definitionsV.Length), 1) * 2; + + if (_definitionIndices == null || _definitionIndices.Length < requiredLength) + { + _definitionIndices = new int[requiredLength]; + } + + return _definitionIndices; + } + } + + /// + /// Helper accessor to rounding errors. + /// + private double[] RoundingErrors + { + get + { + int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length); + + if (_roundingErrors == null && requiredLength == 0) + { + _roundingErrors = new double[1]; + } + else if (_roundingErrors == null || _roundingErrors.Length < requiredLength) + { + _roundingErrors = new double[requiredLength]; + } + return _roundingErrors; + } + } + + /// + /// Returns *-weight, adjusted for scale computed during Phase 1 + /// + static double StarWeight(DefinitionBase def, double scale) + { + if (scale < 0.0) + { + // if one of the *-weights is Infinity, adjust the weights by mapping + // Infinty to 1.0 and everything else to 0.0: the infinite items share the + // available space equally, everyone else gets nothing. + return (double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0; + } + else + { + return def.UserSize.Value * scale; + } + } + + /// + /// LayoutTimeSizeType is used internally and reflects layout-time size type. + /// + [System.Flags] + internal enum LayoutTimeSizeType : byte + { + None = 0x00, + Pixel = 0x01, + Auto = 0x02, + Star = 0x04, + } + + /// + /// CellCache stored calculated values of + /// 1. attached cell positioning properties; + /// 2. size type; + /// 3. index of a next cell in the group; + /// + private struct CellCache + { + internal int ColumnIndex; + internal int RowIndex; + internal int ColumnSpan; + internal int RowSpan; + internal LayoutTimeSizeType SizeTypeU; + internal LayoutTimeSizeType SizeTypeV; + internal int Next; + internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } } + internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } } + internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } } + internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } } + } + + /// + /// Helper class for representing a key for a span in hashtable. + /// + private class SpanKey + { + /// + /// Constructor. + /// + /// Starting index of the span. + /// Span count. + /// true for columns; false for rows. + internal SpanKey(int start, int count, bool u) + { + _start = start; + _count = count; + _u = u; + } + + /// + /// + /// + public override int GetHashCode() + { + int hash = (_start ^ (_count << 2)); + + if (_u) hash &= 0x7ffffff; + else hash |= 0x8000000; + + return (hash); + } + + /// + /// + /// + public override bool Equals(object obj) + { + SpanKey sk = obj as SpanKey; + return (sk != null + && sk._start == _start + && sk._count == _count + && sk._u == _u); + } + + /// + /// Returns start index of the span. + /// + internal int Start { get { return (_start); } } + + /// + /// Returns span count. + /// + internal int Count { get { return (_count); } } + + /// + /// Returns true if this is a column span. + /// false if this is a row span. + /// + internal bool U { get { return (_u); } } + + private int _start; + private int _count; + private bool _u; + } + + /// + /// SpanPreferredDistributionOrderComparer. + /// + private class SpanPreferredDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + if (definitionX.UserSize.IsAuto) + { + if (definitionY.UserSize.IsAuto) + { + result = definitionX.MinSize.CompareTo(definitionY.MinSize); + } + else + { + result = -1; + } + } + else + { + if (definitionY.UserSize.IsAuto) + { + result = +1; + } + else + { + result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize); + } + } + } + + return result; + } + } + + /// + /// SpanMaxDistributionOrderComparer. + /// + private class SpanMaxDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + if (definitionX.UserSize.IsAuto) + { + if (definitionY.UserSize.IsAuto) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + else + { + result = +1; + } + } + else + { + if (definitionY.UserSize.IsAuto) + { + result = -1; + } + else + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + } + } + + return result; + } + } + + /// + /// RoundingErrorIndexComparer. + /// + private class RoundingErrorIndexComparer : IComparer + { + private readonly double[] errors; + + internal RoundingErrorIndexComparer(double[] errors) + { + Contract.Requires(errors != null); + this.errors = errors; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + int result; + + if (!CompareNullRefs(indexX, indexY, out result)) + { + double errorX = errors[indexX.Value]; + double errorY = errors[indexY.Value]; + result = errorX.CompareTo(errorY); + } + + return result; + } + } + + /// + /// MinRatioComparer. + /// Sort by w/min (stored in MeasureSize), descending. + /// We query the list from the back, i.e. in ascending order of w/min. + /// + private class MinRatioComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionY, definitionX, out result)) + { + result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); + } + + return result; + } + } + + /// + /// MaxRatioComparer. + /// Sort by w/max (stored in SizeCache), ascending. + /// We query the list from the back, i.e. in descending order of w/max. + /// + private class MaxRatioComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// StarWeightComparer. + /// Sort by *-weight (stored in MeasureSize), ascending. + /// + private class StarWeightComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); + } + + return result; + } + } + + /// + /// MinRatioIndexComparer. + /// + private class MinRatioIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal MinRatioIndexComparer(DefinitionBase[] definitions) + { + Contract.Requires(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionY, definitionX, out result)) + { + result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); + } + + return result; + } + } + + /// + /// MaxRatioIndexComparer. + /// + private class MaxRatioIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal MaxRatioIndexComparer(DefinitionBase[] definitions) + { + Contract.Requires(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// MaxRatioIndexComparer. + /// + private class StarWeightIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal StarWeightIndexComparer(DefinitionBase[] definitions) + { + Contract.Requires(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); + } + + return result; + } + } + + /// + /// Helper to render grid lines. + /// + private class GridLinesRenderer : Control + { + /// + /// Static initialization + /// + static GridLinesRenderer() + { + var oddDashArray = new List(); + oddDashArray.Add(_dashLength); + oddDashArray.Add(_dashLength); + var ds1 = new DashStyle(oddDashArray, 0); + _oddDashPen = new Pen(Brushes.Blue, + _penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds1); + + var evenDashArray = new List(); + evenDashArray.Add(_dashLength); + evenDashArray.Add(_dashLength); + var ds2 = new DashStyle(evenDashArray, 0); + _evenDashPen = new Pen(Brushes.Yellow, + _penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds2); + } + + /// + /// UpdateRenderBounds. + /// + public override void Render(DrawingContext drawingContext) + { + var grid = this.GetVisualParent(); + + if (grid == null + || !grid.ShowGridLines + || grid.IsTrivialGrid) + { + return; + } + + for (int i = 1; i < grid.ColumnDefinitions.Count; ++i) + { + DrawGridLine( + drawingContext, + grid.ColumnDefinitions[i].ActualWidth, 0.0, + grid.ColumnDefinitions[i].ActualWidth, _lastArrangeSize.Height); + } + + for (int i = 1; i < grid.RowDefinitions.Count; ++i) + { + DrawGridLine( + drawingContext, + 0.0, grid.RowDefinitions[i].ActualHeight, + _lastArrangeSize.Width, grid.RowDefinitions[i].ActualHeight); + } + } + + /// + /// Draw single hi-contrast line. + /// + private static void DrawGridLine( + DrawingContext drawingContext, + double startX, + double startY, + double endX, + double endY) + { + var start = new Point(startX, startY); + var end = new Point(endX, endY); + drawingContext.DrawLine(_oddDashPen, start, end); + drawingContext.DrawLine(_evenDashPen, start, end); + } + + internal void UpdateRenderBounds(Size arrangeSize) + { + _lastArrangeSize = arrangeSize; + this.InvalidateVisual(); + } + + private static Size _lastArrangeSize; + private const double _dashLength = 4.0; // + private const double _penWidth = 1.0; // + private static readonly Pen _oddDashPen; // first pen to draw dash + private static readonly Pen _evenDashPen; // second pen to draw dash + } } -} +} \ No newline at end of file diff --git a/src/Avalonia.Controls/GridWPF.cs b/src/Avalonia.Controls/GridWPF.cs deleted file mode 100644 index 35a38a6423..0000000000 --- a/src/Avalonia.Controls/GridWPF.cs +++ /dev/null @@ -1,2824 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using Avalonia.Collections; -using Avalonia.Controls.Utils; -using Avalonia.VisualTree; -using System.Threading; -using JetBrains.Annotations; -using Avalonia.Controls; -using Avalonia.Media; -using Avalonia; -using System.Collections; -using Avalonia.Utilities; -using Avalonia.Layout; - -namespace Avalonia.Controls -{ - /// - /// Grid - /// - public class Grid : Panel - { - internal bool CellsStructureDirty = true; - internal bool SizeToContentU; - internal bool SizeToContentV; - internal bool HasStarCellsU; - internal bool HasStarCellsV; - internal bool HasGroup3CellsInAutoRows; - internal bool ColumnDefinitionsDirty = true; - internal bool RowDefinitionsDirty = true; - - // index of the first cell in first cell group - internal int CellGroup1; - - // index of the first cell in second cell group - internal int CellGroup2; - - // index of the first cell in third cell group - internal int CellGroup3; - - // index of the first cell in fourth cell group - internal int CellGroup4; - - // temporary array used during layout for various purposes - // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length) - internal DefinitionBase[] _tempDefinitions; - private GridLinesRenderer _gridLinesRenderer; - - // Keeps track of definition indices. - private int[] _definitionIndices; - - private CellCache[] _cellCache; - - - // Stores unrounded values and rounding errors during layout rounding. - private double[] _roundingErrors; - private DefinitionBase[] _definitionsU; - private DefinitionBase[] _definitionsV; - - // 5 is an arbitrary constant chosen to end the measure loop - private const int _layoutLoopMaxCount = 5; - private static readonly LocalDataStoreSlot _tempDefinitionsDataSlot; - private static readonly IComparer _spanPreferredDistributionOrderComparer; - private static readonly IComparer _spanMaxDistributionOrderComparer; - private static readonly IComparer _minRatioComparer; - private static readonly IComparer _maxRatioComparer; - private static readonly IComparer _starWeightComparer; - - static Grid() - { - ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); - AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); - - _tempDefinitionsDataSlot = Thread.AllocateDataSlot(); - _spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); - _spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); - _minRatioComparer = new MinRatioComparer(); - _maxRatioComparer = new MaxRatioComparer(); - _starWeightComparer = new StarWeightComparer(); - } - - /// - /// Defines the Column attached property. - /// - public static readonly AttachedProperty ColumnProperty = - AvaloniaProperty.RegisterAttached( - "Column", - validate: ValidateColumn); - - /// - /// Defines the ColumnSpan attached property. - /// - public static readonly AttachedProperty ColumnSpanProperty = - AvaloniaProperty.RegisterAttached("ColumnSpan", 1); - - /// - /// Defines the Row attached property. - /// - public static readonly AttachedProperty RowProperty = - AvaloniaProperty.RegisterAttached( - "Row", - validate: ValidateRow); - - /// - /// Defines the RowSpan attached property. - /// - public static readonly AttachedProperty RowSpanProperty = - AvaloniaProperty.RegisterAttached("RowSpan", 1); - - public static readonly AttachedProperty IsSharedSizeScopeProperty = - AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); - - /// - /// Defines the property. - /// - public static readonly StyledProperty ShowGridLinesProperty = - AvaloniaProperty.Register( - nameof(ShowGridLines), - defaultValue: false); - - /// - /// ShowGridLines property. - /// - public bool ShowGridLines - { - get { return GetValue(ShowGridLinesProperty); } - set { SetValue(ShowGridLinesProperty, value); } - } - - /// - /// Gets the value of the Column attached property for a control. - /// - /// The control. - /// The control's column. - public static int GetColumn(AvaloniaObject element) - { - return element.GetValue(ColumnProperty); - } - - /// - /// Gets the value of the ColumnSpan attached property for a control. - /// - /// The control. - /// The control's column span. - public static int GetColumnSpan(AvaloniaObject element) - { - return element.GetValue(ColumnSpanProperty); - } - - /// - /// Gets the value of the Row attached property for a control. - /// - /// The control. - /// The control's row. - public static int GetRow(AvaloniaObject element) - { - return element.GetValue(RowProperty); - } - - /// - /// Gets the value of the RowSpan attached property for a control. - /// - /// The control. - /// The control's row span. - public static int GetRowSpan(AvaloniaObject element) - { - return element.GetValue(RowSpanProperty); - } - - /// - /// Gets the value of the IsSharedSizeScope attached property for a control. - /// - /// The control. - /// The control's IsSharedSizeScope value. - public static bool GetIsSharedSizeScope(AvaloniaObject element) - { - return element.GetValue(IsSharedSizeScopeProperty); - } - - /// - /// Sets the value of the Column attached property for a control. - /// - /// The control. - /// The column value. - public static void SetColumn(AvaloniaObject element, int value) - { - element.SetValue(ColumnProperty, value); - } - - /// - /// Sets the value of the ColumnSpan attached property for a control. - /// - /// The control. - /// The column span value. - public static void SetColumnSpan(AvaloniaObject element, int value) - { - element.SetValue(ColumnSpanProperty, value); - } - - /// - /// Sets the value of the Row attached property for a control. - /// - /// The control. - /// The row value. - public static void SetRow(AvaloniaObject element, int value) - { - element.SetValue(RowProperty, value); - } - - /// - /// Sets the value of the RowSpan attached property for a control. - /// - /// The control. - /// The row span value. - public static void SetRowSpan(AvaloniaObject element, int value) - { - element.SetValue(RowSpanProperty, value); - } - - private ColumnDefinitions _columnDefinitions; - private RowDefinitions _rowDefinitions; - - /// - /// Gets or sets the columns definitions for the grid. - /// - public ColumnDefinitions ColumnDefinitions - { - get - { - if (_columnDefinitions == null) - { - ColumnDefinitions = new ColumnDefinitions(); - } - - return _columnDefinitions; - } - set - { - _columnDefinitions = value; - _columnDefinitions.TrackItemPropertyChanged(_ => Invalidate()); - ColumnDefinitionsDirty = true; - - if (_columnDefinitions.Count > 0) - _definitionsU = _columnDefinitions.Cast().ToArray(); - else - _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - - _columnDefinitions.CollectionChanged += (_, e) => - { - if (_columnDefinitions.Count == 0) - { - _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - } - else - { - _definitionsU = _columnDefinitions.Cast().ToArray(); - ColumnDefinitionsDirty = true; - } - Invalidate(); - }; - } - } - - /// - /// Gets or sets the row definitions for the grid. - /// - public RowDefinitions RowDefinitions - { - get - { - if (_rowDefinitions == null) - { - RowDefinitions = new RowDefinitions(); - } - - return _rowDefinitions; - } - set - { - _rowDefinitions = value; - _rowDefinitions.TrackItemPropertyChanged(_ => Invalidate()); - - RowDefinitionsDirty = true; - - if (_rowDefinitions.Count > 0) - _definitionsV = _rowDefinitions.Cast().ToArray(); - else - _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - - _rowDefinitions.CollectionChanged += (_, e) => - { - if (_rowDefinitions.Count == 0) - { - _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - } - else - { - _definitionsV = _rowDefinitions.Cast().ToArray(); - RowDefinitionsDirty = true; - } - Invalidate(); - }; - } - } - - private bool IsTrivialGrid => (_definitionsU?.Length <= 1) && - (_definitionsV?.Length <= 1); - - /// - /// Content measurement. - /// - /// Constraint - /// Desired size - protected override Size MeasureOverride(Size constraint) - { - Size gridDesiredSize; - - try - { - if (IsTrivialGrid) - { - gridDesiredSize = new Size(); - - for (int i = 0, count = Children.Count; i < count; ++i) - { - var child = Children[i]; - if (child != null) - { - child.Measure(constraint); - gridDesiredSize = new Size( - Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), - Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); - } - } - } - else - { - { - bool sizeToContentU = double.IsPositiveInfinity(constraint.Width); - bool sizeToContentV = double.IsPositiveInfinity(constraint.Height); - - // Clear index information and rounding errors - if (RowDefinitionsDirty || ColumnDefinitionsDirty) - { - if (_definitionIndices != null) - { - Array.Clear(_definitionIndices, 0, _definitionIndices.Length); - _definitionIndices = null; - } - - if (UseLayoutRounding) - { - if (_roundingErrors != null) - { - Array.Clear(_roundingErrors, 0, _roundingErrors.Length); - _roundingErrors = null; - } - } - } - - ValidateColumnDefinitionsStructure(); - ValidateDefinitionsLayout(_definitionsU, sizeToContentU); - - ValidateRowDefinitionsStructure(); - ValidateDefinitionsLayout(_definitionsV, sizeToContentV); - - CellsStructureDirty |= (SizeToContentU != sizeToContentU) - || (SizeToContentV != sizeToContentV); - - SizeToContentU = sizeToContentU; - SizeToContentV = sizeToContentV; - } - - ValidateCells(); - - Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0); - - MeasureCellsGroup(CellGroup1, constraint, false, false); - - { - // after Group1 is measured, only Group3 may have cells belonging to Auto rows. - bool canResolveStarsV = !HasGroup3CellsInAutoRows; - - if (canResolveStarsV) - { - if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } - MeasureCellsGroup(CellGroup2, constraint, false, false); - if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } - MeasureCellsGroup(CellGroup3, constraint, false, false); - } - else - { - // if at least one cell exists in Group2, it must be measured before - // StarsU can be resolved. - bool canResolveStarsU = CellGroup2 > _cellCache.Length; - if (canResolveStarsU) - { - if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } - MeasureCellsGroup(CellGroup3, constraint, false, false); - if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } - } - else - { - // This is a revision to the algorithm employed for the cyclic - // dependency case described above. We now repeatedly - // measure Group3 and Group2 until their sizes settle. We - // also use a count heuristic to break a loop in case of one. - - bool hasDesiredSizeUChanged = false; - int cnt = 0; - - // Cache Group2MinWidths & Group3MinHeights - double[] group2MinSizes = CacheMinSizes(CellGroup2, false); - double[] group3MinSizes = CacheMinSizes(CellGroup3, true); - - MeasureCellsGroup(CellGroup2, constraint, false, true); - - do - { - if (hasDesiredSizeUChanged) - { - // Reset cached Group3Heights - ApplyCachedMinSizes(group3MinSizes, true); - } - - if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } - MeasureCellsGroup(CellGroup3, constraint, false, false); - - // Reset cached Group2Widths - ApplyCachedMinSizes(group2MinSizes, false); - - if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } - MeasureCellsGroup(CellGroup2, constraint, - cnt == _layoutLoopMaxCount, false, out hasDesiredSizeUChanged); - } - while (hasDesiredSizeUChanged && ++cnt <= _layoutLoopMaxCount); - } - } - } - - MeasureCellsGroup(CellGroup4, constraint, false, false); - - gridDesiredSize = new Size( - CalculateDesiredSize(_definitionsU), - CalculateDesiredSize(_definitionsV)); - } - } - finally - { - } - - return (gridDesiredSize); - } - - private void ValidateColumnDefinitionsStructure() - { - if (ColumnDefinitionsDirty) - { - if (_definitionsU == null) - _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - ColumnDefinitionsDirty = false; - } - } - - private void ValidateRowDefinitionsStructure() - { - if (RowDefinitionsDirty) - { - if (_definitionsV == null) - _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - - RowDefinitionsDirty = false; - } - } - - /// - /// Content arrangement. - /// - /// Arrange size - protected override Size ArrangeOverride(Size arrangeSize) - { - try - { - if (IsTrivialGrid) - { - for (int i = 0, count = Children.Count; i < count; ++i) - { - var child = Children[i]; - if (child != null) - { - child.Arrange(new Rect(arrangeSize)); - } - } - } - else - { - Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0); - - SetFinalSize(_definitionsU, arrangeSize.Width, true); - SetFinalSize(_definitionsV, arrangeSize.Height, false); - - for (int currentCell = 0; currentCell < _cellCache.Length; ++currentCell) - { - IControl cell = Children[currentCell]; - if (cell == null) - { - continue; - } - - int columnIndex = _cellCache[currentCell].ColumnIndex; - int rowIndex = _cellCache[currentCell].RowIndex; - int columnSpan = _cellCache[currentCell].ColumnSpan; - int rowSpan = _cellCache[currentCell].RowSpan; - - Rect cellRect = new Rect( - columnIndex == 0 ? 0.0 : _definitionsU[columnIndex].FinalOffset, - rowIndex == 0 ? 0.0 : _definitionsV[rowIndex].FinalOffset, - GetFinalSizeForRange(_definitionsU, columnIndex, columnSpan), - GetFinalSizeForRange(_definitionsV, rowIndex, rowSpan)); - - cell.Arrange(cellRect); - } - - // update render bound on grid lines renderer visual - var gridLinesRenderer = EnsureGridLinesRenderer(); - if (gridLinesRenderer != null) - { - gridLinesRenderer.UpdateRenderBounds(arrangeSize); - } - } - } - finally - { - SetValid(); - } - - for (var i = 0; i < ColumnDefinitions.Count; i++) - { - ColumnDefinitions[i].ActualWidth = GetFinalColumnDefinitionWidth(i); - } - - for (var i = 0; i < RowDefinitions.Count; i++) - { - RowDefinitions[i].ActualHeight = GetFinalRowDefinitionHeight(i); - } - - return (arrangeSize); - } - - /// - /// Returns final width for a column. - /// - /// - /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data. - /// - internal double GetFinalColumnDefinitionWidth(int columnIndex) - { - double value = 0.0; - - // actual value calculations require structure to be up-to-date - if (!ColumnDefinitionsDirty) - { - value = _definitionsU[(columnIndex + 1) % _definitionsU.Length].FinalOffset; - if (columnIndex != 0) { value -= _definitionsU[columnIndex].FinalOffset; } - } - return (value); - } - - /// - /// Returns final height for a row. - /// - /// - /// Used from public RowDefinition ActualHeight. Calculates final height using offset data. - /// - internal double GetFinalRowDefinitionHeight(int rowIndex) - { - double value = 0.0; - - // actual value calculations require structure to be up-to-date - if (!RowDefinitionsDirty) - { - value = _definitionsV[(rowIndex + 1) % _definitionsV.Length].FinalOffset; - if (rowIndex != 0) { value -= _definitionsV[rowIndex].FinalOffset; } - } - return (value); - } - - /// - /// Invalidates grid caches and makes the grid dirty for measure. - /// - internal void Invalidate() - { - CellsStructureDirty = true; - InvalidateMeasure(); - } - - /// - /// Lays out cells according to rows and columns, and creates lookup grids. - /// - private void ValidateCells() - { - if (!CellsStructureDirty) return; - - _cellCache = new CellCache[Children.Count]; - CellGroup1 = int.MaxValue; - CellGroup2 = int.MaxValue; - CellGroup3 = int.MaxValue; - CellGroup4 = int.MaxValue; - - bool hasStarCellsU = false; - bool hasStarCellsV = false; - bool hasGroup3CellsInAutoRows = false; - - for (int i = _cellCache.Length - 1; i >= 0; --i) - { - var child = Children[i] as Control; - - if (child == null) - { - continue; - } - - var cell = new CellCache(); - - // read indices from the corresponding properties - // clamp to value < number_of_columns - // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(GetColumn(child), _definitionsU.Length - 1); - - // clamp to value < number_of_rows - // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow(child), _definitionsV.Length - 1); - - // read span properties - // clamp to not exceed beyond right side of the grid - // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan(child), _definitionsU.Length - cell.ColumnIndex); - - // clamp to not exceed beyond bottom side of the grid - // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan(child), _definitionsV.Length - cell.RowIndex); - - Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < _definitionsU.Length); - Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < _definitionsV.Length); - - // - // calculate and cache length types for the child - // - cell.SizeTypeU = GetLengthTypeForRange(_definitionsU, cell.ColumnIndex, cell.ColumnSpan); - cell.SizeTypeV = GetLengthTypeForRange(_definitionsV, cell.RowIndex, cell.RowSpan); - - hasStarCellsU |= cell.IsStarU; - hasStarCellsV |= cell.IsStarV; - - // - // distribute cells into four groups. - // - if (!cell.IsStarV) - { - if (!cell.IsStarU) - { - cell.Next = CellGroup1; - CellGroup1 = i; - } - else - { - cell.Next = CellGroup3; - CellGroup3 = i; - - // remember if this cell belongs to auto row - hasGroup3CellsInAutoRows |= cell.IsAutoV; - } - } - else - { - if (cell.IsAutoU - // note below: if spans through Star column it is NOT Auto - && !cell.IsStarU) - { - cell.Next = CellGroup2; - CellGroup2 = i; - } - else - { - cell.Next = CellGroup4; - CellGroup4 = i; - } - } - - _cellCache[i] = cell; - } - - HasStarCellsU = hasStarCellsU; - HasStarCellsV = hasStarCellsV; - HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows; - - CellsStructureDirty = false; - } - - /// - /// Validates layout time size type information on given array of definitions. - /// Sets MinSize and MeasureSizes. - /// - /// Array of definitions to update. - /// if "true" then star definitions are treated as Auto. - private void ValidateDefinitionsLayout( - DefinitionBase[] definitions, - bool treatStarAsAuto) - { - for (int i = 0; i < definitions.Length; ++i) - { - // Reset minimum size. - definitions[i].MinSize = 0; - - double userMinSize = definitions[i].UserMinSize; - double userMaxSize = definitions[i].UserMaxSize; - double userSize = 0; - - switch (definitions[i].UserSize.GridUnitType) - { - case (GridUnitType.Pixel): - definitions[i].SizeType = LayoutTimeSizeType.Pixel; - userSize = definitions[i].UserSize.Value; - - // this was brought with NewLayout and defeats squishy behavior - userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); - break; - case (GridUnitType.Auto): - definitions[i].SizeType = LayoutTimeSizeType.Auto; - userSize = double.PositiveInfinity; - break; - case (GridUnitType.Star): - if (treatStarAsAuto) - { - definitions[i].SizeType = LayoutTimeSizeType.Auto; - userSize = double.PositiveInfinity; - } - else - { - definitions[i].SizeType = LayoutTimeSizeType.Star; - userSize = double.PositiveInfinity; - } - break; - default: - Debug.Assert(false); - break; - } - - definitions[i].UpdateMinSize(userMinSize); - definitions[i].MeasureSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); - } - } - - private double[] CacheMinSizes(int cellsHead, bool isRows) - { - double[] minSizes = isRows ? new double[_definitionsV.Length] - : new double[_definitionsU.Length]; - - for (int j = 0; j < minSizes.Length; j++) - { - minSizes[j] = -1; - } - - int i = cellsHead; - do - { - if (isRows) - { - minSizes[_cellCache[i].RowIndex] = _definitionsV[_cellCache[i].RowIndex].MinSize; - } - else - { - minSizes[_cellCache[i].ColumnIndex] = _definitionsU[_cellCache[i].ColumnIndex].MinSize; - } - - i = _cellCache[i].Next; - } while (i < _cellCache.Length); - - return minSizes; - } - - private void ApplyCachedMinSizes(double[] minSizes, bool isRows) - { - for (int i = 0; i < minSizes.Length; i++) - { - if (MathUtilities.GreaterThanOrClose(minSizes[i], 0)) - { - if (isRows) - { - _definitionsV[i].MinSize = minSizes[i]; - } - else - { - _definitionsU[i].MinSize = minSizes[i]; - } - } - } - } - - private void MeasureCellsGroup( - int cellsHead, - Size referenceSize, - bool ignoreDesiredSizeU, - bool forceInfinityV) - { - bool unusedHasDesiredSizeUChanged; - MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, - forceInfinityV, out unusedHasDesiredSizeUChanged); - } - - /// - /// Measures one group of cells. - /// - /// Head index of the cells chain. - /// Reference size for spanned cells - /// calculations. - /// When "true" cells' desired - /// width is not registered in columns. - /// Passed through to MeasureCell. - /// When "true" cells' desired height is not registered in rows. - private void MeasureCellsGroup( - int cellsHead, - Size referenceSize, - bool ignoreDesiredSizeU, - bool forceInfinityV, - out bool hasDesiredSizeUChanged) - { - hasDesiredSizeUChanged = false; - - if (cellsHead >= _cellCache.Length) - { - return; - } - - Hashtable spanStore = null; - bool ignoreDesiredSizeV = forceInfinityV; - - int i = cellsHead; - do - { - double oldWidth = Children[i].DesiredSize.Width; - - MeasureCell(i, forceInfinityV); - - hasDesiredSizeUChanged |= !MathUtilities.AreClose(oldWidth, Children[i].DesiredSize.Width); - - if (!ignoreDesiredSizeU) - { - if (_cellCache[i].ColumnSpan == 1) - { - _definitionsU[_cellCache[i].ColumnIndex] - .UpdateMinSize(Math.Min(Children[i].DesiredSize.Width, - _definitionsU[_cellCache[i].ColumnIndex].UserMaxSize)); - } - else - { - RegisterSpan( - ref spanStore, - _cellCache[i].ColumnIndex, - _cellCache[i].ColumnSpan, - true, - Children[i].DesiredSize.Width); - } - } - - if (!ignoreDesiredSizeV) - { - if (_cellCache[i].RowSpan == 1) - { - _definitionsV[_cellCache[i].RowIndex] - .UpdateMinSize(Math.Min(Children[i].DesiredSize.Height, - _definitionsV[_cellCache[i].RowIndex].UserMaxSize)); - } - else - { - RegisterSpan( - ref spanStore, - _cellCache[i].RowIndex, - _cellCache[i].RowSpan, - false, - Children[i].DesiredSize.Height); - } - } - - i = _cellCache[i].Next; - } while (i < _cellCache.Length); - - if (spanStore != null) - { - foreach (DictionaryEntry e in spanStore) - { - SpanKey key = (SpanKey)e.Key; - double requestedSize = (double)e.Value; - - EnsureMinSizeInDefinitionRange( - key.U ? _definitionsU : _definitionsV, - key.Start, - key.Count, - requestedSize, - key.U ? referenceSize.Width : referenceSize.Height); - } - } - } - - /// - /// Helper method to register a span information for delayed processing. - /// - /// Reference to a hashtable object used as storage. - /// Span starting index. - /// Span count. - /// true if this is a column span. false if this is a row span. - /// Value to store. If an entry already exists the biggest value is stored. - private static void RegisterSpan( - ref Hashtable store, - int start, - int count, - bool u, - double value) - { - if (store == null) - { - store = new Hashtable(); - } - - SpanKey key = new SpanKey(start, count, u); - object o = store[key]; - - if (o == null - || value > (double)o) - { - store[key] = value; - } - } - - /// - /// Takes care of measuring a single cell. - /// - /// Index of the cell to measure. - /// If "true" then cell is always - /// calculated to infinite height. - private void MeasureCell( - int cell, - bool forceInfinityV) - { - double cellMeasureWidth; - double cellMeasureHeight; - - if (_cellCache[cell].IsAutoU - && !_cellCache[cell].IsStarU) - { - // if cell belongs to at least one Auto column and not a single Star column - // then it should be calculated "to content", thus it is possible to "shortcut" - // calculations and simply assign PositiveInfinity here. - cellMeasureWidth = double.PositiveInfinity; - } - else - { - // otherwise... - cellMeasureWidth = GetMeasureSizeForRange( - _definitionsU, - _cellCache[cell].ColumnIndex, - _cellCache[cell].ColumnSpan); - } - - if (forceInfinityV) - { - cellMeasureHeight = double.PositiveInfinity; - } - else if (_cellCache[cell].IsAutoV - && !_cellCache[cell].IsStarV) - { - // if cell belongs to at least one Auto row and not a single Star row - // then it should be calculated "to content", thus it is possible to "shortcut" - // calculations and simply assign PositiveInfinity here. - cellMeasureHeight = double.PositiveInfinity; - } - else - { - cellMeasureHeight = GetMeasureSizeForRange( - _definitionsV, - _cellCache[cell].RowIndex, - _cellCache[cell].RowSpan); - } - - var child = Children[cell]; - - if (child != null) - { - Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); - child.Measure(childConstraint); - } - } - - /// - /// Calculates one dimensional measure size for given definitions' range. - /// - /// Source array of definitions to read values from. - /// Starting index of the range. - /// Number of definitions included in the range. - /// Calculated measure size. - /// - /// For "Auto" definitions MinWidth is used in place of PreferredSize. - /// - private double GetMeasureSizeForRange( - DefinitionBase[] definitions, - int start, - int count) - { - Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); - - double measureSize = 0; - int i = start + count - 1; - - do - { - measureSize += (definitions[i].SizeType == LayoutTimeSizeType.Auto) - ? definitions[i].MinSize - : definitions[i].MeasureSize; - } while (--i >= start); - - return (measureSize); - } - - /// - /// Accumulates length type information for given definition's range. - /// - /// Source array of definitions to read values from. - /// Starting index of the range. - /// Number of definitions included in the range. - /// Length type for given range. - private LayoutTimeSizeType GetLengthTypeForRange( - DefinitionBase[] definitions, - int start, - int count) - { - Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); - - LayoutTimeSizeType lengthType = LayoutTimeSizeType.None; - int i = start + count - 1; - - do - { - lengthType |= definitions[i].SizeType; - } while (--i >= start); - - return (lengthType); - } - - /// - /// Distributes min size back to definition array's range. - /// - /// Start of the range. - /// Number of items in the range. - /// Minimum size that should "fit" into the definitions range. - /// Definition array receiving distribution. - /// Size used to resolve percentages. - private void EnsureMinSizeInDefinitionRange( - DefinitionBase[] definitions, - int start, - int count, - double requestedSize, - double percentReferenceSize) - { - Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length); - - // avoid processing when asked to distribute "0" - if (!MathUtilities.IsZero(requestedSize)) - { - DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting - int end = start + count; - int autoDefinitionsCount = 0; - double rangeMinSize = 0; - double rangePreferredSize = 0; - double rangeMaxSize = 0; - double maxMaxSize = 0; // maximum of maximum sizes - - // first accumulate the necessary information: - // a) sum up the sizes in the range; - // b) count the number of auto definitions in the range; - // c) initialize temp array - // d) cache the maximum size into SizeCache - // e) accumulate max of max sizes - for (int i = start; i < end; ++i) - { - double minSize = definitions[i].MinSize; - double preferredSize = definitions[i].PreferredSize; - double maxSize = Math.Max(definitions[i].UserMaxSize, minSize); - - rangeMinSize += minSize; - rangePreferredSize += preferredSize; - rangeMaxSize += maxSize; - - definitions[i].SizeCache = maxSize; - - // sanity check: no matter what, but min size must always be the smaller; - // max size must be the biggest; and preferred should be in between - Debug.Assert(minSize <= preferredSize - && preferredSize <= maxSize - && rangeMinSize <= rangePreferredSize - && rangePreferredSize <= rangeMaxSize); - - if (maxMaxSize < maxSize) maxMaxSize = maxSize; - if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++; - tempDefinitions[i - start] = definitions[i]; - } - - // avoid processing if the range already big enough - if (requestedSize > rangeMinSize) - { - if (requestedSize <= rangePreferredSize) - { - // - // requestedSize fits into preferred size of the range. - // distribute according to the following logic: - // * do not distribute into auto definitions - they should continue to stay "tight"; - // * for all non-auto definitions distribute to equi-size min sizes, without exceeding preferred size. - // - // in order to achieve that, definitions are sorted in a way that all auto definitions - // are first, then definitions follow ascending order with PreferredSize as the key of sorting. - // - double sizeToDistribute; - int i; - - Array.Sort(tempDefinitions, 0, count, _spanPreferredDistributionOrderComparer); - for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) - { - // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); - - // adjust sizeToDistribute value by subtracting auto definition min size - sizeToDistribute -= (tempDefinitions[i].MinSize); - } - - for (; i < count; ++i) - { - // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); - - double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize); - if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); } - sizeToDistribute -= newMinSize; - } - - // sanity check: requested size must all be distributed - Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); - } - else if (requestedSize <= rangeMaxSize) - { - // - // requestedSize bigger than preferred size, but fit into max size of the range. - // distribute according to the following logic: - // * do not distribute into auto definitions, if possible - they should continue to stay "tight"; - // * for all non-auto definitions distribute to euqi-size min sizes, without exceeding max size. - // - // in order to achieve that, definitions are sorted in a way that all non-auto definitions - // are last, then definitions follow ascending order with MaxSize as the key of sorting. - // - double sizeToDistribute; - int i; - - Array.Sort(tempDefinitions, 0, count, _spanMaxDistributionOrderComparer); - for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) - { - // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); - - double preferredSize = tempDefinitions[i].PreferredSize; - double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); - } - - for (; i < count; ++i) - { - // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); - - double preferredSize = tempDefinitions[i].MinSize; - double newMinSize = preferredSize + sizeToDistribute / (count - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); - } - - // sanity check: requested size must all be distributed - Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); - } - else - { - // - // requestedSize bigger than max size of the range. - // distribute according to the following logic: - // * for all definitions distribute to equi-size min sizes. - // - double equalSize = requestedSize / count; - - if (equalSize < maxMaxSize - && !MathUtilities.AreClose(equalSize, maxMaxSize)) - { - // equi-size is less than maximum of maxSizes. - // in this case distribute so that smaller definitions grow faster than - // bigger ones. - double totalRemainingSize = maxMaxSize * count - rangeMaxSize; - double sizeToDistribute = requestedSize - rangeMaxSize; - - // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers - Debug.Assert(!double.IsInfinity(totalRemainingSize) - && !double.IsNaN(totalRemainingSize) - && totalRemainingSize > 0 - && !double.IsInfinity(sizeToDistribute) - && !double.IsNaN(sizeToDistribute) - && sizeToDistribute > 0); - - for (int i = 0; i < count; ++i) - { - double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize; - tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize); - } - } - else - { - // - // equi-size is greater or equal to maximum of max sizes. - // all definitions receive equalSize as their mim sizes. - // - for (int i = 0; i < count; ++i) - { - tempDefinitions[i].UpdateMinSize(equalSize); - } - } - } - } - } - } - - // new implementation as of 4.7. Several improvements: - // 1. Allocate to *-defs hitting their min or max constraints, before allocating - // to other *-defs. A def that hits its min uses more space than its - // proportional share, reducing the space available to everyone else. - // The legacy algorithm deducted this space only from defs processed - // after the min; the new algorithm deducts it proportionally from all - // defs. This avoids the "*-defs exceed available space" problem, - // and other related problems where *-defs don't receive proportional - // allocations even though no constraints are preventing it. - // 2. When multiple defs hit min or max, resolve the one with maximum - // discrepancy (defined below). This avoids discontinuities - small - // change in available space resulting in large change to one def's allocation. - // 3. Correct handling of large *-values, including Infinity. - - /// - /// Resolves Star's for given array of definitions. - /// - /// Array of definitions to resolve stars. - /// All available size. - /// - /// Must initialize LayoutSize for all Star entries in given array of definitions. - /// - private void ResolveStar( - DefinitionBase[] definitions, - double availableSize) - { - int defCount = definitions.Length; - DefinitionBase[] tempDefinitions = TempDefinitions; - int minCount = 0, maxCount = 0; - double takenSize = 0; - double totalStarWeight = 0.0; - int starCount = 0; // number of unresolved *-definitions - double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" - - // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights - double maxStar = 0.0; - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.SizeType == LayoutTimeSizeType.Star) - { - ++starCount; - def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3" - if (def.UserSize.Value > maxStar) - { - maxStar = def.UserSize.Value; - } - } - } - - if (double.IsPositiveInfinity(maxStar)) - { - // negative scale means one or more of the weights was Infinity - scale = -1.0; - } - else if (starCount > 0) - { - // if maxStar * starCount > double.Max, summing all the weights could cause - // floating-point overflow. To avoid that, scale the weights by a factor to keep - // the sum within limits. Choose a power of 2, to preserve precision. - double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0)); - if (power < 0.0) - { - scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia - } - } - - // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights - // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. - // More explanation below... - for (bool runPhase2and3 = true; runPhase2and3;) - { - // Phase 2. Compute total *-weight W and available space S. - // For *-items that have Min or Max constraints, compute the ratios used to decide - // whether proportional space is too big or too small and add the item to the - // corresponding list. (The "min" list is in the first half of tempDefinitions, - // the "max" list in the second half. TempDefinitions has capacity at least - // 2*defCount, so there's room for both lists.) - totalStarWeight = 0.0; - takenSize = 0.0; - minCount = maxCount = 0; - - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - switch (def.SizeType) - { - case (LayoutTimeSizeType.Auto): - takenSize += definitions[i].MinSize; - break; - case (LayoutTimeSizeType.Pixel): - takenSize += def.MeasureSize; - break; - case (LayoutTimeSizeType.Star): - if (def.MeasureSize < 0.0) - { - takenSize += -def.MeasureSize; // already resolved - } - else - { - double starWeight = StarWeight(def, scale); - totalStarWeight += starWeight; - - if (def.MinSize > 0.0) - { - // store ratio w/min in MeasureSize (for now) - tempDefinitions[minCount++] = def; - def.MeasureSize = starWeight / def.MinSize; - } - - double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); - if (!double.IsPositiveInfinity(effectiveMaxSize)) - { - // store ratio w/max in SizeCache (for now) - tempDefinitions[defCount + maxCount++] = def; - def.SizeCache = starWeight / effectiveMaxSize; - } - } - break; - } - } - - // Phase 3. Resolve *-items whose proportional sizes are too big or too small. - int minCountPhase2 = minCount, maxCountPhase2 = maxCount; - double takenStarWeight = 0.0; - double remainingAvailableSize = availableSize - takenSize; - double remainingStarWeight = totalStarWeight - takenStarWeight; - Array.Sort(tempDefinitions, 0, minCount, _minRatioComparer); - Array.Sort(tempDefinitions, defCount, maxCount, _maxRatioComparer); - - while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) - { - // the calculation - // remainingStarWeight = totalStarWeight - takenStarWeight - // is subject to catastrophic cancellation if the two terms are nearly equal, - // which leads to meaningless results. Check for that, and recompute from - // the remaining definitions. [This leads to quadratic behavior in really - // pathological cases - but they'd never arise in practice.] - const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate - if (remainingStarWeight < totalStarWeight * starFactor) - { - takenStarWeight = 0.0; - totalStarWeight = 0.0; - - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - if (def.SizeType == LayoutTimeSizeType.Star && def.MeasureSize > 0.0) - { - totalStarWeight += StarWeight(def, scale); - } - } - - remainingStarWeight = totalStarWeight - takenStarWeight; - } - - double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : double.PositiveInfinity; - double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; - - // choose the def with larger ratio to the current proportion ("max discrepancy") - double proportion = remainingStarWeight / remainingAvailableSize; - bool? chooseMin = Choose(minRatio, maxRatio, proportion); - - // if no def was chosen, advance to phase 4; the current proportion doesn't - // conflict with any min or max values. - if (!(chooseMin.HasValue)) - { - break; - } - - // get the chosen definition and its resolved size - DefinitionBase resolvedDef; - double resolvedSize; - if (chooseMin == true) - { - resolvedDef = tempDefinitions[minCount - 1]; - resolvedSize = resolvedDef.MinSize; - --minCount; - } - else - { - resolvedDef = tempDefinitions[defCount + maxCount - 1]; - resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); - --maxCount; - } - - // resolve the chosen def, deduct its contributions from W and S. - // Defs resolved in phase 3 are marked by storing the negative of their resolved - // size in MeasureSize, to distinguish them from a pending def. - takenSize += resolvedSize; - resolvedDef.MeasureSize = -resolvedSize; - takenStarWeight += StarWeight(resolvedDef, scale); - --starCount; - - remainingAvailableSize = availableSize - takenSize; - remainingStarWeight = totalStarWeight - takenStarWeight; - - // advance to the next candidate defs, removing ones that have been resolved. - // Both counts are advanced, as a def might appear in both lists. - while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0) - { - --minCount; - tempDefinitions[minCount] = null; - } - while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0) - { - --maxCount; - tempDefinitions[defCount + maxCount] = null; - } - } - - // decide whether to run Phase2 and Phase3 again. There are 3 cases: - // 1. There is space available, and *-defs remaining. This is the - // normal case - move on to Phase 4 to allocate the remaining - // space proportionally to the remaining *-defs. - // 2. There is space available, but no *-defs. This implies at least one - // def was resolved as 'max', taking less space than its proportion. - // If there are also 'min' defs, reconsider them - we can give - // them more space. If not, all the *-defs are 'max', so there's - // no way to use all the available space. - // 3. We allocated too much space. This implies at least one def was - // resolved as 'min'. If there are also 'max' defs, reconsider - // them, otherwise the over-allocation is an inevitable consequence - // of the given min constraints. - // Note that if we return to Phase2, at least one *-def will have been - // resolved. This guarantees we don't run Phase2+3 infinitely often. - runPhase2and3 = false; - if (starCount == 0 && takenSize < availableSize) - { - // if no *-defs remain and we haven't allocated all the space, reconsider the defs - // resolved as 'min'. Their allocation can be increased to make up the gap. - for (int i = minCount; i < minCountPhase2; ++i) - { - DefinitionBase def = tempDefinitions[i]; - if (def != null) - { - def.MeasureSize = 1.0; // mark as 'not yet resolved' - ++starCount; - runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 - } - } - } - - if (takenSize > availableSize) - { - // if we've allocated too much space, reconsider the defs - // resolved as 'max'. Their allocation can be decreased to make up the gap. - for (int i = maxCount; i < maxCountPhase2; ++i) - { - DefinitionBase def = tempDefinitions[defCount + i]; - if (def != null) - { - def.MeasureSize = 1.0; // mark as 'not yet resolved' - ++starCount; - runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 - } - } - } - } - - // Phase 4. Resolve the remaining defs proportionally. - starCount = 0; - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.SizeType == LayoutTimeSizeType.Star) - { - if (def.MeasureSize < 0.0) - { - // this def was resolved in phase 3 - fix up its measure size - def.MeasureSize = -def.MeasureSize; - } - else - { - // this def needs resolution, add it to the list, sorted by *-weight - tempDefinitions[starCount++] = def; - def.MeasureSize = StarWeight(def, scale); - } - } - } - - if (starCount > 0) - { - Array.Sort(tempDefinitions, 0, starCount, _starWeightComparer); - - // compute the partial sums of *-weight, in increasing order of weight - // for minimal loss of precision. - totalStarWeight = 0.0; - for (int i = 0; i < starCount; ++i) - { - DefinitionBase def = tempDefinitions[i]; - totalStarWeight += def.MeasureSize; - def.SizeCache = totalStarWeight; - } - - // resolve the defs, in decreasing order of weight - for (int i = starCount - 1; i >= 0; --i) - { - DefinitionBase def = tempDefinitions[i]; - double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; - - // min and max should have no effect by now, but just in case... - resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); - resolvedSize = Math.Max(def.MinSize, resolvedSize); - - def.MeasureSize = resolvedSize; - takenSize += resolvedSize; - } - } - } - - /// - /// Calculates desired size for given array of definitions. - /// - /// Array of definitions to use for calculations. - /// Desired size. - private double CalculateDesiredSize( - DefinitionBase[] definitions) - { - double desiredSize = 0; - - for (int i = 0; i < definitions.Length; ++i) - { - desiredSize += definitions[i].MinSize; - } - - return (desiredSize); - } - - /// - /// Calculates and sets final size for all definitions in the given array. - /// - /// Array of definitions to process. - /// Final size to lay out to. - /// True if sizing row definitions, false for columns - private void SetFinalSize( - DefinitionBase[] definitions, - double finalSize, - bool columns) - { - int defCount = definitions.Length; - int[] definitionIndices = DefinitionIndices; - int minCount = 0, maxCount = 0; - double takenSize = 0.0; - double totalStarWeight = 0.0; - int starCount = 0; // number of unresolved *-definitions - double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" - - // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights - double maxStar = 0.0; - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.UserSize.IsStar) - { - ++starCount; - def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3" - if (def.UserSize.Value > maxStar) - { - maxStar = def.UserSize.Value; - } - } - } - - if (double.IsPositiveInfinity(maxStar)) - { - // negative scale means one or more of the weights was Infinity - scale = -1.0; - } - else if (starCount > 0) - { - // if maxStar * starCount > double.Max, summing all the weights could cause - // floating-point overflow. To avoid that, scale the weights by a factor to keep - // the sum within limits. Choose a power of 2, to preserve precision. - double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0)); - if (power < 0.0) - { - scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia - } - } - - - // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights - // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. - // More explanation below... - for (bool runPhase2and3 = true; runPhase2and3;) - { - // Phase 2. Compute total *-weight W and available space S. - // For *-items that have Min or Max constraints, compute the ratios used to decide - // whether proportional space is too big or too small and add the item to the - // corresponding list. (The "min" list is in the first half of definitionIndices, - // the "max" list in the second half. DefinitionIndices has capacity at least - // 2*defCount, so there's room for both lists.) - totalStarWeight = 0.0; - takenSize = 0.0; - minCount = maxCount = 0; - - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.UserSize.IsStar) - { - // Debug.Assert(!def.IsShared, "*-defs cannot be shared"); - - if (def.MeasureSize < 0.0) - { - takenSize += -def.MeasureSize; // already resolved - } - else - { - double starWeight = StarWeight(def, scale); - totalStarWeight += starWeight; - - if (def.MinSize > 0.0) - { - // store ratio w/min in MeasureSize (for now) - definitionIndices[minCount++] = i; - def.MeasureSize = starWeight / def.MinSize; - } - - double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); - if (!double.IsPositiveInfinity(effectiveMaxSize)) - { - // store ratio w/max in SizeCache (for now) - definitionIndices[defCount + maxCount++] = i; - def.SizeCache = starWeight / effectiveMaxSize; - } - } - } - else - { - double userSize = 0; - - switch (def.UserSize.GridUnitType) - { - case (GridUnitType.Pixel): - userSize = def.UserSize.Value; - break; - - case (GridUnitType.Auto): - userSize = def.MinSize; - break; - } - - double userMaxSize; - - // if (def.IsShared) - // { - // // overriding userMaxSize effectively prevents squishy-ness. - // // this is a "solution" to avoid shared definitions from been sized to - // // different final size at arrange time, if / when different grids receive - // // different final sizes. - // userMaxSize = userSize; - // } - // else - // { - userMaxSize = def.UserMaxSize; - // } - - def.SizeCache = Math.Max(def.MinSize, Math.Min(userSize, userMaxSize)); - takenSize += def.SizeCache; - } - } - - // Phase 3. Resolve *-items whose proportional sizes are too big or too small. - int minCountPhase2 = minCount, maxCountPhase2 = maxCount; - double takenStarWeight = 0.0; - double remainingAvailableSize = finalSize - takenSize; - double remainingStarWeight = totalStarWeight - takenStarWeight; - - MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions); - Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer); - MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions); - Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer); - - while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) - { - // the calculation - // remainingStarWeight = totalStarWeight - takenStarWeight - // is subject to catastrophic cancellation if the two terms are nearly equal, - // which leads to meaningless results. Check for that, and recompute from - // the remaining definitions. [This leads to quadratic behavior in really - // pathological cases - but they'd never arise in practice.] - const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate - if (remainingStarWeight < totalStarWeight * starFactor) - { - takenStarWeight = 0.0; - totalStarWeight = 0.0; - - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - if (def.UserSize.IsStar && def.MeasureSize > 0.0) - { - totalStarWeight += StarWeight(def, scale); - } - } - - remainingStarWeight = totalStarWeight - takenStarWeight; - } - - double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : double.PositiveInfinity; - double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0; - - // choose the def with larger ratio to the current proportion ("max discrepancy") - double proportion = remainingStarWeight / remainingAvailableSize; - bool? chooseMin = Choose(minRatio, maxRatio, proportion); - - // if no def was chosen, advance to phase 4; the current proportion doesn't - // conflict with any min or max values. - if (!(chooseMin.HasValue)) - { - break; - } - - // get the chosen definition and its resolved size - int resolvedIndex; - DefinitionBase resolvedDef; - double resolvedSize; - if (chooseMin == true) - { - resolvedIndex = definitionIndices[minCount - 1]; - resolvedDef = definitions[resolvedIndex]; - resolvedSize = resolvedDef.MinSize; - --minCount; - } - else - { - resolvedIndex = definitionIndices[defCount + maxCount - 1]; - resolvedDef = definitions[resolvedIndex]; - resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); - --maxCount; - } - - // resolve the chosen def, deduct its contributions from W and S. - // Defs resolved in phase 3 are marked by storing the negative of their resolved - // size in MeasureSize, to distinguish them from a pending def. - takenSize += resolvedSize; - resolvedDef.MeasureSize = -resolvedSize; - takenStarWeight += StarWeight(resolvedDef, scale); - --starCount; - - remainingAvailableSize = finalSize - takenSize; - remainingStarWeight = totalStarWeight - takenStarWeight; - - // advance to the next candidate defs, removing ones that have been resolved. - // Both counts are advanced, as a def might appear in both lists. - while (minCount > 0 && definitions[definitionIndices[minCount - 1]].MeasureSize < 0.0) - { - --minCount; - definitionIndices[minCount] = -1; - } - while (maxCount > 0 && definitions[definitionIndices[defCount + maxCount - 1]].MeasureSize < 0.0) - { - --maxCount; - definitionIndices[defCount + maxCount] = -1; - } - } - - // decide whether to run Phase2 and Phase3 again. There are 3 cases: - // 1. There is space available, and *-defs remaining. This is the - // normal case - move on to Phase 4 to allocate the remaining - // space proportionally to the remaining *-defs. - // 2. There is space available, but no *-defs. This implies at least one - // def was resolved as 'max', taking less space than its proportion. - // If there are also 'min' defs, reconsider them - we can give - // them more space. If not, all the *-defs are 'max', so there's - // no way to use all the available space. - // 3. We allocated too much space. This implies at least one def was - // resolved as 'min'. If there are also 'max' defs, reconsider - // them, otherwise the over-allocation is an inevitable consequence - // of the given min constraints. - // Note that if we return to Phase2, at least one *-def will have been - // resolved. This guarantees we don't run Phase2+3 infinitely often. - runPhase2and3 = false; - if (starCount == 0 && takenSize < finalSize) - { - // if no *-defs remain and we haven't allocated all the space, reconsider the defs - // resolved as 'min'. Their allocation can be increased to make up the gap. - for (int i = minCount; i < minCountPhase2; ++i) - { - if (definitionIndices[i] >= 0) - { - DefinitionBase def = definitions[definitionIndices[i]]; - def.MeasureSize = 1.0; // mark as 'not yet resolved' - ++starCount; - runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 - } - } - } - - if (takenSize > finalSize) - { - // if we've allocated too much space, reconsider the defs - // resolved as 'max'. Their allocation can be decreased to make up the gap. - for (int i = maxCount; i < maxCountPhase2; ++i) - { - if (definitionIndices[defCount + i] >= 0) - { - DefinitionBase def = definitions[definitionIndices[defCount + i]]; - def.MeasureSize = 1.0; // mark as 'not yet resolved' - ++starCount; - runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 - } - } - } - } - - // Phase 4. Resolve the remaining defs proportionally. - starCount = 0; - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.UserSize.IsStar) - { - if (def.MeasureSize < 0.0) - { - // this def was resolved in phase 3 - fix up its size - def.SizeCache = -def.MeasureSize; - } - else - { - // this def needs resolution, add it to the list, sorted by *-weight - definitionIndices[starCount++] = i; - def.MeasureSize = StarWeight(def, scale); - } - } - } - - if (starCount > 0) - { - StarWeightIndexComparer starWeightIndexComparer = new StarWeightIndexComparer(definitions); - Array.Sort(definitionIndices, 0, starCount, starWeightIndexComparer); - - // compute the partial sums of *-weight, in increasing order of weight - // for minimal loss of precision. - totalStarWeight = 0.0; - for (int i = 0; i < starCount; ++i) - { - DefinitionBase def = definitions[definitionIndices[i]]; - totalStarWeight += def.MeasureSize; - def.SizeCache = totalStarWeight; - } - - // resolve the defs, in decreasing order of weight. - for (int i = starCount - 1; i >= 0; --i) - { - DefinitionBase def = definitions[definitionIndices[i]]; - double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(finalSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; - - // min and max should have no effect by now, but just in case... - resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); - resolvedSize = Math.Max(def.MinSize, resolvedSize); - - // Use the raw (unrounded) sizes to update takenSize, so that - // proportions are computed in the same terms as in phase 3; - // this avoids errors arising from min/max constraints. - takenSize += resolvedSize; - def.SizeCache = resolvedSize; - } - } - - // Phase 5. Apply layout rounding. We do this after fully allocating - // unrounded sizes, to avoid breaking assumptions in the previous phases - if (UseLayoutRounding) - { - var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; - - double[] roundingErrors = RoundingErrors; - double roundedTakenSize = 0.0; - - // round each of the allocated sizes, keeping track of the deltas - for (int i = 0; i < definitions.Length; ++i) - { - DefinitionBase def = definitions[i]; - double roundedSize = RoundLayoutValue(def.SizeCache, dpi); - roundingErrors[i] = (roundedSize - def.SizeCache); - def.SizeCache = roundedSize; - roundedTakenSize += roundedSize; - } - - // The total allocation might differ from finalSize due to rounding - // effects. Tweak the allocations accordingly. - - // Theoretical and historical note. The problem at hand - allocating - // space to columns (or rows) with *-weights, min and max constraints, - // and layout rounding - has a long history. Especially the special - // case of 50 columns with min=1 and available space=435 - allocating - // seats in the U.S. House of Representatives to the 50 states in - // proportion to their population. There are numerous algorithms - // and papers dating back to the 1700's, including the book: - // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982. - // - // One surprising result of all this research is that *any* algorithm - // will suffer from one or more undesirable features such as the - // "population paradox" or the "Alabama paradox", where (to use our terminology) - // increasing the available space by one pixel might actually decrease - // the space allocated to a given column, or increasing the weight of - // a column might decrease its allocation. This is worth knowing - // in case someone complains about this behavior; it's not a bug so - // much as something inherent to the problem. Cite the book mentioned - // above or one of the 100s of references, and resolve as WontFix. - // - // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer) - // each being allocated a large number of pixels (~50 or greater), and - // people don't even notice the kind of 1-pixel anomolies that are - // theoretically inevitable, or don't care if they do. At least they shouldn't - // care - no one should be using the results WPF's grid layout to make - // quantitative decisions; its job is to produce a reasonable display, not - // to allocate seats in Congress. - // - // Our algorithm is more susceptible to paradox than the one currently - // used for Congressional allocation ("Huntington-Hill" algorithm), but - // it is faster to run: O(N log N) vs. O(S * N), where N=number of - // definitions, S = number of available pixels. And it produces - // adequate results in practice, as mentioned above. - // - // To reiterate one point: all this only applies when layout rounding - // is in effect. When fractional sizes are allowed, the algorithm - // behaves as well as possible, subject to the min/max constraints - // and precision of floating-point computation. (However, the resulting - // display is subject to anti-aliasing problems. TANSTAAFL.) - - if (!MathUtilities.AreClose(roundedTakenSize, finalSize)) - { - // Compute deltas - for (int i = 0; i < definitions.Length; ++i) - { - definitionIndices[i] = i; - } - - // Sort rounding errors - RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); - Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); - double adjustedSize = roundedTakenSize; - double dpiIncrement = 1.0 / dpi; - - if (roundedTakenSize > finalSize) - { - int i = definitions.Length - 1; - while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0) - { - DefinitionBase definition = definitions[definitionIndices[i]]; - double final = definition.SizeCache - dpiIncrement; - final = Math.Max(final, definition.MinSize); - if (final < definition.SizeCache) - { - adjustedSize -= dpiIncrement; - } - definition.SizeCache = final; - i--; - } - } - else if (roundedTakenSize < finalSize) - { - int i = 0; - while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Length) - { - DefinitionBase definition = definitions[definitionIndices[i]]; - double final = definition.SizeCache + dpiIncrement; - final = Math.Max(final, definition.MinSize); - if (final > definition.SizeCache) - { - adjustedSize += dpiIncrement; - } - definition.SizeCache = final; - i++; - } - } - } - } - - // Phase 6. Compute final offsets - definitions[0].FinalOffset = 0.0; - for (int i = 0; i < definitions.Length; ++i) - { - definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; - } - } - - // Choose the ratio with maximum discrepancy from the current proportion. - // Returns: - // true if proportion fails a min constraint but not a max, or - // if the min constraint has higher discrepancy - // false if proportion fails a max constraint but not a min, or - // if the max constraint has higher discrepancy - // null if proportion doesn't fail a min or max constraint - // The discrepancy is the ratio of the proportion to the max- or min-ratio. - // When both ratios hit the constraint, minRatio < proportion < maxRatio, - // and the minRatio has higher discrepancy if - // (proportion / minRatio) > (maxRatio / proportion) - private static bool? Choose(double minRatio, double maxRatio, double proportion) - { - if (minRatio < proportion) - { - if (maxRatio > proportion) - { - // compare proportion/minRatio : maxRatio/proportion, but - // do it carefully to avoid floating-point overflow or underflow - // and divide-by-0. - double minPower = Math.Floor(Math.Log(minRatio, 2.0)); - double maxPower = Math.Floor(Math.Log(maxRatio, 2.0)); - double f = Math.Pow(2.0, Math.Floor((minPower + maxPower) / 2.0)); - if ((proportion / f) * (proportion / f) > (minRatio / f) * (maxRatio / f)) - { - return true; - } - else - { - return false; - } - } - else - { - return true; - } - } - else if (maxRatio > proportion) - { - return false; - } - - return null; - } - - /// - /// Sorts row/column indices by rounding error if layout rounding is applied. - /// - /// Index, rounding error pair - /// Index, rounding error pair - /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise - private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y) - { - if (x.Value < y.Value) - { - return -1; - } - else if (x.Value > y.Value) - { - return 1; - } - return 0; - } - - /// - /// Calculates final (aka arrange) size for given range. - /// - /// Array of definitions to process. - /// Start of the range. - /// Number of items in the range. - /// Final size. - private double GetFinalSizeForRange( - DefinitionBase[] definitions, - int start, - int count) - { - double size = 0; - int i = start + count - 1; - - do - { - size += definitions[i].SizeCache; - } while (--i >= start); - - return (size); - } - - /// - /// Clears dirty state for the grid and its columns / rows - /// - private void SetValid() - { - if (IsTrivialGrid) - { - if (_tempDefinitions != null) - { - // TempDefinitions has to be cleared to avoid "memory leaks" - Array.Clear(_tempDefinitions, 0, Math.Max(_definitionsU.Length, _definitionsV.Length)); - _tempDefinitions = null; - } - } - } - - - /// - /// Synchronized ShowGridLines property with the state of the grid's visual collection - /// by adding / removing GridLinesRenderer visual. - /// Returns a reference to GridLinesRenderer visual or null. - /// - private GridLinesRenderer EnsureGridLinesRenderer() - { - // - // synchronize the state - // - if (ShowGridLines && (_gridLinesRenderer == null)) - { - _gridLinesRenderer = new GridLinesRenderer(); - this.VisualChildren.Add(_gridLinesRenderer); - } - - if ((!ShowGridLines) && (_gridLinesRenderer != null)) - { - this.VisualChildren.Remove(_gridLinesRenderer); - _gridLinesRenderer = null; - } - - return (_gridLinesRenderer); - } - - private double RoundLayoutValue(double value, double dpiScale) - { - double newValue; - - // If DPI == 1, don't use DPI-aware rounding. - if (!MathUtilities.AreClose(dpiScale, 1.0)) - { - newValue = Math.Round(value * dpiScale) / dpiScale; - // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. - if (double.IsNaN(newValue) || - double.IsInfinity(newValue) || - MathUtilities.AreClose(newValue, double.MaxValue)) - { - newValue = value; - } - } - else - { - newValue = Math.Round(value); - } - - return newValue; - } - - - private static int ValidateColumn(AvaloniaObject o, int value) - { - if (value < 0) - { - throw new ArgumentException("Invalid Grid.Column value."); - } - - return value; - } - - private static int ValidateRow(AvaloniaObject o, int value) - { - if (value < 0) - { - throw new ArgumentException("Invalid Grid.Row value."); - } - - return value; - } - - private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) - { - if (!grid.IsTrivialGrid) // trivial grid is 1 by 1. there is no grid lines anyway - { - grid.Invalidate(); - } - } - - /// - /// Helper for Comparer methods. - /// - /// - /// true if one or both of x and y are null, in which case result holds - /// the relative sort order. - /// - private static bool CompareNullRefs(object x, object y, out int result) - { - result = 2; - - if (x == null) - { - if (y == null) - { - result = 0; - } - else - { - result = -1; - } - } - else - { - if (y == null) - { - result = 1; - } - } - - return (result != 2); - } - - /// - /// Helper accessor to layout time array of definitions. - /// - private DefinitionBase[] TempDefinitions - { - get - { - int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length) * 2; - - if (_tempDefinitions == null - || _tempDefinitions.Length < requiredLength) - { - WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(_tempDefinitionsDataSlot); - if (tempDefinitionsWeakRef == null) - { - _tempDefinitions = new DefinitionBase[requiredLength]; - Thread.SetData(_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions)); - } - else - { - _tempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; - if (_tempDefinitions == null - || _tempDefinitions.Length < requiredLength) - { - _tempDefinitions = new DefinitionBase[requiredLength]; - tempDefinitionsWeakRef.Target = _tempDefinitions; - } - } - } - return (_tempDefinitions); - } - } - - /// - /// Helper accessor to definition indices. - /// - private int[] DefinitionIndices - { - get - { - int requiredLength = Math.Max(Math.Max(_definitionsU.Length, _definitionsV.Length), 1) * 2; - - if (_definitionIndices == null || _definitionIndices.Length < requiredLength) - { - _definitionIndices = new int[requiredLength]; - } - - return _definitionIndices; - } - } - - /// - /// Helper accessor to rounding errors. - /// - private double[] RoundingErrors - { - get - { - int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length); - - if (_roundingErrors == null && requiredLength == 0) - { - _roundingErrors = new double[1]; - } - else if (_roundingErrors == null || _roundingErrors.Length < requiredLength) - { - _roundingErrors = new double[requiredLength]; - } - return _roundingErrors; - } - } - - /// - /// Returns *-weight, adjusted for scale computed during Phase 1 - /// - static double StarWeight(DefinitionBase def, double scale) - { - if (scale < 0.0) - { - // if one of the *-weights is Infinity, adjust the weights by mapping - // Infinty to 1.0 and everything else to 0.0: the infinite items share the - // available space equally, everyone else gets nothing. - return (double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0; - } - else - { - return def.UserSize.Value * scale; - } - } - - /// - /// LayoutTimeSizeType is used internally and reflects layout-time size type. - /// - [System.Flags] - internal enum LayoutTimeSizeType : byte - { - None = 0x00, - Pixel = 0x01, - Auto = 0x02, - Star = 0x04, - } - - /// - /// CellCache stored calculated values of - /// 1. attached cell positioning properties; - /// 2. size type; - /// 3. index of a next cell in the group; - /// - private struct CellCache - { - internal int ColumnIndex; - internal int RowIndex; - internal int ColumnSpan; - internal int RowSpan; - internal LayoutTimeSizeType SizeTypeU; - internal LayoutTimeSizeType SizeTypeV; - internal int Next; - internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } } - internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } } - internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } } - internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } } - } - - /// - /// Helper class for representing a key for a span in hashtable. - /// - private class SpanKey - { - /// - /// Constructor. - /// - /// Starting index of the span. - /// Span count. - /// true for columns; false for rows. - internal SpanKey(int start, int count, bool u) - { - _start = start; - _count = count; - _u = u; - } - - /// - /// - /// - public override int GetHashCode() - { - int hash = (_start ^ (_count << 2)); - - if (_u) hash &= 0x7ffffff; - else hash |= 0x8000000; - - return (hash); - } - - /// - /// - /// - public override bool Equals(object obj) - { - SpanKey sk = obj as SpanKey; - return (sk != null - && sk._start == _start - && sk._count == _count - && sk._u == _u); - } - - /// - /// Returns start index of the span. - /// - internal int Start { get { return (_start); } } - - /// - /// Returns span count. - /// - internal int Count { get { return (_count); } } - - /// - /// Returns true if this is a column span. - /// false if this is a row span. - /// - internal bool U { get { return (_u); } } - - private int _start; - private int _count; - private bool _u; - } - - /// - /// SpanPreferredDistributionOrderComparer. - /// - private class SpanPreferredDistributionOrderComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - if (definitionX.UserSize.IsAuto) - { - if (definitionY.UserSize.IsAuto) - { - result = definitionX.MinSize.CompareTo(definitionY.MinSize); - } - else - { - result = -1; - } - } - else - { - if (definitionY.UserSize.IsAuto) - { - result = +1; - } - else - { - result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize); - } - } - } - - return result; - } - } - - /// - /// SpanMaxDistributionOrderComparer. - /// - private class SpanMaxDistributionOrderComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - if (definitionX.UserSize.IsAuto) - { - if (definitionY.UserSize.IsAuto) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - else - { - result = +1; - } - } - else - { - if (definitionY.UserSize.IsAuto) - { - result = -1; - } - else - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - } - } - - return result; - } - } - - /// - /// RoundingErrorIndexComparer. - /// - private class RoundingErrorIndexComparer : IComparer - { - private readonly double[] errors; - - internal RoundingErrorIndexComparer(double[] errors) - { - Contract.Requires(errors != null); - this.errors = errors; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - int result; - - if (!CompareNullRefs(indexX, indexY, out result)) - { - double errorX = errors[indexX.Value]; - double errorY = errors[indexY.Value]; - result = errorX.CompareTo(errorY); - } - - return result; - } - } - - /// - /// MinRatioComparer. - /// Sort by w/min (stored in MeasureSize), descending. - /// We query the list from the back, i.e. in ascending order of w/min. - /// - private class MinRatioComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionY, definitionX, out result)) - { - result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); - } - - return result; - } - } - - /// - /// MaxRatioComparer. - /// Sort by w/max (stored in SizeCache), ascending. - /// We query the list from the back, i.e. in descending order of w/max. - /// - private class MaxRatioComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - - return result; - } - } - - /// - /// StarWeightComparer. - /// Sort by *-weight (stored in MeasureSize), ascending. - /// - private class StarWeightComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); - } - - return result; - } - } - - /// - /// MinRatioIndexComparer. - /// - private class MinRatioIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal MinRatioIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!CompareNullRefs(definitionY, definitionX, out result)) - { - result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); - } - - return result; - } - } - - /// - /// MaxRatioIndexComparer. - /// - private class MaxRatioIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal MaxRatioIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - - return result; - } - } - - /// - /// MaxRatioIndexComparer. - /// - private class StarWeightIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal StarWeightIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); - } - - return result; - } - } - - /// - /// Helper to render grid lines. - /// - private class GridLinesRenderer : Control - { - /// - /// Static initialization - /// - static GridLinesRenderer() - { - var oddDashArray = new List(); - oddDashArray.Add(c_dashLength); - oddDashArray.Add(c_dashLength); - var ds1 = new DashStyle(oddDashArray, 0); - s_oddDashPen = new Pen(Brushes.Blue, - c_penWidth, - lineCap: PenLineCap.Flat, - dashStyle: ds1); - - var evenDashArray = new List(); - evenDashArray.Add(c_dashLength); - evenDashArray.Add(c_dashLength); - var ds2 = new DashStyle(evenDashArray, 0); - s_evenDashPen = new Pen(Brushes.Yellow, - c_penWidth, - lineCap: PenLineCap.Flat, - dashStyle: ds2); - } - - /// - /// UpdateRenderBounds. - /// - public override void Render(DrawingContext drawingContext) - { - var grid = this.GetVisualParent(); - - if (grid == null - || !grid.ShowGridLines - || grid.IsTrivialGrid) - { - return; - } - - for (int i = 1; i < grid.ColumnDefinitions.Count; ++i) - { - DrawGridLine( - drawingContext, - grid.ColumnDefinitions[i].FinalOffset, 0.0, - grid.ColumnDefinitions[i].FinalOffset, lastArrangeSize.Height); - } - - for (int i = 1; i < grid.RowDefinitions.Count; ++i) - { - DrawGridLine( - drawingContext, - 0.0, grid.RowDefinitions[i].FinalOffset, - lastArrangeSize.Width, grid.RowDefinitions[i].FinalOffset); - } - } - - /// - /// Draw single hi-contrast line. - /// - private static void DrawGridLine( - DrawingContext drawingContext, - double startX, - double startY, - double endX, - double endY) - { - var start = new Point(startX, startY); - var end = new Point(endX, endY); - drawingContext.DrawLine(s_oddDashPen, start, end); - drawingContext.DrawLine(s_evenDashPen, start, end); - } - - internal void UpdateRenderBounds(Size arrangeSize) - { - lastArrangeSize = arrangeSize; - this.InvalidateVisual(); - } - - private static Size lastArrangeSize; - private const double c_dashLength = 4.0; // - private const double c_penWidth = 1.0; // - private static readonly Pen s_oddDashPen; // first pen to draw dash - private static readonly Pen s_evenDashPen; // second pen to draw dash - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs deleted file mode 100644 index 7704228a4e..0000000000 --- a/src/Avalonia.Controls/Utils/GridLayout.cs +++ /dev/null @@ -1,705 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Runtime.CompilerServices; -using Avalonia.Layout; -using JetBrains.Annotations; - -namespace Avalonia.Controls.Utils -{ - /// - /// Contains algorithms that can help to measure and arrange a Grid. - /// - internal class GridLayout - { - /// - /// Initialize a new instance from the column definitions. - /// The instance doesn't care about whether the definitions are rows or columns. - /// It will not calculate the column or row differently. - /// - internal GridLayout([NotNull] ColumnDefinitions columns) - { - if (columns == null) throw new ArgumentNullException(nameof(columns)); - _conventions = columns.Count == 0 - ? new List { new LengthConvention() } - : columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList(); - } - - /// - /// Initialize a new instance from the row definitions. - /// The instance doesn't care about whether the definitions are rows or columns. - /// It will not calculate the column or row differently. - /// - internal GridLayout([NotNull] RowDefinitions rows) - { - if (rows == null) throw new ArgumentNullException(nameof(rows)); - _conventions = rows.Count == 0 - ? new List { new LengthConvention() } - : rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList(); - } - - /// - /// Gets the layout tolerance. If any length offset is less than this value, we will treat them the same. - /// - private const double LayoutTolerance = 1.0 / 256.0; - - /// - /// Gets all the length conventions that come from column/row definitions. - /// These conventions provide cell limitations, such as the expected pixel length, the min/max pixel length and the * count. - /// - [NotNull] - private readonly List _conventions; - - /// - /// Gets all the length conventions that come from the grid children. - /// - [NotNull] - private readonly List _additionalConventions = - new List(); - - /// - /// Appending these elements into the convention list helps lay them out according to their desired sizes. - /// - /// Some elements are not only in a single grid cell, they have one or more column/row spans, - /// and these elements may affect the grid layout especially the measuring procedure. - /// Append these elements into the convention list can help to layout them correctly through - /// their desired size. Only a small subset of children need to be measured before layout starts - /// and they will be called via the callback. - /// - /// The grid children type. - /// - /// Contains the safe column/row index and its span. - /// Notice that we will not verify whether the range is in the column/row count, - /// so you should get the safe column/row info first. - /// - /// - /// This callback will be called if the thinks that a child should be - /// measured first. Usually, these are the children that have the * or Auto length. - /// - internal void AppendMeasureConventions([NotNull] IDictionary source, - [NotNull] Func getDesiredLength) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (getDesiredLength == null) throw new ArgumentNullException(nameof(getDesiredLength)); - - // M1/7. Find all the Auto and * length columns/rows. (M1/7 means the 1st procedure of measurement.) - // Only these columns/rows' layout can be affected by the child desired size. - // - // Find all columns/rows that have Auto or * length. We'll measure the children in advance. - // Only these kind of columns/rows will affect the Grid layout. - // Please note: - // - If the column / row has Auto length, the Grid.DesiredSize and the column width - // will be affected by the child's desired size. - // - If the column / row has* length, the Grid.DesiredSize will be affected by the - // child's desired size but the column width not. - - // +-----------------------------------------------------------+ - // | * | A | * | P | A | * | P | * | * | - // +-----------------------------------------------------------+ - // _conventions: | min | max | | | min | | min max | max | - // _additionalC: |<- desired ->| |< desired >| - // _additionalC: |< desired >| |<- desired ->| - - // 寻找所有行列范围中包含 Auto 和 * 的元素,使用全部可用尺寸提前测量。 - // 因为只有这部分元素的布局才会被 Grid 的子元素尺寸影响。 - // 请注意: - // - Auto 长度的行列必定会受到子元素布局影响,会影响到行列的布局长度和 Grid 本身的 DesiredSize; - // - 而对于 * 长度,只有 Grid.DesiredSize 会受到子元素布局影响,而行列长度不会受影响。 - - // Find all the Auto and * length columns/rows. - var found = new Dictionary(); - for (var i = 0; i < _conventions.Count; i++) - { - var index = i; - var convention = _conventions[index]; - if (convention.Length.IsAuto || convention.Length.IsStar) - { - foreach (var pair in source.Where(x => - x.Value.index <= index && index < x.Value.index + x.Value.span)) - { - found[pair.Key] = pair.Value; - } - } - } - - // Append these layout into the additional convention list. - foreach (var pair in found) - { - var t = pair.Key; - var (index, span) = pair.Value; - var desiredLength = getDesiredLength(t); - if (Math.Abs(desiredLength) > LayoutTolerance) - { - _additionalConventions.Add(new AdditionalLengthConvention(index, span, desiredLength)); - } - } - } - - /// - /// Run measure procedure according to the and gets the . - /// - /// - /// The container length. Usually, it is the constraint of the method. - /// - /// - /// Overriding conventions that allows the algorithm to handle external inputa - /// - /// - /// The measured result that containing the desired size and all the column/row lengths. - /// - [NotNull, Pure] - internal MeasureResult Measure(double containerLength, IReadOnlyList conventions = null) - { - // Prepare all the variables that this method needs to use. - conventions = conventions ?? _conventions.Select(x => x.Clone()).ToList(); - var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value); - var aggregatedLength = 0.0; - double starUnitLength; - - // M2/7. Aggregate all the pixel lengths. Then we can get the remaining length by `containerLength - aggregatedLength`. - // We mark the aggregated length as "fix" because we can completely determine their values. Same as below. - // - // +-----------------------------------------------------------+ - // | * | A | * | P | A | * | P | * | * | - // +-----------------------------------------------------------+ - // |#fix#| |#fix#| - // - // 将全部的固定像素长度的行列长度累加。这样,containerLength - aggregatedLength 便能得到剩余长度。 - // 我们会将所有能够确定下长度的行列标记为 fix。下同。 - // 请注意: - // - 我们并没有直接从 containerLength 一直减下去,而是使用 aggregatedLength 进行累加,是因为无穷大相减得到的是 NaN,不利于后续计算。 - - aggregatedLength += conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value); - - // M3/7. Fix all the * lengths that have reached the minimum. - // - // +-----------------------------------------------------------+ - // | * | A | * | P | A | * | P | * | * | - // +-----------------------------------------------------------+ - // | min | max | | | min | | min max | max | - // | fix | |#fix#| fix | - - var shouldTestStarMin = true; - while (shouldTestStarMin) - { - // Calculate the unit * length to estimate the length of each column/row that has * length. - // Under this estimated length, check if there is a minimum value that has a length less than its constraint. - // If there is such a *, then fix the size of this cell, and then loop it again until there is no * that can be constrained by the minimum value. - // - // 计算单位 * 的长度,以便预估出每一个 * 行列的长度。 - // 在此预估的长度下,从前往后寻找是否存在某个 * 长度已经小于其约束的最小值。 - // 如果发现存在这样的 *,那么将此单元格的尺寸固定下来(Fix),然后循环重来,直至再也没有能被最小值约束的 *。 - var @fixed = false; - starUnitLength = (containerLength - aggregatedLength) / starCount; - foreach (var convention in conventions.Where(x => x.Length.IsStar)) - { - var (star, min) = (convention.Length.Value, convention.MinLength); - var starLength = star * starUnitLength; - if (starLength < min) - { - convention.Fix(min); - starLength = min; - aggregatedLength += starLength; - starCount -= star; - @fixed = true; - break; - } - } - - shouldTestStarMin = @fixed; - } - - // M4/7. Determine the absolute pixel size of all columns/rows that have an Auto length. - // - // +-----------------------------------------------------------+ - // | * | A | * | P | A | * | P | * | * | - // +-----------------------------------------------------------+ - // | min | max | | | min | | min max | max | - // |#fix#| | fix |#fix#| fix | fix | - - var shouldTestAuto = true; - while (shouldTestAuto) - { - var @fixed = false; - starUnitLength = (containerLength - aggregatedLength) / starCount; - for (var i = 0; i < conventions.Count; i++) - { - var convention = conventions[i]; - if (!convention.Length.IsAuto) - { - continue; - } - - var more = ApplyAdditionalConventionsForAuto(conventions, i, starUnitLength); - convention.Fix(more); - aggregatedLength += more; - @fixed = true; - break; - } - - shouldTestAuto = @fixed; - } - - // M5/7. Expand the stars according to the additional conventions (usually the child desired length). - // We can't fix this kind of length, so we just mark them as desired (des). - // - // +-----------------------------------------------------------+ - // | * | A | * | P | A | * | P | * | * | - // +-----------------------------------------------------------+ - // | min | max | | | min | | min max | max | - // |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#| - - var (minLengths, desiredStarMin) = AggregateAdditionalConventionsForStars(conventions); - aggregatedLength += desiredStarMin; - - // M6/7. Determine the desired length of the grid for current container length. Its value is stored in desiredLength. - // Assume if the container has infinite length, the grid desired length is stored in greedyDesiredLength. - // - // +-----------------------------------------------------------+ - // | * | A | * | P | A | * | P | * | * | - // +-----------------------------------------------------------+ - // | min | max | | | min | | min max | max | - // |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#| - // Note: This table will be stored as the intermediate result into the MeasureResult and it will be reused by Arrange procedure. - // - // desiredLength = Math.Max(0.0, des + fix + des + fix + fix + fix + fix + des + des) - // greedyDesiredLength = des + fix + des + fix + fix + fix + fix + des + des - - var desiredLength = containerLength - aggregatedLength >= 0.0 ? aggregatedLength : containerLength; - var greedyDesiredLength = aggregatedLength; - - // M7/7. Expand all the rest stars. These stars have no conventions or only have - // max value they can be expanded from zero to constraint. - // - // +-----------------------------------------------------------+ - // | * | A | * | P | A | * | P | * | * | - // +-----------------------------------------------------------+ - // | min | max | | | min | | min max | max | - // |#fix#| fix |#fix#| fix | fix | fix | fix | #fix# |#fix#| - // Note: This table will be stored as the final result into the MeasureResult. - - var dynamicConvention = ExpandStars(conventions, containerLength); - Clip(dynamicConvention, containerLength); - - // Returns the measuring result. - return new MeasureResult(containerLength, desiredLength, greedyDesiredLength, - conventions, dynamicConvention, minLengths); - } - - /// - /// Run arrange procedure according to the and gets the . - /// - /// - /// The container length. Usually, it is the finalSize of the method. - /// - /// - /// The result that the measuring procedure returns. If it is null, a new measure procedure will run. - /// - /// - /// The measured result that containing the desired size and all the column/row length. - /// - [NotNull, Pure] - public ArrangeResult Arrange(double finalLength, [CanBeNull] MeasureResult measure) - { - measure = measure ?? Measure(finalLength); - - // If the arrange final length does not equal to the measure length, we should measure again. - if (finalLength - measure.ContainerLength > LayoutTolerance) - { - // If the final length is larger, we will rerun the whole measure. - measure = Measure(finalLength, measure.LeanLengthList); - } - else if (finalLength - measure.ContainerLength < -LayoutTolerance) - { - // If the final length is smaller, we measure the M6/6 procedure only. - var dynamicConvention = ExpandStars(measure.LeanLengthList, finalLength); - measure = new MeasureResult(finalLength, measure.DesiredLength, measure.GreedyDesiredLength, - measure.LeanLengthList, dynamicConvention, measure.MinLengths); - } - - return new ArrangeResult(measure.LengthList); - } - - /// - /// Use the to calculate the fixed length of the Auto column/row. - /// - /// The convention list that all the * with minimum length are fixed. - /// The column/row index that should be fixed. - /// The unit * length for the current rest length. - /// The final length of the Auto length column/row. - [Pure] - private double ApplyAdditionalConventionsForAuto(IReadOnlyList conventions, - int index, double starUnitLength) - { - // 1. Calculate all the * length with starUnitLength. - // 2. Exclude all the fixed length and all the * length. - // 3. Compare the rest of the desired length and the convention. - // +-----------------+ - // | * | A | * | - // +-----------------+ - // | exl | | exl | - // |< desired >| - // |< desired >| - - var more = 0.0; - foreach (var additional in _additionalConventions) - { - // If the additional convention's last column/row contains the Auto column/row, try to determine the Auto column/row length. - if (index == additional.Index + additional.Span - 1) - { - var min = Enumerable.Range(additional.Index, additional.Span) - .Select(x => - { - var c = conventions[x]; - if (c.Length.IsAbsolute) return c.Length.Value; - if (c.Length.IsStar) return c.Length.Value * starUnitLength; - return 0.0; - }).Sum(); - more = Math.Max(additional.Min - min, more); - } - } - - return Math.Min(conventions[index].MaxLength, more); - } - - /// - /// Calculate the total desired length of all the * length. - /// Bug Warning: - /// - The behavior of this method is undefined! Different UI Frameworks have different behaviors. - /// - We ignore all the span columns/rows and just take single cells into consideration. - /// - /// All the conventions that have almost been fixed except the rest *. - /// The total desired length of all the * length. - [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] - private (List, double) AggregateAdditionalConventionsForStars( - IReadOnlyList conventions) - { - // 1. Determine all one-span column's desired widths or row's desired heights. - // 2. Order the multi-span conventions by its last index - // (Notice that the sorted data is much smaller than the source.) - // 3. Determine each multi-span last index by calculating the maximum desired size. - - // Before we determine the behavior of this method, we just aggregate the one-span * columns. - - var fixedLength = conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value); - - // Prepare a lengthList variable indicating the fixed length of each column/row. - var lengthList = conventions.Select(x => x.Length.IsAbsolute ? x.Length.Value : 0.0).ToList(); - foreach (var group in _additionalConventions - .Where(x => x.Span == 1 && conventions[x.Index].Length.IsStar) - .ToLookup(x => x.Index)) - { - lengthList[group.Key] = Math.Max(lengthList[group.Key], group.Max(x => x.Min)); - } - - // Now the lengthList is fixed by every one-span columns/rows. - // Then we should determine the multi-span column's/row's length. - foreach (var group in _additionalConventions - .Where(x => x.Span > 1) - .ToLookup(x => x.Index + x.Span - 1) - // Order the multi-span columns/rows by last index. - .OrderBy(x => x.Key)) - { - var length = group.Max(x => x.Min - Enumerable.Range(x.Index, x.Span - 1).Sum(r => lengthList[r])); - lengthList[group.Key] = Math.Max(lengthList[group.Key], length > 0 ? length : 0); - } - - return (lengthList, lengthList.Sum() - fixedLength); - } - - /// - /// This method implements the last procedure (M7/7) of measure. - /// It expands all the * length to the fixed length according to the . - /// - /// All the conventions that have almost been fixed except the remaining *. - /// The container length. - /// The final pixel length list. - [Pure] - private static List ExpandStars(IEnumerable conventions, double constraint) - { - // Initial. - var dynamicConvention = conventions.Select(x => x.Clone()).ToList(); - constraint -= dynamicConvention.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value); - var starUnitLength = 0.0; - - // M6/6. - if (constraint >= 0) - { - var starCount = dynamicConvention.Where(x => x.Length.IsStar).Sum(x => x.Length.Value); - - var shouldTestStarMax = true; - while (shouldTestStarMax) - { - var @fixed = false; - starUnitLength = constraint / starCount; - foreach (var convention in dynamicConvention.Where(x => - x.Length.IsStar && !double.IsPositiveInfinity(x.MaxLength))) - { - var (star, max) = (convention.Length.Value, convention.MaxLength); - var starLength = star * starUnitLength; - if (starLength > max) - { - convention.Fix(max); - starLength = max; - constraint -= starLength; - starCount -= star; - @fixed = true; - break; - } - } - - shouldTestStarMax = @fixed; - } - } - - Debug.Assert(dynamicConvention.All(x => !x.Length.IsAuto)); - - var starUnit = starUnitLength; - var result = dynamicConvention.Select(x => - { - if (x.Length.IsStar) - { - return double.IsInfinity(starUnit) ? double.PositiveInfinity : starUnit * x.Length.Value; - } - - return x.Length.Value; - }).ToList(); - - return result; - } - - /// - /// If the container length is not infinity. It may be not enough to contain all the columns/rows. - /// We should clip the columns/rows that have been out of the container bounds. - /// Note: This method may change the items value of . - /// - /// A list of all the column widths and row heights with a fixed pixel length - /// the container length. It can be positive infinity. - private static void Clip([NotNull] IList lengthList, double constraint) - { - if (double.IsInfinity(constraint)) - { - return; - } - - var measureLength = 0.0; - for (var i = 0; i < lengthList.Count; i++) - { - var length = lengthList[i]; - if (constraint - measureLength > length) - { - measureLength += length; - } - else - { - lengthList[i] = constraint - measureLength; - measureLength = constraint; - } - } - } - - /// - /// Contains the convention of each column/row. - /// This is mostly the same as or . - /// We use this because we can treat the column and the row the same. - /// - [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] - internal class LengthConvention : ICloneable - { - /// - /// Initialize a new instance of . - /// - public LengthConvention() - { - Length = new GridLength(1.0, GridUnitType.Star); - MinLength = 0.0; - MaxLength = double.PositiveInfinity; - } - - /// - /// Initialize a new instance of . - /// - public LengthConvention(GridLength length, double minLength, double maxLength) - { - Length = length; - MinLength = minLength; - MaxLength = maxLength; - if (length.IsAbsolute) - { - _isFixed = true; - } - } - - /// - /// Gets the of a column or a row. - /// - internal GridLength Length { get; private set; } - - /// - /// Gets the minimum convention for a column or a row. - /// - internal double MinLength { get; } - - /// - /// Gets the maximum convention for a column or a row. - /// - internal double MaxLength { get; } - - /// - /// Fix the . - /// If all columns/rows are fixed, we can get the size of all columns/rows in pixels. - /// - /// - /// The pixel length that should be used to fix the convention. - /// - /// - /// If the convention is pixel length, this exception will throw. - /// - public void Fix(double pixel) - { - if (_isFixed) - { - throw new InvalidOperationException("Cannot fix the length convention if it is fixed."); - } - - Length = new GridLength(pixel); - _isFixed = true; - } - - /// - /// Gets a value that indicates whether this convention is fixed. - /// - private bool _isFixed; - - /// - /// Helps the debugger to display the intermediate column/row calculation result. - /// - private string DebuggerDisplay => - $"{(_isFixed ? Length.Value.ToString(CultureInfo.InvariantCulture) : (Length.GridUnitType == GridUnitType.Auto ? "Auto" : $"{Length.Value}*"))}, ∈[{MinLength}, {MaxLength}]"; - - /// - object ICloneable.Clone() => Clone(); - - /// - /// Get a deep copy of this convention list. - /// We need this because we want to store some intermediate states. - /// - internal LengthConvention Clone() => new LengthConvention(Length, MinLength, MaxLength); - } - - /// - /// Contains the convention that comes from the grid children. - /// Some children span multiple columns or rows, so even a simple column/row can have multiple conventions. - /// - [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] - internal struct AdditionalLengthConvention - { - /// - /// Initialize a new instance of . - /// - public AdditionalLengthConvention(int index, int span, double min) - { - Index = index; - Span = span; - Min = min; - } - - /// - /// Gets the start index of this additional convention. - /// - public int Index { get; } - - /// - /// Gets the span of this additional convention. - /// - public int Span { get; } - - /// - /// Gets the minimum length of this additional convention. - /// This value is usually provided by the child's desired length. - /// - public double Min { get; } - - /// - /// Helps the debugger to display the intermediate column/row calculation result. - /// - private string DebuggerDisplay => - $"{{{string.Join(",", Enumerable.Range(Index, Span))}}}, ∈[{Min},∞)"; - } - - /// - /// Stores the result of the measuring procedure. - /// This result can be used to measure children and assign the desired size. - /// Passing this result to can reduce calculation. - /// - [DebuggerDisplay("{" + nameof(LengthList) + ",nq}")] - internal class MeasureResult - { - /// - /// Initialize a new instance of . - /// - internal MeasureResult(double containerLength, double desiredLength, double greedyDesiredLength, - IReadOnlyList leanConventions, IReadOnlyList expandedConventions, IReadOnlyList minLengths) - { - ContainerLength = containerLength; - DesiredLength = desiredLength; - GreedyDesiredLength = greedyDesiredLength; - LeanLengthList = leanConventions; - LengthList = expandedConventions; - MinLengths = minLengths; - } - - /// - /// Gets the container length for this result. - /// This property will be used by to determine whether to measure again or not. - /// - public double ContainerLength { get; } - - /// - /// Gets the desired length of this result. - /// Just return this value as the desired size in . - /// - public double DesiredLength { get; } - - /// - /// Gets the desired length if the container has infinite length. - /// - public double GreedyDesiredLength { get; } - - /// - /// Contains the column/row calculation intermediate result. - /// This value is used by for reducing repeat calculation. - /// - public IReadOnlyList LeanLengthList { get; } - - /// - /// Gets the length list for each column/row. - /// - public IReadOnlyList LengthList { get; } - public IReadOnlyList MinLengths { get; } - } - - /// - /// Stores the result of the measuring procedure. - /// This result can be used to arrange children and assign the render size. - /// - [DebuggerDisplay("{" + nameof(LengthList) + ",nq}")] - internal class ArrangeResult - { - /// - /// Initialize a new instance of . - /// - internal ArrangeResult(IReadOnlyList lengthList) - { - LengthList = lengthList; - } - - /// - /// Gets the length list for each column/row. - /// - public IReadOnlyList LengthList { get; } - } - } -} diff --git a/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs b/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs deleted file mode 100644 index 5f70557385..0000000000 --- a/src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs +++ /dev/null @@ -1,651 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Subjects; -using Avalonia.Collections; -using Avalonia.Controls.Utils; -using Avalonia.Layout; -using Avalonia.VisualTree; - -namespace Avalonia.Controls -{ - /// - /// Shared size scope implementation. - /// Shares the size information between participating grids. - /// An instance of this class is attached to every that has its - /// IsSharedSizeScope property set to true. - /// - internal sealed class SharedSizeScopeHost : IDisposable - { - private enum MeasurementState - { - Invalidated, - Measuring, - Cached - } - - /// - /// Class containing the measured rows/columns for a single grid. - /// Monitors changes to the row/column collections as well as the SharedSizeGroup changes - /// for the individual items in those collections. - /// Notifies the of SharedSizeGroup changes. - /// - private sealed class MeasurementCache : IDisposable - { - readonly CompositeDisposable _subscriptions; - readonly Subject<(string, string, MeasurementResult)> _groupChanged = new Subject<(string, string, MeasurementResult)>(); - - public ISubject<(string oldName, string newName, MeasurementResult result)> GroupChanged => _groupChanged; - - public MeasurementCache(Grid grid) - { - Grid = grid; - Results = grid.RowDefinitions.Cast() - .Concat(grid.ColumnDefinitions) - .Select(d => new MeasurementResult(grid, d)) - .ToList(); - - grid.RowDefinitions.CollectionChanged += DefinitionsCollectionChanged; - grid.ColumnDefinitions.CollectionChanged += DefinitionsCollectionChanged; - - - _subscriptions = new CompositeDisposable( - Disposable.Create(() => grid.RowDefinitions.CollectionChanged -= DefinitionsCollectionChanged), - Disposable.Create(() => grid.ColumnDefinitions.CollectionChanged -= DefinitionsCollectionChanged), - grid.RowDefinitions.TrackItemPropertyChanged(DefinitionPropertyChanged), - grid.ColumnDefinitions.TrackItemPropertyChanged(DefinitionPropertyChanged)); - - } - - // method to be hooked up once RowDefinitions/ColumnDefinitions collections can be replaced on a grid - private void DefinitionsChanged(object sender, AvaloniaPropertyChangedEventArgs e) - { - // route to collection changed as a Reset. - DefinitionsCollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - private void DefinitionPropertyChanged(Tuple propertyChanged) - { - if (propertyChanged.Item2.PropertyName == nameof(DefinitionBase.SharedSizeGroup)) - { - var result = Results.Single(mr => ReferenceEquals(mr.Definition, propertyChanged.Item1)); - var oldName = result.SizeGroup?.Name; - var newName = (propertyChanged.Item1 as DefinitionBase).SharedSizeGroup; - _groupChanged.OnNext((oldName, newName, result)); - } - } - - private void DefinitionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int offset = 0; - if (sender is ColumnDefinitions) - offset = Grid.RowDefinitions.Count; - - var newItems = e.NewItems?.OfType().Select(db => new MeasurementResult(Grid, db)).ToList() ?? new List(); - var oldItems = e.OldStartingIndex >= 0 - ? Results.GetRange(e.OldStartingIndex + offset, e.OldItems.Count) - : new List(); - - void NotifyNewItems() - { - foreach (var item in newItems) - { - if (string.IsNullOrEmpty(item.Definition.SharedSizeGroup)) - continue; - - _groupChanged.OnNext((null, item.Definition.SharedSizeGroup, item)); - } - } - - void NotifyOldItems() - { - foreach (var item in oldItems) - { - if (string.IsNullOrEmpty(item.Definition.SharedSizeGroup)) - continue; - - _groupChanged.OnNext((item.Definition.SharedSizeGroup, null, item)); - } - } - - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - Results.InsertRange(e.NewStartingIndex + offset, newItems); - NotifyNewItems(); - break; - - case NotifyCollectionChangedAction.Remove: - Results.RemoveRange(e.OldStartingIndex + offset, oldItems.Count); - NotifyOldItems(); - break; - - case NotifyCollectionChangedAction.Move: - Results.RemoveRange(e.OldStartingIndex + offset, oldItems.Count); - Results.InsertRange(e.NewStartingIndex + offset, oldItems); - break; - - case NotifyCollectionChangedAction.Replace: - Results.RemoveRange(e.OldStartingIndex + offset, oldItems.Count); - Results.InsertRange(e.NewStartingIndex + offset, newItems); - - NotifyOldItems(); - NotifyNewItems(); - - break; - - case NotifyCollectionChangedAction.Reset: - oldItems = Results; - newItems = Results = Grid.RowDefinitions.Cast() - .Concat(Grid.ColumnDefinitions) - .Select(d => new MeasurementResult(Grid, d)) - .ToList(); - NotifyOldItems(); - NotifyNewItems(); - - break; - } - } - - - /// - /// Updates the Results collection with Grid Measure results. - /// - /// Result of the GridLayout.Measure method for the RowDefinitions in the grid. - /// Result of the GridLayout.Measure method for the ColumnDefinitions in the grid. - public void UpdateMeasureResult(GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult) - { - MeasurementState = MeasurementState.Cached; - for (int i = 0; i < Grid.RowDefinitions.Count; i++) - { - Results[i].MeasuredResult = rowResult.LengthList[i]; - Results[i].MinLength = rowResult.MinLengths[i]; - } - - for (int i = 0; i < Grid.ColumnDefinitions.Count; i++) - { - Results[i + Grid.RowDefinitions.Count].MeasuredResult = columnResult.LengthList[i]; - Results[i + Grid.RowDefinitions.Count].MinLength = columnResult.MinLengths[i]; - } - } - - /// - /// Clears the measurement cache, in preparation for the Measure pass. - /// - public void InvalidateMeasure() - { - var newItems = new List(); - var oldItems = new List(); - - MeasurementState = MeasurementState.Invalidated; - - Results.ForEach(r => - { - r.MeasuredResult = double.NaN; - r.SizeGroup?.Reset(); - }); - } - - /// - /// Clears the subscriptions. - /// - public void Dispose() - { - _subscriptions.Dispose(); - _groupChanged.OnCompleted(); - } - - /// - /// Gets the for which this cache has been created. - /// - public Grid Grid { get; } - - /// - /// Gets the of this cache. - /// - public MeasurementState MeasurementState { get; private set; } - - /// - /// Gets the list of instances. - /// - /// - /// The list is a 1-1 map of the concatenation of RowDefinitions and ColumnDefinitions - /// - public List Results { get; private set; } - } - - - /// - /// Class containing the Measure result for a single Row/Column in a grid. - /// - private class MeasurementResult - { - public MeasurementResult(Grid owningGrid, DefinitionBase definition) - { - OwningGrid = owningGrid; - Definition = definition; - MeasuredResult = double.NaN; - } - - /// - /// Gets the / related to this - /// - public DefinitionBase Definition { get; } - - /// - /// Gets or sets the actual result of the Measure operation for this column. - /// - public double MeasuredResult { get; set; } - - /// - /// Gets or sets the Minimum constraint for a Row/Column - relevant for star Rows/Columns in unconstrained grids. - /// - public double MinLength { get; set; } - - /// - /// Gets or sets the that this result belongs to. - /// - public Group SizeGroup { get; set; } - - /// - /// Gets the Grid that is the parent of the Row/Column - /// - public Grid OwningGrid { get; } - - /// - /// Calculates the effective length that this Row/Column wishes to enforce in the SharedSizeGroup. - /// - /// A tuple of length and the priority in the shared size group. - public (double length, int priority) GetPriorityLength() - { - var length = (Definition as ColumnDefinition)?.Width ?? ((RowDefinition)Definition).Height; - - if (length.IsAbsolute) - return (MeasuredResult, 1); - if (length.IsAuto) - return (MeasuredResult, 2); - if (MinLength > 0) - return (MinLength, 3); - return (MeasuredResult, 4); - } - } - - /// - /// Visitor class used to gather the final length for a given SharedSizeGroup. - /// - /// - /// The values are applied according to priorities defined in . - /// - private class LentgthGatherer - { - /// - /// Gets the final Length to be applied to every Row/Column in a SharedSizeGroup - /// - public double Length { get; private set; } - private int gatheredPriority = 6; - - /// - /// Visits the applying the result of to its internal cache. - /// - /// The instance to visit. - public void Visit(MeasurementResult result) - { - var (length, priority) = result.GetPriorityLength(); - - if (gatheredPriority < priority) - return; - - gatheredPriority = priority; - if (gatheredPriority == priority) - { - Length = Math.Max(length,Length); - } - else - { - Length = length; - } - } - } - - /// - /// Representation of a SharedSizeGroup, containing Rows/Columns with the same SharedSizeGroup property value. - /// - private class Group - { - private double? cachedResult; - private List _results = new List(); - - /// - /// Gets the name of the SharedSizeGroup. - /// - public string Name { get; } - - public Group(string name) - { - Name = name; - } - - /// - /// Gets the collection of the instances. - /// - public IReadOnlyList Results => _results; - - /// - /// Gets the final, calculated length for all Rows/Columns in the SharedSizeGroup. - /// - public double CalculatedLength => (cachedResult ?? (cachedResult = Gather())).Value; - - /// - /// Clears the previously cached result in preparation for measurement. - /// - public void Reset() - { - cachedResult = null; - } - - /// - /// Ads a measurement result to this group and sets it's property - /// to this instance. - /// - /// The to include in this group. - public void Add(MeasurementResult result) - { - if (_results.Contains(result)) - throw new AvaloniaInternalException( - $"SharedSizeScopeHost: Invalid call to Group.Add - The SharedSizeGroup {Name} already contains the passed result"); - - result.SizeGroup = this; - _results.Add(result); - } - - /// - /// Removes the measurement result from this group and clears its value. - /// - /// The to clear. - public void Remove(MeasurementResult result) - { - if (!_results.Contains(result)) - throw new AvaloniaInternalException( - $"SharedSizeScopeHost: Invalid call to Group.Remove - The SharedSizeGroup {Name} does not contain the passed result"); - result.SizeGroup = null; - _results.Remove(result); - } - - - private double Gather() - { - var visitor = new LentgthGatherer(); - - _results.ForEach(visitor.Visit); - - return visitor.Length; - } - } - - private readonly AvaloniaList _measurementCaches = new AvaloniaList(); - private readonly Dictionary _groups = new Dictionary(); - private bool _invalidating; - - /// - /// Removes the SharedSizeScope and notifies all affected grids of the change. - /// - public void Dispose() - { - // while (_measurementCaches.Any()) - // _measurementCaches[0].Grid.SharedScopeChanged(); - } - - /// - /// Registers the grid in this SharedSizeScope, to be called when the grid is added to the visual tree. - /// - /// The to add to this scope. - internal void RegisterGrid(Grid toAdd) - { - if (_measurementCaches.Any(mc => ReferenceEquals(mc.Grid, toAdd))) - throw new AvaloniaInternalException("SharedSizeScopeHost: tried to register a grid twice!"); - - var cache = new MeasurementCache(toAdd); - _measurementCaches.Add(cache); - AddGridToScopes(cache); - } - - /// - /// Removes the registration for a grid in this SharedSizeScope. - /// - /// The to remove. - internal void UnegisterGrid(Grid toRemove) - { - var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, toRemove)); - if (cache == null) - throw new AvaloniaInternalException("SharedSizeScopeHost: tried to unregister a grid that wasn't registered before!"); - - _measurementCaches.Remove(cache); - RemoveGridFromScopes(cache); - cache.Dispose(); - } - - /// - /// Helper method to check if a grid needs to forward its Mesure results to, and requrest Arrange results from this scope. - /// - /// The that should be checked. - /// True if the grid should forward its calls. - internal bool ParticipatesInScope(Grid toCheck) - { - return _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, toCheck)) - ?.Results.Any(r => r.SizeGroup != null) ?? false; - } - - /// - /// Notifies the SharedSizeScope that a grid had requested its measurement to be invalidated. - /// Forwards the same call to all affected grids in this scope. - /// - /// The that had it's Measure invalidated. - internal void InvalidateMeasure(Grid grid) - { - // prevent stack overflow - if (_invalidating) - return; - _invalidating = true; - - InvalidateMeasureImpl(grid); - - _invalidating = false; - } - - /// - /// Updates the measurement cache with the results of the measurement pass. - /// - /// The that has been measured. - /// Measurement result for the grid's - /// Measurement result for the grid's - internal void UpdateMeasureStatus(Grid grid, GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult) - { - var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid)); - if (cache == null) - throw new AvaloniaInternalException("SharedSizeScopeHost: Attempted to update measurement status for a grid that wasn't registered!"); - - cache.UpdateMeasureResult(rowResult, columnResult); - } - - /// - /// Calculates the measurement result including the impact of any SharedSizeGroups that might affect this grid. - /// - /// The that is being Arranged - /// The 's cached measurement result. - /// The 's cached measurement result. - /// Row and column measurement result updated with the SharedSizeScope constraints. - internal (GridLayout.MeasureResult, GridLayout.MeasureResult) HandleArrange(Grid grid, GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult) - { - return ( - Arrange(grid.RowDefinitions, rowResult), - Arrange(grid.ColumnDefinitions, columnResult) - ); - } - - /// - /// Invalidates the measure of all grids affected by the SharedSizeGroups contained within. - /// - /// The that is being invalidated. - private void InvalidateMeasureImpl(Grid grid) - { - var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid)); - - if (cache == null) - throw new AvaloniaInternalException( - $"SharedSizeScopeHost: InvalidateMeasureImpl - called with a grid not present in the internal cache"); - - // already invalidated the cache, early out. - if (cache.MeasurementState == MeasurementState.Invalidated) - return; - - // we won't calculate, so we should not invalidate. - if (!ParticipatesInScope(grid)) - return; - - cache.InvalidateMeasure(); - - // maybe there is a condition to only call arrange on some of the calls? - grid.InvalidateMeasure(); - - // find all the scopes within the invalidated grid - var scopeNames = cache.Results - .Where(mr => mr.SizeGroup != null) - .Select(mr => mr.SizeGroup.Name) - .Distinct(); - // find all grids related to those scopes - var otherGrids = scopeNames.SelectMany(sn => _groups[sn].Results) - .Select(r => r.OwningGrid) - .Where(g => g.IsMeasureValid) - .Distinct(); - - // invalidate them as well - foreach (var otherGrid in otherGrids) - { - InvalidateMeasureImpl(otherGrid); - } - } - - /// - /// callback notifying the scope that a has changed its - /// SharedSizeGroup - /// - /// Old and New name (either can be null) of the SharedSizeGroup, as well as the result. - private void SharedGroupChanged((string oldName, string newName, MeasurementResult result) change) - { - RemoveFromGroup(change.oldName, change.result); - AddToGroup(change.newName, change.result); - } - - /// - /// Handles the impact of SharedSizeGroups on the Arrange of / - /// - /// Rows/Columns that were measured - /// The initial measurement result. - /// Modified measure result - private GridLayout.MeasureResult Arrange(IReadOnlyList definitions, GridLayout.MeasureResult measureResult) - { - var conventions = measureResult.LeanLengthList.ToList(); - var lengths = measureResult.LengthList.ToList(); - var desiredLength = 0.0; - for (int i = 0; i < definitions.Count; i++) - { - var definition = definitions[i]; - - // for empty SharedSizeGroups pass on unmodified result. - if (string.IsNullOrEmpty(definition.SharedSizeGroup)) - { - desiredLength += measureResult.LengthList[i]; - continue; - } - - var group = _groups[definition.SharedSizeGroup]; - // Length calculated over all Definitions participating in a SharedSizeGroup. - var length = group.CalculatedLength; - - conventions[i] = new GridLayout.LengthConvention( - new GridLength(length), - measureResult.LeanLengthList[i].MinLength, - measureResult.LeanLengthList[i].MaxLength - ); - lengths[i] = length; - desiredLength += length; - } - - return new GridLayout.MeasureResult( - measureResult.ContainerLength, - desiredLength, - measureResult.GreedyDesiredLength,//?? - conventions, - lengths, - measureResult.MinLengths); - } - - /// - /// Adds all measurement results for a grid to their repsective scopes. - /// - /// The for a grid to be added. - private void AddGridToScopes(MeasurementCache cache) - { - cache.GroupChanged.Subscribe(SharedGroupChanged); - - foreach (var result in cache.Results) - { - var scopeName = result.Definition.SharedSizeGroup; - AddToGroup(scopeName, result); - } - } - - /// - /// Handles adding the to a SharedSizeGroup. - /// Does nothing for empty SharedSizeGroups. - /// - /// The name (can be null or empty) of the group to add the to. - /// The to add to a scope. - private void AddToGroup(string scopeName, MeasurementResult result) - { - if (string.IsNullOrEmpty(scopeName)) - return; - - if (!_groups.TryGetValue(scopeName, out var group)) - _groups.Add(scopeName, group = new Group(scopeName)); - - group.Add(result); - } - - /// - /// Removes all measurement results for a grid from their respective scopes. - /// - /// The for a grid to be removed. - private void RemoveGridFromScopes(MeasurementCache cache) - { - foreach (var result in cache.Results) - { - var scopeName = result.Definition.SharedSizeGroup; - RemoveFromGroup(scopeName, result); - } - } - - /// - /// Handles removing the from a SharedSizeGroup. - /// Does nothing for empty SharedSizeGroups. - /// - /// The name (can be null or empty) of the group to remove the from. - /// The to remove from a scope. - private void RemoveFromGroup(string scopeName, MeasurementResult result) - { - if (string.IsNullOrEmpty(scopeName)) - return; - - if (!_groups.TryGetValue(scopeName, out var group)) - throw new AvaloniaInternalException($"SharedSizeScopeHost: The scope {scopeName} wasn't found in the shared size scope"); - - group.Remove(result); - if (!group.Results.Any()) - _groups.Remove(scopeName); - } - } -} diff --git a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs deleted file mode 100644 index 93163f4a92..0000000000 --- a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Avalonia.Controls.Utils; -using Xunit; - -namespace Avalonia.Controls.UnitTests -{ - public class GridLayoutTests - { - private const double Inf = double.PositiveInfinity; - - [Theory] - [InlineData("100, 200, 300", 0d, 0d, new[] { 0d, 0d, 0d })] - [InlineData("100, 200, 300", 800d, 600d, new[] { 100d, 200d, 300d })] - [InlineData("100, 200, 300", 600d, 600d, new[] { 100d, 200d, 300d })] - [InlineData("100, 200, 300", 400d, 400d, new[] { 100d, 200d, 100d })] - public void MeasureArrange_AllPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) - { - TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); - } - - [Theory] - [InlineData("*,2*,3*", 0d, 0d, new[] { 0d, 0d, 0d })] - [InlineData("*,2*,3*", 600d, 0d, new[] { 100d, 200d, 300d })] - public void MeasureArrange_AllStarLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) - { - TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); - } - - [Theory] - [InlineData("100,2*,3*", 0d, 0d, new[] { 0d, 0d, 0d })] - [InlineData("100,2*,3*", 600d, 100d, new[] { 100d, 200d, 300d })] - [InlineData("100,2*,3*", 100d, 100d, new[] { 100d, 0d, 0d })] - [InlineData("100,2*,3*", 50d, 50d, new[] { 50d, 0d, 0d })] - public void MeasureArrange_MixStarPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) - { - TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); - } - - [Theory] - [InlineData("100,200,Auto", 0d, 0d, new[] { 0d, 0d, 0d })] - [InlineData("100,200,Auto", 600d, 300d, new[] { 100d, 200d, 0d })] - [InlineData("100,200,Auto", 300d, 300d, new[] { 100d, 200d, 0d })] - [InlineData("100,200,Auto", 200d, 200d, new[] { 100d, 100d, 0d })] - [InlineData("100,200,Auto", 100d, 100d, new[] { 100d, 0d, 0d })] - [InlineData("100,200,Auto", 50d, 50d, new[] { 50d, 0d, 0d })] - public void MeasureArrange_MixAutoPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) - { - TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); - } - - [Theory] - [InlineData("*,2*,Auto", 0d, 0d, new[] { 0d, 0d, 0d })] - [InlineData("*,2*,Auto", 600d, 0d, new[] { 200d, 400d, 0d })] - public void MeasureArrange_MixAutoStarLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) - { - TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); - } - - [Theory] - [InlineData("*,200,Auto", 0d, 0d, new[] { 0d, 0d, 0d })] - [InlineData("*,200,Auto", 600d, 200d, new[] { 400d, 200d, 0d })] - [InlineData("*,200,Auto", 200d, 200d, new[] { 0d, 200d, 0d })] - [InlineData("*,200,Auto", 100d, 100d, new[] { 0d, 100d, 0d })] - public void MeasureArrange_MixAutoStarPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) - { - TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); - } - - - /// - /// This is needed because Mono somehow converts double array to object array in attribute metadata - /// - static void AssertEqual(IList expected, IReadOnlyList actual) - { - var conv = expected.Cast().ToArray(); - Assert.Equal(conv, actual); - } - - [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local")] - private static void TestRowDefinitionsOnly(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) - { - // Arrange - var layout = new GridLayout(new RowDefinitions(length)); - - // Measure - Action & Assert - var measure = layout.Measure(containerLength); - Assert.Equal(expectedDesiredLength, measure.DesiredLength); - AssertEqual(expectedLengthList, measure.LengthList); - - // Arrange - Action & Assert - var arrange = layout.Arrange(containerLength, measure); - AssertEqual(expectedLengthList, arrange.LengthList); - } - - [Theory] - [InlineData("100, 200, 300", 600d, new[] { 100d, 200d, 300d }, new[] { 100d, 200d, 300d })] - [InlineData("*,2*,3*", 0d, new[] { Inf, Inf, Inf }, new[] { 0d, 0d, 0d })] - [InlineData("100,2*,3*", 100d, new[] { 100d, Inf, Inf }, new[] { 100d, 0d, 0d })] - [InlineData("100,200,Auto", 300d, new[] { 100d, 200d, 0d }, new[] { 100d, 200d, 0d })] - [InlineData("*,2*,Auto", 0d, new[] { Inf, Inf, 0d }, new[] { 0d, 0d, 0d })] - [InlineData("*,200,Auto", 200d, new[] { Inf, 200d, 0d }, new[] { 0d, 200d, 0d })] - public void MeasureArrange_InfiniteMeasure_Correct(string length, double expectedDesiredLength, - IList expectedMeasureList, IList expectedArrangeList) - { - // Arrange - var layout = new GridLayout(new RowDefinitions(length)); - - // Measure - Action & Assert - var measure = layout.Measure(Inf); - Assert.Equal(expectedDesiredLength, measure.DesiredLength); - AssertEqual(expectedMeasureList, measure.LengthList); - - // Arrange - Action & Assert - var arrange = layout.Arrange(measure.DesiredLength, measure); - AssertEqual(expectedArrangeList, arrange.LengthList); - } - - [Theory] - [InlineData("Auto,*,*", new[] { 100d, 100d, 100d }, 600d, 300d, new[] { 100d, 250d, 250d })] - public void MeasureArrange_ChildHasSize_Correct(string length, - IList childLengthList, double containerLength, - double expectedDesiredLength, IList expectedLengthList) - { - // Arrange - var lengthList = new ColumnDefinitions(length); - var layout = new GridLayout(lengthList); - layout.AppendMeasureConventions( - Enumerable.Range(0, lengthList.Count).ToDictionary(x => x, x => (x, 1)), - x => (double)childLengthList[x]); - - // Measure - Action & Assert - var measure = layout.Measure(containerLength); - Assert.Equal(expectedDesiredLength, measure.DesiredLength); - AssertEqual(expectedLengthList, measure.LengthList); - - // Arrange - Action & Assert - var arrange = layout.Arrange(containerLength, measure); - AssertEqual(expectedLengthList, arrange.LengthList); - } - - [Theory] - [InlineData(Inf, 250d, new[] { 100d, Inf, Inf }, new[] { 100d, 50d, 100d })] - [InlineData(400d, 250d, new[] { 100d, 100d, 200d }, new[] { 100d, 100d, 200d })] - [InlineData(325d, 250d, new[] { 100d, 75d, 150d }, new[] { 100d, 75d, 150d })] - [InlineData(250d, 250d, new[] { 100d, 50d, 100d }, new[] { 100d, 50d, 100d })] - [InlineData(160d, 160d, new[] { 100d, 20d, 40d }, new[] { 100d, 20d, 40d })] - public void MeasureArrange_ChildHasSizeAndHasMultiSpan_Correct( - double containerLength, double expectedDesiredLength, - IList expectedMeasureLengthList, IList expectedArrangeLengthList) - { - var length = "100,*,2*"; - var childLengthList = new[] { 150d, 150d, 150d }; - var spans = new[] { 1, 2, 1 }; - - // Arrange - var lengthList = new ColumnDefinitions(length); - var layout = new GridLayout(lengthList); - layout.AppendMeasureConventions( - Enumerable.Range(0, lengthList.Count).ToDictionary(x => x, x => (x, spans[x])), - x => childLengthList[x]); - - // Measure - Action & Assert - var measure = layout.Measure(containerLength); - Assert.Equal(expectedDesiredLength, measure.DesiredLength); - AssertEqual(expectedMeasureLengthList, measure.LengthList); - - // Arrange - Action & Assert - var arrange = layout.Arrange( - double.IsInfinity(containerLength) ? measure.DesiredLength : containerLength, - measure); - AssertEqual(expectedArrangeLengthList, arrange.LengthList); - } - } -} From 7f6414970aadafdb35ec3d632231bafa66f34c30 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 19:47:32 +0800 Subject: [PATCH 041/130] Revert `DrawGridLines` to FinalOffsets --- src/Avalonia.Controls/Grid.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 236b478f95..5b7b4b2afa 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -2779,16 +2779,16 @@ namespace Avalonia.Controls { DrawGridLine( drawingContext, - grid.ColumnDefinitions[i].ActualWidth, 0.0, - grid.ColumnDefinitions[i].ActualWidth, _lastArrangeSize.Height); + grid.ColumnDefinitions[i].FinalOffset, 0.0, + grid.ColumnDefinitions[i].FinalOffset, _lastArrangeSize.Height); } for (int i = 1; i < grid.RowDefinitions.Count; ++i) { DrawGridLine( drawingContext, - 0.0, grid.RowDefinitions[i].ActualHeight, - _lastArrangeSize.Width, grid.RowDefinitions[i].ActualHeight); + 0.0, grid.RowDefinitions[i].FinalOffset, + _lastArrangeSize.Width, grid.RowDefinitions[i].FinalOffset); } } From 9c6c096ea97466ff897dfe47cd31d3eed1ee5801 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 20:45:09 +0800 Subject: [PATCH 042/130] Organize Grid into its own folder and split up its internal classes. --- .../{ => Grid}/DefinitionBase.cs | 6 +- src/Avalonia.Controls/{ => Grid}/Grid.cs | 513 +----------------- src/Avalonia.Controls/Grid/GridCellCache.cs | 26 + .../{ => Grid}/GridLength.cs | 0 .../Grid/GridLinesRenderer.cs | 95 ++++ src/Avalonia.Controls/Grid/GridSpanKey.cs | 69 +++ .../{ => Grid}/GridSplitter.cs | 0 .../Grid/LayoutTimeSizeType.cs | 17 + .../Grid/MaxRatioComparer.cs | 31 ++ .../Grid/MaxRatioIndexComparer.cs | 46 ++ .../Grid/MinRatioComparer.cs | 30 + .../Grid/MinRatioIndexComparer.cs | 46 ++ .../Grid/RoundingErrorIndexComparer.cs | 36 ++ .../Grid/SpanMaxDistributionOrderComparer.cs | 46 ++ .../SpanPreferredDistributionOrderComparer.cs | 46 ++ .../Grid/StarWeightComparer.cs | 29 + .../Grid/StarWeightIndexComparer.cs | 47 ++ 17 files changed, 574 insertions(+), 509 deletions(-) rename src/Avalonia.Controls/{ => Grid}/DefinitionBase.cs (94%) rename src/Avalonia.Controls/{ => Grid}/Grid.cs (85%) create mode 100644 src/Avalonia.Controls/Grid/GridCellCache.cs rename src/Avalonia.Controls/{ => Grid}/GridLength.cs (100%) create mode 100644 src/Avalonia.Controls/Grid/GridLinesRenderer.cs create mode 100644 src/Avalonia.Controls/Grid/GridSpanKey.cs rename src/Avalonia.Controls/{ => Grid}/GridSplitter.cs (100%) create mode 100644 src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs create mode 100644 src/Avalonia.Controls/Grid/MaxRatioComparer.cs create mode 100644 src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs create mode 100644 src/Avalonia.Controls/Grid/MinRatioComparer.cs create mode 100644 src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs create mode 100644 src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs create mode 100644 src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs create mode 100644 src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs create mode 100644 src/Avalonia.Controls/Grid/StarWeightComparer.cs create mode 100644 src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/Grid/DefinitionBase.cs similarity index 94% rename from src/Avalonia.Controls/DefinitionBase.cs rename to src/Avalonia.Controls/Grid/DefinitionBase.cs index 3dd4819ca7..ad2f33a18a 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/Grid/DefinitionBase.cs @@ -47,7 +47,7 @@ namespace Avalonia.Controls /// /// Layout-time user size type. /// - internal Grid.LayoutTimeSizeType SizeType { get; set; } + internal LayoutTimeSizeType SizeType { get; set; } /// /// Returns or sets measure size for the definition. @@ -65,12 +65,12 @@ namespace Avalonia.Controls get { double preferredSize = MinSize; - if (SizeType != Grid.LayoutTimeSizeType.Auto + if (SizeType != LayoutTimeSizeType.Auto && preferredSize < MeasureSize) { preferredSize = MeasureSize; } - return (preferredSize); + return (preferredSize); } } diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs similarity index 85% rename from src/Avalonia.Controls/Grid.cs rename to src/Avalonia.Controls/Grid/Grid.cs index 5b7b4b2afa..a14e3f0d01 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid/Grid.cs @@ -9,11 +9,9 @@ using System.Reactive.Linq; using System.Runtime.CompilerServices; using Avalonia.Collections; using Avalonia.Controls.Utils; -using Avalonia.VisualTree; using System.Threading; using JetBrains.Annotations; using Avalonia.Controls; -using Avalonia.Media; using Avalonia; using System.Collections; using Avalonia.Utilities; @@ -21,9 +19,6 @@ using Avalonia.Layout; namespace Avalonia.Controls { - /// - /// Grid - /// public class Grid : Panel { internal bool CellsStructureDirty = true; @@ -55,8 +50,7 @@ namespace Avalonia.Controls // Keeps track of definition indices. private int[] _definitionIndices; - private CellCache[] _cellCache; - + private GridCellCache[] _cellCache; // Stores unrounded values and rounding errors during layout rounding. private double[] _roundingErrors; @@ -309,7 +303,7 @@ namespace Avalonia.Controls } } - private bool IsTrivialGrid => (_definitionsU?.Length <= 1) && + internal bool IsTrivialGrid => (_definitionsU?.Length <= 1) && (_definitionsV?.Length <= 1); /// @@ -607,7 +601,7 @@ namespace Avalonia.Controls { if (!CellsStructureDirty) return; - _cellCache = new CellCache[Children.Count]; + _cellCache = new GridCellCache[Children.Count]; CellGroup1 = int.MaxValue; CellGroup2 = int.MaxValue; CellGroup3 = int.MaxValue; @@ -626,7 +620,7 @@ namespace Avalonia.Controls continue; } - var cell = new CellCache(); + var cell = new GridCellCache(); // read indices from the corresponding properties // clamp to value < number_of_columns @@ -895,7 +889,7 @@ namespace Avalonia.Controls { foreach (DictionaryEntry e in spanStore) { - SpanKey key = (SpanKey)e.Key; + GridSpanKey key = (GridSpanKey)e.Key; double requestedSize = (double)e.Value; EnsureMinSizeInDefinitionRange( @@ -928,7 +922,7 @@ namespace Avalonia.Controls store = new Hashtable(); } - SpanKey key = new SpanKey(start, count, u); + GridSpanKey key = new GridSpanKey(start, count, u); object o = store[key]; if (o == null @@ -2127,7 +2121,6 @@ namespace Avalonia.Controls } } - /// /// Synchronized ShowGridLines property with the state of the grid's visual collection /// by adding / removing GridLinesRenderer visual. @@ -2213,7 +2206,7 @@ namespace Avalonia.Controls /// true if one or both of x and y are null, in which case result holds /// the relative sort order. /// - private static bool CompareNullRefs(object x, object y, out int result) + internal static bool CompareNullRefs(object x, object y, out int result) { result = 2; @@ -2328,497 +2321,5 @@ namespace Avalonia.Controls return def.UserSize.Value * scale; } } - - /// - /// LayoutTimeSizeType is used internally and reflects layout-time size type. - /// - [System.Flags] - internal enum LayoutTimeSizeType : byte - { - None = 0x00, - Pixel = 0x01, - Auto = 0x02, - Star = 0x04, - } - - /// - /// CellCache stored calculated values of - /// 1. attached cell positioning properties; - /// 2. size type; - /// 3. index of a next cell in the group; - /// - private struct CellCache - { - internal int ColumnIndex; - internal int RowIndex; - internal int ColumnSpan; - internal int RowSpan; - internal LayoutTimeSizeType SizeTypeU; - internal LayoutTimeSizeType SizeTypeV; - internal int Next; - internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } } - internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } } - internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } } - internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } } - } - - /// - /// Helper class for representing a key for a span in hashtable. - /// - private class SpanKey - { - /// - /// Constructor. - /// - /// Starting index of the span. - /// Span count. - /// true for columns; false for rows. - internal SpanKey(int start, int count, bool u) - { - _start = start; - _count = count; - _u = u; - } - - /// - /// - /// - public override int GetHashCode() - { - int hash = (_start ^ (_count << 2)); - - if (_u) hash &= 0x7ffffff; - else hash |= 0x8000000; - - return (hash); - } - - /// - /// - /// - public override bool Equals(object obj) - { - SpanKey sk = obj as SpanKey; - return (sk != null - && sk._start == _start - && sk._count == _count - && sk._u == _u); - } - - /// - /// Returns start index of the span. - /// - internal int Start { get { return (_start); } } - - /// - /// Returns span count. - /// - internal int Count { get { return (_count); } } - - /// - /// Returns true if this is a column span. - /// false if this is a row span. - /// - internal bool U { get { return (_u); } } - - private int _start; - private int _count; - private bool _u; - } - - /// - /// SpanPreferredDistributionOrderComparer. - /// - private class SpanPreferredDistributionOrderComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - if (definitionX.UserSize.IsAuto) - { - if (definitionY.UserSize.IsAuto) - { - result = definitionX.MinSize.CompareTo(definitionY.MinSize); - } - else - { - result = -1; - } - } - else - { - if (definitionY.UserSize.IsAuto) - { - result = +1; - } - else - { - result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize); - } - } - } - - return result; - } - } - - /// - /// SpanMaxDistributionOrderComparer. - /// - private class SpanMaxDistributionOrderComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - if (definitionX.UserSize.IsAuto) - { - if (definitionY.UserSize.IsAuto) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - else - { - result = +1; - } - } - else - { - if (definitionY.UserSize.IsAuto) - { - result = -1; - } - else - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - } - } - - return result; - } - } - - /// - /// RoundingErrorIndexComparer. - /// - private class RoundingErrorIndexComparer : IComparer - { - private readonly double[] errors; - - internal RoundingErrorIndexComparer(double[] errors) - { - Contract.Requires(errors != null); - this.errors = errors; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - int result; - - if (!CompareNullRefs(indexX, indexY, out result)) - { - double errorX = errors[indexX.Value]; - double errorY = errors[indexY.Value]; - result = errorX.CompareTo(errorY); - } - - return result; - } - } - - /// - /// MinRatioComparer. - /// Sort by w/min (stored in MeasureSize), descending. - /// We query the list from the back, i.e. in ascending order of w/min. - /// - private class MinRatioComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionY, definitionX, out result)) - { - result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); - } - - return result; - } - } - - /// - /// MaxRatioComparer. - /// Sort by w/max (stored in SizeCache), ascending. - /// We query the list from the back, i.e. in descending order of w/max. - /// - private class MaxRatioComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - - return result; - } - } - - /// - /// StarWeightComparer. - /// Sort by *-weight (stored in MeasureSize), ascending. - /// - private class StarWeightComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); - } - - return result; - } - } - - /// - /// MinRatioIndexComparer. - /// - private class MinRatioIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal MinRatioIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!CompareNullRefs(definitionY, definitionX, out result)) - { - result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); - } - - return result; - } - } - - /// - /// MaxRatioIndexComparer. - /// - private class MaxRatioIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal MaxRatioIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - - return result; - } - } - - /// - /// MaxRatioIndexComparer. - /// - private class StarWeightIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal StarWeightIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); - } - - return result; - } - } - - /// - /// Helper to render grid lines. - /// - private class GridLinesRenderer : Control - { - /// - /// Static initialization - /// - static GridLinesRenderer() - { - var oddDashArray = new List(); - oddDashArray.Add(_dashLength); - oddDashArray.Add(_dashLength); - var ds1 = new DashStyle(oddDashArray, 0); - _oddDashPen = new Pen(Brushes.Blue, - _penWidth, - lineCap: PenLineCap.Flat, - dashStyle: ds1); - - var evenDashArray = new List(); - evenDashArray.Add(_dashLength); - evenDashArray.Add(_dashLength); - var ds2 = new DashStyle(evenDashArray, 0); - _evenDashPen = new Pen(Brushes.Yellow, - _penWidth, - lineCap: PenLineCap.Flat, - dashStyle: ds2); - } - - /// - /// UpdateRenderBounds. - /// - public override void Render(DrawingContext drawingContext) - { - var grid = this.GetVisualParent(); - - if (grid == null - || !grid.ShowGridLines - || grid.IsTrivialGrid) - { - return; - } - - for (int i = 1; i < grid.ColumnDefinitions.Count; ++i) - { - DrawGridLine( - drawingContext, - grid.ColumnDefinitions[i].FinalOffset, 0.0, - grid.ColumnDefinitions[i].FinalOffset, _lastArrangeSize.Height); - } - - for (int i = 1; i < grid.RowDefinitions.Count; ++i) - { - DrawGridLine( - drawingContext, - 0.0, grid.RowDefinitions[i].FinalOffset, - _lastArrangeSize.Width, grid.RowDefinitions[i].FinalOffset); - } - } - - /// - /// Draw single hi-contrast line. - /// - private static void DrawGridLine( - DrawingContext drawingContext, - double startX, - double startY, - double endX, - double endY) - { - var start = new Point(startX, startY); - var end = new Point(endX, endY); - drawingContext.DrawLine(_oddDashPen, start, end); - drawingContext.DrawLine(_evenDashPen, start, end); - } - - internal void UpdateRenderBounds(Size arrangeSize) - { - _lastArrangeSize = arrangeSize; - this.InvalidateVisual(); - } - - private static Size _lastArrangeSize; - private const double _dashLength = 4.0; // - private const double _penWidth = 1.0; // - private static readonly Pen _oddDashPen; // first pen to draw dash - private static readonly Pen _evenDashPen; // second pen to draw dash - } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/GridCellCache.cs b/src/Avalonia.Controls/Grid/GridCellCache.cs new file mode 100644 index 0000000000..81edf72ca5 --- /dev/null +++ b/src/Avalonia.Controls/Grid/GridCellCache.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Avalonia.Controls +{ + /// + /// CellCache stored calculated values of + /// 1. attached cell positioning properties; + /// 2. size type; + /// 3. index of a next cell in the group; + /// + internal struct GridCellCache + { + internal int ColumnIndex; + internal int RowIndex; + internal int ColumnSpan; + internal int RowSpan; + internal LayoutTimeSizeType SizeTypeU; + internal LayoutTimeSizeType SizeTypeV; + internal int Next; + internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } } + internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } } + internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } } + internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/Grid/GridLength.cs similarity index 100% rename from src/Avalonia.Controls/GridLength.cs rename to src/Avalonia.Controls/Grid/GridLength.cs diff --git a/src/Avalonia.Controls/Grid/GridLinesRenderer.cs b/src/Avalonia.Controls/Grid/GridLinesRenderer.cs new file mode 100644 index 0000000000..338b8c40c1 --- /dev/null +++ b/src/Avalonia.Controls/Grid/GridLinesRenderer.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Avalonia.VisualTree; +using Avalonia.Media; + +namespace Avalonia.Controls +{ + internal class GridLinesRenderer : Control + { + /// + /// Static initialization + /// + static GridLinesRenderer() + { + var oddDashArray = new List(); + oddDashArray.Add(_dashLength); + oddDashArray.Add(_dashLength); + var ds1 = new DashStyle(oddDashArray, 0); + _oddDashPen = new Pen(Brushes.Blue, + _penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds1); + + var evenDashArray = new List(); + evenDashArray.Add(_dashLength); + evenDashArray.Add(_dashLength); + var ds2 = new DashStyle(evenDashArray, 0); + _evenDashPen = new Pen(Brushes.Yellow, + _penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds2); + } + + /// + /// UpdateRenderBounds. + /// + public override void Render(DrawingContext drawingContext) + { + var grid = this.GetVisualParent(); + + if (grid == null + || !grid.ShowGridLines + || grid.IsTrivialGrid) + { + return; + } + + for (int i = 1; i < grid.ColumnDefinitions.Count; ++i) + { + DrawGridLine( + drawingContext, + grid.ColumnDefinitions[i].FinalOffset, 0.0, + grid.ColumnDefinitions[i].FinalOffset, _lastArrangeSize.Height); + } + + for (int i = 1; i < grid.RowDefinitions.Count; ++i) + { + DrawGridLine( + drawingContext, + 0.0, grid.RowDefinitions[i].FinalOffset, + _lastArrangeSize.Width, grid.RowDefinitions[i].FinalOffset); + } + } + + /// + /// Draw single hi-contrast line. + /// + private static void DrawGridLine( + DrawingContext drawingContext, + double startX, + double startY, + double endX, + double endY) + { + var start = new Point(startX, startY); + var end = new Point(endX, endY); + drawingContext.DrawLine(_oddDashPen, start, end); + drawingContext.DrawLine(_evenDashPen, start, end); + } + + internal void UpdateRenderBounds(Size arrangeSize) + { + _lastArrangeSize = arrangeSize; + this.InvalidateVisual(); + } + + private static Size _lastArrangeSize; + private const double _dashLength = 4.0; // + private const double _penWidth = 1.0; // + private static readonly Pen _oddDashPen; // first pen to draw dash + private static readonly Pen _evenDashPen; // second pen to draw dash + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/GridSpanKey.cs b/src/Avalonia.Controls/Grid/GridSpanKey.cs new file mode 100644 index 0000000000..cd48bc1265 --- /dev/null +++ b/src/Avalonia.Controls/Grid/GridSpanKey.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Avalonia.Controls +{ + /// + /// Helper class for representing a key for a span in hashtable. + /// + internal class GridSpanKey + { + /// + /// Constructor. + /// + /// Starting index of the span. + /// Span count. + /// true for columns; false for rows. + internal GridSpanKey(int start, int count, bool u) + { + _start = start; + _count = count; + _u = u; + } + + /// + /// + /// + public override int GetHashCode() + { + int hash = (_start ^ (_count << 2)); + + if (_u) hash &= 0x7ffffff; + else hash |= 0x8000000; + + return (hash); + } + + /// + /// + /// + public override bool Equals(object obj) + { + GridSpanKey sk = obj as GridSpanKey; + return (sk != null + && sk._start == _start + && sk._count == _count + && sk._u == _u); + } + + /// + /// Returns start index of the span. + /// + internal int Start { get { return (_start); } } + + /// + /// Returns span count. + /// + internal int Count { get { return (_count); } } + + /// + /// Returns true if this is a column span. + /// false if this is a row span. + /// + internal bool U { get { return (_u); } } + + private int _start; + private int _count; + private bool _u; + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/Grid/GridSplitter.cs similarity index 100% rename from src/Avalonia.Controls/GridSplitter.cs rename to src/Avalonia.Controls/Grid/GridSplitter.cs diff --git a/src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs b/src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs new file mode 100644 index 0000000000..1432c29ae5 --- /dev/null +++ b/src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Avalonia.Controls +{ + /// + /// LayoutTimeSizeType is used internally and reflects layout-time size type. + /// + [System.Flags] + internal enum LayoutTimeSizeType : byte + { + None = 0x00, + Pixel = 0x01, + Auto = 0x02, + Star = 0x04, + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/MaxRatioComparer.cs b/src/Avalonia.Controls/Grid/MaxRatioComparer.cs new file mode 100644 index 0000000000..fe7eb356ec --- /dev/null +++ b/src/Avalonia.Controls/Grid/MaxRatioComparer.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; + +namespace Avalonia.Controls +{ + + /// + /// MaxRatioComparer. + /// Sort by w/max (stored in SizeCache), ascending. + /// We query the list from the back, i.e. in descending order of w/max. + /// + internal class MaxRatioComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs b/src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs new file mode 100644 index 0000000000..01bcf85b27 --- /dev/null +++ b/src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; + +namespace Avalonia.Controls +{ + internal class MaxRatioIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal MaxRatioIndexComparer(DefinitionBase[] definitions) + { + Contract.Requires(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/MinRatioComparer.cs b/src/Avalonia.Controls/Grid/MinRatioComparer.cs new file mode 100644 index 0000000000..8e0fa0a282 --- /dev/null +++ b/src/Avalonia.Controls/Grid/MinRatioComparer.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; + +namespace Avalonia.Controls +{ + /// + /// MinRatioComparer. + /// Sort by w/min (stored in MeasureSize), descending. + /// We query the list from the back, i.e. in ascending order of w/min. + /// + internal class MinRatioComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!Grid.CompareNullRefs(definitionY, definitionX, out result)) + { + result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs b/src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs new file mode 100644 index 0000000000..01add324c1 --- /dev/null +++ b/src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; + +namespace Avalonia.Controls +{ + internal class MinRatioIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal MinRatioIndexComparer(DefinitionBase[] definitions) + { + Contract.Requires(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!Grid.CompareNullRefs(definitionY, definitionX, out result)) + { + result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs b/src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs new file mode 100644 index 0000000000..a0a9035384 --- /dev/null +++ b/src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; + +namespace Avalonia.Controls +{ + internal class RoundingErrorIndexComparer : IComparer + { + private readonly double[] errors; + + internal RoundingErrorIndexComparer(double[] errors) + { + Contract.Requires(errors != null); + this.errors = errors; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + int result; + + if (!Grid.CompareNullRefs(indexX, indexY, out result)) + { + double errorX = errors[indexX.Value]; + double errorY = errors[indexY.Value]; + result = errorX.CompareTo(errorY); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs b/src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs new file mode 100644 index 0000000000..f6fbf4d2bb --- /dev/null +++ b/src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; + +namespace Avalonia.Controls +{ + internal class SpanMaxDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) + { + if (definitionX.UserSize.IsAuto) + { + if (definitionY.UserSize.IsAuto) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + else + { + result = +1; + } + } + else + { + if (definitionY.UserSize.IsAuto) + { + result = -1; + } + else + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs b/src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs new file mode 100644 index 0000000000..1adb62590c --- /dev/null +++ b/src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; + +namespace Avalonia.Controls +{ + internal class SpanPreferredDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) + { + if (definitionX.UserSize.IsAuto) + { + if (definitionY.UserSize.IsAuto) + { + result = definitionX.MinSize.CompareTo(definitionY.MinSize); + } + else + { + result = -1; + } + } + else + { + if (definitionY.UserSize.IsAuto) + { + result = +1; + } + else + { + result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize); + } + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/StarWeightComparer.cs b/src/Avalonia.Controls/Grid/StarWeightComparer.cs new file mode 100644 index 0000000000..216f97f2c1 --- /dev/null +++ b/src/Avalonia.Controls/Grid/StarWeightComparer.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; + +namespace Avalonia.Controls +{ + /// + /// StarWeightComparer. + /// Sort by *-weight (stored in MeasureSize), ascending. + /// + internal class StarWeightComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs b/src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs new file mode 100644 index 0000000000..da5148e9a5 --- /dev/null +++ b/src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; + +namespace Avalonia.Controls +{ + + internal class StarWeightIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal StarWeightIndexComparer(DefinitionBase[] definitions) + { + Contract.Requires(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); + } + + return result; + } + } +} \ No newline at end of file From 34d2258aef257b5dcbaab0490138cab400e6dd29 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 21:00:50 +0800 Subject: [PATCH 043/130] Re-run CI --- src/Avalonia.Controls/Grid/Grid.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/Grid/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs index a14e3f0d01..9291426c7f 100644 --- a/src/Avalonia.Controls/Grid/Grid.cs +++ b/src/Avalonia.Controls/Grid/Grid.cs @@ -21,6 +21,7 @@ namespace Avalonia.Controls { public class Grid : Panel { + internal bool CellsStructureDirty = true; internal bool SizeToContentU; internal bool SizeToContentV; From 5b9755329fbb92d5336bae80e1f101d60cc7db6c Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 27 May 2019 21:08:49 +0800 Subject: [PATCH 044/130] Fix GridLineRenderer's DashStyles. --- src/Avalonia.Controls/Grid/GridLinesRenderer.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Grid/GridLinesRenderer.cs b/src/Avalonia.Controls/Grid/GridLinesRenderer.cs index 338b8c40c1..0f7f5963d2 100644 --- a/src/Avalonia.Controls/Grid/GridLinesRenderer.cs +++ b/src/Avalonia.Controls/Grid/GridLinesRenderer.cs @@ -14,19 +14,15 @@ namespace Avalonia.Controls /// static GridLinesRenderer() { - var oddDashArray = new List(); - oddDashArray.Add(_dashLength); - oddDashArray.Add(_dashLength); - var ds1 = new DashStyle(oddDashArray, 0); + var dashArray = new List() { _dashLength, _dashLength }; + + var ds1 = new DashStyle(dashArray, 0); _oddDashPen = new Pen(Brushes.Blue, _penWidth, lineCap: PenLineCap.Flat, dashStyle: ds1); - var evenDashArray = new List(); - evenDashArray.Add(_dashLength); - evenDashArray.Add(_dashLength); - var ds2 = new DashStyle(evenDashArray, 0); + var ds2 = new DashStyle(dashArray, _dashLength); _evenDashPen = new Pen(Brushes.Yellow, _penWidth, lineCap: PenLineCap.Flat, From 5b62d7c859bf73fc7a8db87aa06b3d0282c257b1 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 28 May 2019 00:11:42 +0800 Subject: [PATCH 045/130] More reorganizing + refactoring... --- .../{ => Grid}/ColumnDefinition.cs | 0 .../{ => Grid}/ColumnDefinitions.cs | 0 src/Avalonia.Controls/Grid/Grid.cs | 453 ++++++++---------- .../{ => Grid}/RowDefinition.cs | 0 .../{ => Grid}/RowDefinitions.cs | 0 5 files changed, 207 insertions(+), 246 deletions(-) rename src/Avalonia.Controls/{ => Grid}/ColumnDefinition.cs (100%) rename src/Avalonia.Controls/{ => Grid}/ColumnDefinitions.cs (100%) rename src/Avalonia.Controls/{ => Grid}/RowDefinition.cs (100%) rename src/Avalonia.Controls/{ => Grid}/RowDefinitions.cs (100%) diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/Grid/ColumnDefinition.cs similarity index 100% rename from src/Avalonia.Controls/ColumnDefinition.cs rename to src/Avalonia.Controls/Grid/ColumnDefinition.cs diff --git a/src/Avalonia.Controls/ColumnDefinitions.cs b/src/Avalonia.Controls/Grid/ColumnDefinitions.cs similarity index 100% rename from src/Avalonia.Controls/ColumnDefinitions.cs rename to src/Avalonia.Controls/Grid/ColumnDefinitions.cs diff --git a/src/Avalonia.Controls/Grid/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs index 9291426c7f..5def043136 100644 --- a/src/Avalonia.Controls/Grid/Grid.cs +++ b/src/Avalonia.Controls/Grid/Grid.cs @@ -21,31 +21,58 @@ namespace Avalonia.Controls { public class Grid : Panel { - internal bool CellsStructureDirty = true; internal bool SizeToContentU; internal bool SizeToContentV; internal bool HasStarCellsU; internal bool HasStarCellsV; internal bool HasGroup3CellsInAutoRows; - internal bool ColumnDefinitionsDirty = true; - internal bool RowDefinitionsDirty = true; - - // index of the first cell in first cell group + internal bool DefinitionsDirty; + internal bool IsTrivialGrid => (_definitionsU?.Length <= 1) && + (_definitionsV?.Length <= 1); internal int CellGroup1; - - // index of the first cell in second cell group internal int CellGroup2; - - // index of the first cell in third cell group internal int CellGroup3; - - // index of the first cell in fourth cell group internal int CellGroup4; + /// + /// Helper for Comparer methods. + /// + /// + /// true if one or both of x and y are null, in which case result holds + /// the relative sort order. + /// + internal static bool CompareNullRefs(object x, object y, out int result) + { + result = 2; + + if (x == null) + { + if (y == null) + { + result = 0; + } + else + { + result = -1; + } + } + else + { + if (y == null) + { + result = 1; + } + } + + return (result != 2); + } + // temporary array used during layout for various purposes // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length) private DefinitionBase[] _tempDefinitions; + + private GridLinesRenderer _gridLinesRenderer; // Keeps track of definition indices. @@ -55,8 +82,10 @@ namespace Avalonia.Controls // Stores unrounded values and rounding errors during layout rounding. private double[] _roundingErrors; - private DefinitionBase[] _definitionsU; - private DefinitionBase[] _definitionsV; + private ColumnDefinitions _columnDefinitions; + private RowDefinitions _rowDefinitions; + private DefinitionBase[] _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + private DefinitionBase[] _definitionsV = new DefinitionBase[1] { new RowDefinition() }; // 5 is an arbitrary constant chosen to end the measure loop private const int _layoutLoopMaxCount = 5; @@ -67,6 +96,78 @@ namespace Avalonia.Controls private static readonly IComparer _maxRatioComparer; private static readonly IComparer _starWeightComparer; + /// + /// Helper accessor to layout time array of definitions. + /// + private DefinitionBase[] TempDefinitions + { + get + { + int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length) * 2; + + if (_tempDefinitions == null + || _tempDefinitions.Length < requiredLength) + { + WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(_tempDefinitionsDataSlot); + if (tempDefinitionsWeakRef == null) + { + _tempDefinitions = new DefinitionBase[requiredLength]; + Thread.SetData(_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions)); + } + else + { + _tempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; + if (_tempDefinitions == null + || _tempDefinitions.Length < requiredLength) + { + _tempDefinitions = new DefinitionBase[requiredLength]; + tempDefinitionsWeakRef.Target = _tempDefinitions; + } + } + } + return (_tempDefinitions); + } + } + + /// + /// Helper accessor to definition indices. + /// + private int[] DefinitionIndices + { + get + { + int requiredLength = Math.Max(Math.Max(_definitionsU.Length, _definitionsV.Length), 1) * 2; + + if (_definitionIndices == null || _definitionIndices.Length < requiredLength) + { + _definitionIndices = new int[requiredLength]; + } + + return _definitionIndices; + } + } + + /// + /// Helper accessor to rounding errors. + /// + private double[] RoundingErrors + { + get + { + int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length); + + if (_roundingErrors == null && requiredLength == 0) + { + _roundingErrors = new double[1]; + } + else if (_roundingErrors == null || _roundingErrors.Length < requiredLength) + { + _roundingErrors = new double[requiredLength]; + } + return _roundingErrors; + } + } + static Grid() { ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); @@ -128,6 +229,86 @@ namespace Avalonia.Controls set { SetValue(ShowGridLinesProperty, value); } } + /// + /// Gets or sets the columns definitions for the grid. + /// + public ColumnDefinitions ColumnDefinitions + { + get + { + if (_columnDefinitions == null) + { + ColumnDefinitions = new ColumnDefinitions(); + } + + return _columnDefinitions; + } + set + { + _columnDefinitions = value; + _columnDefinitions.TrackItemPropertyChanged(_ => Invalidate()); + DefinitionsDirty = true; + + if (_columnDefinitions.Count > 0) + _definitionsU = _columnDefinitions.Cast().ToArray(); + + _columnDefinitions.CollectionChanged += delegate + { + if (_columnDefinitions.Count == 0) + { + _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; + } + else + { + _definitionsU = _columnDefinitions.Cast().ToArray(); + DefinitionsDirty = true; + } + Invalidate(); + }; + } + } + + /// + /// Gets or sets the row definitions for the grid. + /// + public RowDefinitions RowDefinitions + { + get + { + if (_rowDefinitions == null) + { + RowDefinitions = new RowDefinitions(); + } + + return _rowDefinitions; + } + set + { + _rowDefinitions = value; + _rowDefinitions.TrackItemPropertyChanged(_ => Invalidate()); + + DefinitionsDirty = true; + + if (_rowDefinitions.Count > 0) + _definitionsV = _rowDefinitions.Cast().ToArray(); + + _rowDefinitions.CollectionChanged += delegate + { + if (_rowDefinitions.Count == 0) + { + _definitionsV = new DefinitionBase[1] { new RowDefinition() }; + } + else + { + _definitionsV = _rowDefinitions.Cast().ToArray(); + DefinitionsDirty = true; + } + Invalidate(); + }; + } + } + + /// /// Gets the value of the Column attached property for a control. /// @@ -218,95 +399,6 @@ namespace Avalonia.Controls element.SetValue(RowSpanProperty, value); } - private ColumnDefinitions _columnDefinitions; - private RowDefinitions _rowDefinitions; - - /// - /// Gets or sets the columns definitions for the grid. - /// - public ColumnDefinitions ColumnDefinitions - { - get - { - if (_columnDefinitions == null) - { - ColumnDefinitions = new ColumnDefinitions(); - } - - return _columnDefinitions; - } - set - { - _columnDefinitions = value; - _columnDefinitions.TrackItemPropertyChanged(_ => Invalidate()); - ColumnDefinitionsDirty = true; - - if (_columnDefinitions.Count > 0) - _definitionsU = _columnDefinitions.Cast().ToArray(); - else - _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - - _columnDefinitions.CollectionChanged += (_, e) => - { - if (_columnDefinitions.Count == 0) - { - _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - } - else - { - _definitionsU = _columnDefinitions.Cast().ToArray(); - ColumnDefinitionsDirty = true; - } - Invalidate(); - }; - } - } - - /// - /// Gets or sets the row definitions for the grid. - /// - public RowDefinitions RowDefinitions - { - get - { - if (_rowDefinitions == null) - { - RowDefinitions = new RowDefinitions(); - } - - return _rowDefinitions; - } - set - { - _rowDefinitions = value; - _rowDefinitions.TrackItemPropertyChanged(_ => Invalidate()); - - RowDefinitionsDirty = true; - - if (_rowDefinitions.Count > 0) - _definitionsV = _rowDefinitions.Cast().ToArray(); - else - _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - - _rowDefinitions.CollectionChanged += (_, e) => - { - if (_rowDefinitions.Count == 0) - { - _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - } - else - { - _definitionsV = _rowDefinitions.Cast().ToArray(); - RowDefinitionsDirty = true; - } - Invalidate(); - }; - } - } - - internal bool IsTrivialGrid => (_definitionsU?.Length <= 1) && - (_definitionsV?.Length <= 1); - /// /// Content measurement. /// @@ -341,7 +433,7 @@ namespace Avalonia.Controls bool sizeToContentV = double.IsPositiveInfinity(constraint.Height); // Clear index information and rounding errors - if (RowDefinitionsDirty || ColumnDefinitionsDirty) + if (DefinitionsDirty) { if (_definitionIndices != null) { @@ -357,12 +449,11 @@ namespace Avalonia.Controls _roundingErrors = null; } } + + DefinitionsDirty = false; } - ValidateColumnDefinitionsStructure(); ValidateDefinitionsLayout(_definitionsU, sizeToContentU); - - ValidateRowDefinitionsStructure(); ValidateDefinitionsLayout(_definitionsV, sizeToContentV); CellsStructureDirty |= (SizeToContentU != sizeToContentU) @@ -453,27 +544,6 @@ namespace Avalonia.Controls return (gridDesiredSize); } - private void ValidateColumnDefinitionsStructure() - { - if (ColumnDefinitionsDirty) - { - if (_definitionsU == null) - _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - ColumnDefinitionsDirty = false; - } - } - - private void ValidateRowDefinitionsStructure() - { - if (RowDefinitionsDirty) - { - if (_definitionsV == null) - _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - - RowDefinitionsDirty = false; - } - } - /// /// Content arrangement. /// @@ -554,12 +624,12 @@ namespace Avalonia.Controls /// /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data. /// - internal double GetFinalColumnDefinitionWidth(int columnIndex) + private double GetFinalColumnDefinitionWidth(int columnIndex) { double value = 0.0; // actual value calculations require structure to be up-to-date - if (!ColumnDefinitionsDirty) + if (!DefinitionsDirty) { value = _definitionsU[(columnIndex + 1) % _definitionsU.Length].FinalOffset; if (columnIndex != 0) { value -= _definitionsU[columnIndex].FinalOffset; } @@ -573,12 +643,12 @@ namespace Avalonia.Controls /// /// Used from public RowDefinition ActualHeight. Calculates final height using offset data. /// - internal double GetFinalRowDefinitionHeight(int rowIndex) + private double GetFinalRowDefinitionHeight(int rowIndex) { double value = 0.0; // actual value calculations require structure to be up-to-date - if (!RowDefinitionsDirty) + if (!DefinitionsDirty) { value = _definitionsV[(rowIndex + 1) % _definitionsV.Length].FinalOffset; if (rowIndex != 0) { value -= _definitionsV[rowIndex].FinalOffset; } @@ -589,7 +659,7 @@ namespace Avalonia.Controls /// /// Invalidates grid caches and makes the grid dirty for measure. /// - internal void Invalidate() + private void Invalidate() { CellsStructureDirty = true; InvalidateMeasure(); @@ -2111,14 +2181,11 @@ namespace Avalonia.Controls /// private void SetValid() { - if (IsTrivialGrid) + if (IsTrivialGrid && _tempDefinitions != null) { - if (_tempDefinitions != null) - { - // TempDefinitions has to be cleared to avoid "memory leaks" - Array.Clear(_tempDefinitions, 0, Math.Max(_definitionsU.Length, _definitionsV.Length)); - _tempDefinitions = null; - } + // TempDefinitions has to be cleared to avoid "memory leaks" + Array.Clear(_tempDefinitions, 0, Math.Max(_definitionsU.Length, _definitionsV.Length)); + _tempDefinitions = null; } } @@ -2171,7 +2238,6 @@ namespace Avalonia.Controls return newValue; } - private static int ValidateColumn(AvaloniaObject o, int value) { if (value < 0) @@ -2200,115 +2266,10 @@ namespace Avalonia.Controls } } - /// - /// Helper for Comparer methods. - /// - /// - /// true if one or both of x and y are null, in which case result holds - /// the relative sort order. - /// - internal static bool CompareNullRefs(object x, object y, out int result) - { - result = 2; - - if (x == null) - { - if (y == null) - { - result = 0; - } - else - { - result = -1; - } - } - else - { - if (y == null) - { - result = 1; - } - } - - return (result != 2); - } - - /// - /// Helper accessor to layout time array of definitions. - /// - private DefinitionBase[] TempDefinitions - { - get - { - int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length) * 2; - - if (_tempDefinitions == null - || _tempDefinitions.Length < requiredLength) - { - WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(_tempDefinitionsDataSlot); - if (tempDefinitionsWeakRef == null) - { - _tempDefinitions = new DefinitionBase[requiredLength]; - Thread.SetData(_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions)); - } - else - { - _tempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; - if (_tempDefinitions == null - || _tempDefinitions.Length < requiredLength) - { - _tempDefinitions = new DefinitionBase[requiredLength]; - tempDefinitionsWeakRef.Target = _tempDefinitions; - } - } - } - return (_tempDefinitions); - } - } - - /// - /// Helper accessor to definition indices. - /// - private int[] DefinitionIndices - { - get - { - int requiredLength = Math.Max(Math.Max(_definitionsU.Length, _definitionsV.Length), 1) * 2; - - if (_definitionIndices == null || _definitionIndices.Length < requiredLength) - { - _definitionIndices = new int[requiredLength]; - } - - return _definitionIndices; - } - } - - /// - /// Helper accessor to rounding errors. - /// - private double[] RoundingErrors - { - get - { - int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length); - - if (_roundingErrors == null && requiredLength == 0) - { - _roundingErrors = new double[1]; - } - else if (_roundingErrors == null || _roundingErrors.Length < requiredLength) - { - _roundingErrors = new double[requiredLength]; - } - return _roundingErrors; - } - } - /// /// Returns *-weight, adjusted for scale computed during Phase 1 /// - static double StarWeight(DefinitionBase def, double scale) + private static double StarWeight(DefinitionBase def, double scale) { if (scale < 0.0) { diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/Grid/RowDefinition.cs similarity index 100% rename from src/Avalonia.Controls/RowDefinition.cs rename to src/Avalonia.Controls/Grid/RowDefinition.cs diff --git a/src/Avalonia.Controls/RowDefinitions.cs b/src/Avalonia.Controls/Grid/RowDefinitions.cs similarity index 100% rename from src/Avalonia.Controls/RowDefinitions.cs rename to src/Avalonia.Controls/Grid/RowDefinitions.cs From 6dba3467b77773570466bc5089e6998172bdc016 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 28 May 2019 00:28:52 +0800 Subject: [PATCH 046/130] Revert `ShowGridLines` default value. --- src/Avalonia.Controls/Grid/Grid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Grid/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs index 5def043136..449b92ac52 100644 --- a/src/Avalonia.Controls/Grid/Grid.cs +++ b/src/Avalonia.Controls/Grid/Grid.cs @@ -218,7 +218,7 @@ namespace Avalonia.Controls public static readonly StyledProperty ShowGridLinesProperty = AvaloniaProperty.Register( nameof(ShowGridLines), - defaultValue: true); + defaultValue: false); /// /// ShowGridLines property. From ad45848a294a3ba7af3b98bcf104e9c802d20994 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 28 May 2019 16:35:45 +0800 Subject: [PATCH 047/130] Implement and make SharedSizeScopes work :) --- .../Grid/ColumnDefinition.cs | 6 +- src/Avalonia.Controls/Grid/DefinitionBase.cs | 286 ++++++++++++++++-- src/Avalonia.Controls/Grid/Grid.cs | 114 +++++-- src/Avalonia.Controls/Grid/RowDefinition.cs | 6 +- src/Avalonia.Controls/Grid/SharedSizeScope.cs | 43 +++ src/Avalonia.Controls/Grid/SharedSizeState.cs | 209 +++++++++++++ 6 files changed, 606 insertions(+), 58 deletions(-) create mode 100644 src/Avalonia.Controls/Grid/SharedSizeScope.cs create mode 100644 src/Avalonia.Controls/Grid/SharedSizeState.cs diff --git a/src/Avalonia.Controls/Grid/ColumnDefinition.cs b/src/Avalonia.Controls/Grid/ColumnDefinition.cs index 56a6d41b7b..015484dbcc 100644 --- a/src/Avalonia.Controls/Grid/ColumnDefinition.cs +++ b/src/Avalonia.Controls/Grid/ColumnDefinition.cs @@ -88,10 +88,10 @@ namespace Avalonia.Controls set { SetValue(WidthProperty, value); } } - internal override GridLength UserSize => this.Width; + internal override GridLength UserSizeValueCache => this.Width; - internal override double UserMinSize => this.MinWidth; + internal override double UserMinSizeValueCache => this.MinWidth; - internal override double UserMaxSize => this.MaxWidth; + internal override double UserMaxSizeValueCache => this.MaxWidth; } } diff --git a/src/Avalonia.Controls/Grid/DefinitionBase.cs b/src/Avalonia.Controls/Grid/DefinitionBase.cs index ad2f33a18a..e21d55c1f6 100644 --- a/src/Avalonia.Controls/Grid/DefinitionBase.cs +++ b/src/Avalonia.Controls/Grid/DefinitionBase.cs @@ -3,9 +3,7 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using Avalonia.Utilities; namespace Avalonia.Controls { @@ -14,6 +12,26 @@ namespace Avalonia.Controls /// public abstract class DefinitionBase : AvaloniaObject { + /// + /// Static ctor. Used for static registration of properties. + /// + static DefinitionBase() + { + SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); + } + internal bool UseSharedMinimum { get; set; } + internal bool LayoutWasUpdated { get; set; } + + private int _parentIndex = -1; // this instance's index in parent's children collection + private LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" + private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's + private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure + private double _sizeCache; // cache used for various purposes (sorting, caching, etc) during calculations + private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) + internal SharedSizeScope _privateSharedSizeScope; + private SharedSizeState _sharedState; // reference to shared state object this instance is registered with + private bool _successUpdateSharedScope; + /// /// Defines the property. /// @@ -28,31 +46,182 @@ namespace Avalonia.Controls get { return GetValue(SharedSizeGroupProperty); } set { SetValue(SharedSizeGroupProperty, value); } } + /// + /// Callback to notify about entering model tree. + /// + internal void OnEnterParentTree(Grid grid, int index) + { + Parent = grid; + _parentIndex = index; + } + + internal void UpdateSharedScope() + { + if (_sharedState == null & + SharedSizeGroup != null & + Parent?.sharedSizeScope != null & + !_successUpdateSharedScope) + { + _privateSharedSizeScope = Parent.sharedSizeScope; + _sharedState = _privateSharedSizeScope.EnsureSharedState(SharedSizeGroup); + _sharedState.AddMember(this); + _successUpdateSharedScope = true; + } + } + + internal Grid Parent { get; set; } /// - /// Internal helper to access up-to-date UserSize property value. + /// Callback to notify about exitting model tree. /// - internal abstract GridLength UserSize { get; } + internal void OnExitParentTree() + { + _offset = 0; + if (_sharedState != null) + { + _sharedState.RemoveMember(this); + _sharedState = null; + } + } /// - /// Internal helper to access up-to-date UserMinSize property value. + /// Performs action preparing definition to enter layout calculation mode. /// - internal abstract double UserMinSize { get; } + internal void OnBeforeLayout(Grid grid) + { + if (SharedSizeGroup != null) + UpdateSharedScope(); + + // reset layout state. + _minSize = 0; + LayoutWasUpdated = true; + + // defer verification for shared definitions + if (_sharedState != null) + { + _sharedState.EnsureDeferredValidation(grid); + } + } /// - /// Internal helper to access up-to-date UserMaxSize property value. + /// Updates min size. + /// + /// New size. + internal void UpdateMinSize(double minSize) + { + _minSize = Math.Max(_minSize, minSize); + } + + /// + /// Sets min size. + /// + /// New size. + internal void SetMinSize(double minSize) + { + _minSize = minSize; + } + + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal void OnUserSizePropertyChanged(AvaloniaPropertyChangedEventArgs e) + { + _sharedState?.Invalidate(); + } + + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserMinSizePropertyValueValid(object value) + { + double v = (double)value; + return (!double.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); + } + + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static void OnUserMaxSizePropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) + { + Grid parentGrid = (Grid)definition.Parent; + parentGrid.InvalidateMeasure(); + + } + + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserMaxSizePropertyValueValid(object value) + { + double v = (double)value; + return (!double.IsNaN(v) && v >= 0.0d); + } + + /// + /// Returns true if this definition is a part of shared group. + /// + internal bool IsShared + { + get { return (_sharedState != null); } + } + + /// + /// Internal accessor to user size field. + /// + internal GridLength UserSize + { + get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); } + } + + /// + /// Internal accessor to user min size field. + /// + internal double UserMinSize + { + get { return (UserMinSizeValueCache); } + } + + /// + /// Internal accessor to user max size field. /// - internal abstract double UserMaxSize { get; } + internal double UserMaxSize + { + get { return (UserMaxSizeValueCache); } + } + + /// + /// DefinitionBase's index in the parents collection. + /// + internal int Index + { + get + { + return (_parentIndex); + } + set + { + Debug.Assert(value >= -1 && _parentIndex != value); + _parentIndex = value; + } + } /// /// Layout-time user size type. /// - internal LayoutTimeSizeType SizeType { get; set; } - + internal LayoutTimeSizeType SizeType + { + get { return (_sizeType); } + set { _sizeType = value; } + } + /// /// Returns or sets measure size for the definition. /// - internal double MeasureSize { get; set; } + internal double MeasureSize + { + get { return (_measureSize); } + set { _measureSize = value; } + } /// /// Returns definition's layout time type sensitive preferred size. @@ -65,37 +234,108 @@ namespace Avalonia.Controls get { double preferredSize = MinSize; - if (SizeType != LayoutTimeSizeType.Auto - && preferredSize < MeasureSize) + if (_sizeType != LayoutTimeSizeType.Auto + && preferredSize < _measureSize) { - preferredSize = MeasureSize; + preferredSize = _measureSize; } - return (preferredSize); + return (preferredSize); } } /// /// Returns or sets size cache for the definition. /// - internal double SizeCache { get; set; } + internal double SizeCache + { + get { return (_sizeCache); } + set { _sizeCache = value; } + } /// - /// Used during measure and arrange to accumulate size for "Auto" and "Star" DefinitionBase's + /// Returns min size. /// - internal double MinSize { get; set; } + internal double MinSize + { + get + { + double minSize = _minSize; + if (UseSharedMinimum + && _sharedState != null + && minSize < _sharedState.MinSize) + { + minSize = _sharedState.MinSize; + } + return (minSize); + } + } /// - /// Updates min size. + /// Returns min size, always taking into account shared state. /// - /// New size. - internal void UpdateMinSize(double minSize) + internal double MinSizeForArrange { - MinSize = Math.Max(MinSize, minSize); + get + { + double minSize = _minSize; + if (_sharedState != null + && (UseSharedMinimum || !LayoutWasUpdated) + && minSize < _sharedState.MinSize) + { + minSize = _sharedState.MinSize; + } + return (minSize); + } } /// /// Offset. /// - internal double FinalOffset { get; set; } + internal double FinalOffset + { + get { return _offset; } + set { _offset = value; } + } + + /// + /// Internal helper to access up-to-date UserSize property value. + /// + internal abstract GridLength UserSizeValueCache { get; } + + /// + /// Internal helper to access up-to-date UserMinSize property value. + /// + internal abstract double UserMinSizeValueCache { get; } + + /// + /// Internal helper to access up-to-date UserMaxSize property value. + /// + internal abstract double UserMaxSizeValueCache { get; } + + private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) + { + string sharedSizeGroupId = (string)e.NewValue; + + if (definition._sharedState != null) + { + // if definition is already registered AND shared size group id is changing, + // then un-register the definition from the current shared size state object. + definition._sharedState.RemoveMember(definition); + definition._sharedState = null; + } + + if ((definition._sharedState == null) && (sharedSizeGroupId != null)) + { + var privateSharedSizeScope = definition._privateSharedSizeScope; + if (privateSharedSizeScope != null) + { + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + definition._sharedState.AddMember(definition); + + } + } + } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs index 449b92ac52..c08c4396c5 100644 --- a/src/Avalonia.Controls/Grid/Grid.cs +++ b/src/Avalonia.Controls/Grid/Grid.cs @@ -86,6 +86,7 @@ namespace Avalonia.Controls private RowDefinitions _rowDefinitions; private DefinitionBase[] _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; private DefinitionBase[] _definitionsV = new DefinitionBase[1] { new RowDefinition() }; + internal SharedSizeScope sharedSizeScope; // 5 is an arbitrary constant chosen to end the measure loop private const int _layoutLoopMaxCount = 5; @@ -171,6 +172,9 @@ namespace Avalonia.Controls static Grid() { ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); + IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopePropertyChanged); + BoundsProperty.Changed.AddClassHandler(BoundsPropertyChanged); + AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); _tempDefinitionsDataSlot = Thread.AllocateDataSlot(); @@ -181,6 +185,26 @@ namespace Avalonia.Controls _starWeightComparer = new StarWeightComparer(); } + private static void BoundsPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs arg2) + { + for (int i = 0; i < grid._definitionsU.Length; i++) + grid._definitionsU[i].OnUserSizePropertyChanged(arg2); + for (int i = 0; i < grid._definitionsV.Length; i++) + grid._definitionsV[i].OnUserSizePropertyChanged(arg2); + } + + private static void IsSharedSizeScopePropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) + { + if ((bool)e.NewValue) + { + grid.sharedSizeScope = new SharedSizeScope(); + } + else + { + grid.sharedSizeScope = null; + } + } + /// /// Defines the Column attached property. /// @@ -252,8 +276,12 @@ namespace Avalonia.Controls if (_columnDefinitions.Count > 0) _definitionsU = _columnDefinitions.Cast().ToArray(); + CallEnterParentTree(_definitionsU); + _columnDefinitions.CollectionChanged += delegate { + CallExitParentTree(_definitionsU); + if (_columnDefinitions.Count == 0) { _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; @@ -263,11 +291,26 @@ namespace Avalonia.Controls _definitionsU = _columnDefinitions.Cast().ToArray(); DefinitionsDirty = true; } + + CallEnterParentTree(_definitionsU); + Invalidate(); }; } } + private void CallEnterParentTree(DefinitionBase[] definitionsU) + { + for (int i = 0; i < definitionsU.Length; i++) + definitionsU[i].OnEnterParentTree(this, i); + } + + private void CallExitParentTree(DefinitionBase[] definitionsU) + { + for (int i = 0; i < definitionsU.Length; i++) + definitionsU[i].OnExitParentTree(); + } + /// /// Gets or sets the row definitions for the grid. /// @@ -294,6 +337,8 @@ namespace Avalonia.Controls _rowDefinitions.CollectionChanged += delegate { + CallExitParentTree(_definitionsU); + if (_rowDefinitions.Count == 0) { _definitionsV = new DefinitionBase[1] { new RowDefinition() }; @@ -303,6 +348,8 @@ namespace Avalonia.Controls _definitionsV = _rowDefinitions.Cast().ToArray(); DefinitionsDirty = true; } + CallEnterParentTree(_definitionsU); + Invalidate(); }; } @@ -359,6 +406,16 @@ namespace Avalonia.Controls return element.GetValue(IsSharedSizeScopeProperty); } + /// + /// Sets the value of the IsSharedSizeScope attached property for a control. + /// + /// The control. + /// The control's IsSharedSizeScope value. + public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) + { + element.SetValue(IsSharedSizeScopeProperty, value); + } + /// /// Sets the value of the Column attached property for a control. /// @@ -659,7 +716,7 @@ namespace Avalonia.Controls /// /// Invalidates grid caches and makes the grid dirty for measure. /// - private void Invalidate() + internal void Invalidate() { CellsStructureDirty = true; InvalidateMeasure(); @@ -780,8 +837,7 @@ namespace Avalonia.Controls { for (int i = 0; i < definitions.Length; ++i) { - // Reset minimum size. - definitions[i].MinSize = 0; + definitions[i].OnBeforeLayout(this); double userMinSize = definitions[i].UserMinSize; double userMaxSize = definitions[i].UserMaxSize; @@ -858,11 +914,11 @@ namespace Avalonia.Controls { if (isRows) { - _definitionsV[i].MinSize = minSizes[i]; + _definitionsV[i].SetMinSize(minSizes[i]); } else { - _definitionsU[i].MinSize = minSizes[i]; + _definitionsU[i].SetMinSize(minSizes[i]); } } } @@ -1710,7 +1766,7 @@ namespace Avalonia.Controls if (def.UserSize.IsStar) { - // Debug.Assert(!def.IsShared, "*-defs cannot be shared"); + Debug.Assert(!def.IsShared, "*-defs cannot be shared"); if (def.MeasureSize < 0.0) { @@ -1721,14 +1777,14 @@ namespace Avalonia.Controls double starWeight = StarWeight(def, scale); totalStarWeight += starWeight; - if (def.MinSize > 0.0) + if (def.MinSizeForArrange > 0.0) { // store ratio w/min in MeasureSize (for now) definitionIndices[minCount++] = i; - def.MeasureSize = starWeight / def.MinSize; + def.MeasureSize = starWeight / def.MinSizeForArrange; } - double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); + double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize); if (!double.IsPositiveInfinity(effectiveMaxSize)) { // store ratio w/max in SizeCache (for now) @@ -1748,26 +1804,26 @@ namespace Avalonia.Controls break; case (GridUnitType.Auto): - userSize = def.MinSize; + userSize = def.MinSizeForArrange; break; } double userMaxSize; - // if (def.IsShared) - // { - // // overriding userMaxSize effectively prevents squishy-ness. - // // this is a "solution" to avoid shared definitions from been sized to - // // different final size at arrange time, if / when different grids receive - // // different final sizes. - // userMaxSize = userSize; - // } - // else - // { - userMaxSize = def.UserMaxSize; - // } - - def.SizeCache = Math.Max(def.MinSize, Math.Min(userSize, userMaxSize)); + if (def.IsShared) + { + // overriding userMaxSize effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + userMaxSize = userSize; + } + else + { + userMaxSize = def.UserMaxSize; + } + + def.SizeCache = Math.Max(def.MinSizeForArrange, Math.Min(userSize, userMaxSize)); takenSize += def.SizeCache; } } @@ -1831,14 +1887,14 @@ namespace Avalonia.Controls { resolvedIndex = definitionIndices[minCount - 1]; resolvedDef = definitions[resolvedIndex]; - resolvedSize = resolvedDef.MinSize; + resolvedSize = resolvedDef.MinSizeForArrange; --minCount; } else { resolvedIndex = definitionIndices[defCount + maxCount - 1]; resolvedDef = definitions[resolvedIndex]; - resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); + resolvedSize = Math.Max(resolvedDef.MinSizeForArrange, resolvedDef.UserMaxSize); --maxCount; } @@ -1961,7 +2017,7 @@ namespace Avalonia.Controls // min and max should have no effect by now, but just in case... resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); - resolvedSize = Math.Max(def.MinSize, resolvedSize); + resolvedSize = Math.Max(def.MinSizeForArrange, resolvedSize); // Use the raw (unrounded) sizes to update takenSize, so that // proportions are computed in the same terms as in phase 3; @@ -2053,7 +2109,7 @@ namespace Avalonia.Controls { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache - dpiIncrement; - final = Math.Max(final, definition.MinSize); + final = Math.Max(final, definition.MinSizeForArrange); if (final < definition.SizeCache) { adjustedSize -= dpiIncrement; @@ -2069,7 +2125,7 @@ namespace Avalonia.Controls { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache + dpiIncrement; - final = Math.Max(final, definition.MinSize); + final = Math.Max(final, definition.MinSizeForArrange); if (final > definition.SizeCache) { adjustedSize += dpiIncrement; diff --git a/src/Avalonia.Controls/Grid/RowDefinition.cs b/src/Avalonia.Controls/Grid/RowDefinition.cs index d9120b010e..1cb09e16e9 100644 --- a/src/Avalonia.Controls/Grid/RowDefinition.cs +++ b/src/Avalonia.Controls/Grid/RowDefinition.cs @@ -89,10 +89,10 @@ namespace Avalonia.Controls } - internal override GridLength UserSize => this.Height; + internal override GridLength UserSizeValueCache => this.Height; - internal override double UserMinSize => this.MinHeight; + internal override double UserMinSizeValueCache => this.MinHeight; - internal override double UserMaxSize => this.MaxHeight; + internal override double UserMaxSizeValueCache => this.MaxHeight; } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SharedSizeScope.cs b/src/Avalonia.Controls/Grid/SharedSizeScope.cs new file mode 100644 index 0000000000..6835d13132 --- /dev/null +++ b/src/Avalonia.Controls/Grid/SharedSizeScope.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Diagnostics; + +namespace Avalonia.Controls +{ + /// + /// Collection of shared states objects for a single scope + /// + internal class SharedSizeScope + { + /// + /// Returns SharedSizeState object for a given group. + /// Creates a new StatedState object if necessary. + /// + internal SharedSizeState EnsureSharedState(string sharedSizeGroup) + { + // check that sharedSizeGroup is not default + Debug.Assert(sharedSizeGroup != null); + + SharedSizeState sharedState = _registry[sharedSizeGroup] as SharedSizeState; + if (sharedState == null) + { + sharedState = new SharedSizeState(this, sharedSizeGroup); + _registry[sharedSizeGroup] = sharedState; + } + return (sharedState); + } + + /// + /// Removes an entry in the registry by the given key. + /// + internal void Remove(object key) + { + Debug.Assert(_registry.Contains(key)); + _registry.Remove(key); + } + + private Hashtable _registry = new Hashtable(); // storage for shared state objects + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SharedSizeState.cs b/src/Avalonia.Controls/Grid/SharedSizeState.cs new file mode 100644 index 0000000000..2b99c09861 --- /dev/null +++ b/src/Avalonia.Controls/Grid/SharedSizeState.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Avalonia.Utilities; + +namespace Avalonia.Controls +{ + /// + /// Implementation of per shared group state object + /// + internal class SharedSizeState + { + private readonly SharedSizeScope _sharedSizeScope; // the scope this state belongs to + private readonly string _sharedSizeGroupId; // Id of the shared size group this object is servicing + private readonly List _registry; // registry of participating definitions + private readonly EventHandler _layoutUpdated; // instance event handler for layout updated event + private Control _layoutUpdatedHost; // Control for which layout updated event handler is registered + private bool _broadcastInvalidation; // "true" when broadcasting of invalidation is needed + private bool _userSizeValid; // "true" when _userSize is up to date + private GridLength _userSize; // shared state + private double _minSize; // shared state + + /// + /// Default ctor. + /// + internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) + { + Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null); + _sharedSizeScope = sharedSizeScope; + _sharedSizeGroupId = sharedSizeGroupId; + _registry = new List(); + _layoutUpdated = new EventHandler(OnLayoutUpdated); + _broadcastInvalidation = true; + } + + /// + /// Adds / registers a definition instance. + /// + internal void AddMember(DefinitionBase member) + { + Debug.Assert(!_registry.Contains(member)); + _registry.Add(member); + Invalidate(); + } + + /// + /// Removes / un-registers a definition instance. + /// + /// + /// If the collection of registered definitions becomes empty + /// instantiates self removal from owner's collection. + /// + internal void RemoveMember(DefinitionBase member) + { + Invalidate(); + _registry.Remove(member); + + if (_registry.Count == 0) + { + _sharedSizeScope.Remove(_sharedSizeGroupId); + } + } + + /// + /// Propogates invalidations for all registered definitions. + /// Resets its own state. + /// + internal void Invalidate() + { + _userSizeValid = false; + + if (_broadcastInvalidation) + { + for (int i = 0, count = _registry.Count; i < count; ++i) + { + Grid parentGrid = (Grid)(_registry[i].Parent); + parentGrid.Invalidate(); + } + _broadcastInvalidation = false; + } + } + + /// + /// Makes sure that one and only one layout updated handler is registered for this shared state. + /// + internal void EnsureDeferredValidation(Control layoutUpdatedHost) + { + if (_layoutUpdatedHost == null) + { + _layoutUpdatedHost = layoutUpdatedHost; + _layoutUpdatedHost.LayoutUpdated += _layoutUpdated; + } + } + + /// + /// DefinitionBase's specific code. + /// + internal double MinSize + { + get + { + if (!_userSizeValid) { EnsureUserSizeValid(); } + return (_minSize); + } + } + + /// + /// DefinitionBase's specific code. + /// + internal GridLength UserSize + { + get + { + if (!_userSizeValid) { EnsureUserSizeValid(); } + return (_userSize); + } + } + + private void EnsureUserSizeValid() + { + _userSize = new GridLength(1, GridUnitType.Auto); + + for (int i = 0, count = _registry.Count; i < count; ++i) + { + Debug.Assert(_userSize.GridUnitType == GridUnitType.Auto + || _userSize.GridUnitType == GridUnitType.Pixel); + + GridLength currentGridLength = _registry[i].UserSizeValueCache; + if (currentGridLength.GridUnitType == GridUnitType.Pixel) + { + if (_userSize.GridUnitType == GridUnitType.Auto) + { + _userSize = currentGridLength; + } + else if (_userSize.Value < currentGridLength.Value) + { + _userSize = currentGridLength; + } + } + } + // taking maximum with user size effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + _minSize = _userSize.IsAbsolute ? _userSize.Value : 0.0; + + _userSizeValid = true; + } + + /// + /// OnLayoutUpdated handler. Validates that all participating definitions + /// have updated min size value. Forces another layout update cycle if needed. + /// + private void OnLayoutUpdated(object sender, EventArgs e) + { + double sharedMinSize = 0; + + // accumulate min size of all participating definitions + for (int i = 0, count = _registry.Count; i < count; ++i) + { + sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); + } + + bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); + + // compare accumulated min size with min sizes of the individual definitions + for (int i = 0, count = _registry.Count; i < count; ++i) + { + DefinitionBase definitionBase = _registry[i]; + + if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) + { + // if definition's min size is different, then need to re-measure + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) + { + Grid parentGrid = (Grid)definitionBase.Parent; + parentGrid.InvalidateMeasure(); + definitionBase.UseSharedMinimum = true; + } + else + { + definitionBase.UseSharedMinimum = false; + + // if measure is valid then also need to check arrange. + // Note: definitionBase.SizeCache is volatile but at this point + // it contains up-to-date final size + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) + { + Grid parentGrid = (Grid)definitionBase.Parent; + parentGrid.InvalidateArrange(); + } + } + + definitionBase.LayoutWasUpdated = false; + } + } + + _minSize = sharedMinSize; + + _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; + _layoutUpdatedHost = null; + + _broadcastInvalidation = true; + } + } +} \ No newline at end of file From 2310dab605457d38fedbb720eccb76d3c0d62793 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 28 May 2019 17:06:22 +0800 Subject: [PATCH 048/130] Inherit SharedSize scopes to child grids. --- src/Avalonia.Controls/Grid/DefinitionBase.cs | 5 ++-- src/Avalonia.Controls/Grid/Grid.cs | 26 ++++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/Grid/DefinitionBase.cs b/src/Avalonia.Controls/Grid/DefinitionBase.cs index e21d55c1f6..a59ab3cdb9 100644 --- a/src/Avalonia.Controls/Grid/DefinitionBase.cs +++ b/src/Avalonia.Controls/Grid/DefinitionBase.cs @@ -19,6 +19,7 @@ namespace Avalonia.Controls { SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); } + internal bool UseSharedMinimum { get; set; } internal bool LayoutWasUpdated { get; set; } @@ -59,10 +60,10 @@ namespace Avalonia.Controls { if (_sharedState == null & SharedSizeGroup != null & - Parent?.sharedSizeScope != null & + Parent?.PrivateSharedSizeScope != null & !_successUpdateSharedScope) { - _privateSharedSizeScope = Parent.sharedSizeScope; + _privateSharedSizeScope = Parent.PrivateSharedSizeScope; _sharedState = _privateSharedSizeScope.EnsureSharedState(SharedSizeGroup); _sharedState.AddMember(this); _successUpdateSharedScope = true; diff --git a/src/Avalonia.Controls/Grid/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs index c08c4396c5..075d2c098f 100644 --- a/src/Avalonia.Controls/Grid/Grid.cs +++ b/src/Avalonia.Controls/Grid/Grid.cs @@ -86,7 +86,12 @@ namespace Avalonia.Controls private RowDefinitions _rowDefinitions; private DefinitionBase[] _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; private DefinitionBase[] _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - internal SharedSizeScope sharedSizeScope; + + internal SharedSizeScope PrivateSharedSizeScope + { + get { return GetPrivateSharedSizeScope(this); } + set { SetPrivateSharedSizeScope(this, value); } + } // 5 is an arbitrary constant chosen to end the measure loop private const int _layoutLoopMaxCount = 5; @@ -197,11 +202,11 @@ namespace Avalonia.Controls { if ((bool)e.NewValue) { - grid.sharedSizeScope = new SharedSizeScope(); + grid.PrivateSharedSizeScope = new SharedSizeScope(); } else { - grid.sharedSizeScope = null; + grid.PrivateSharedSizeScope = null; } } @@ -236,6 +241,9 @@ namespace Avalonia.Controls public static readonly AttachedProperty IsSharedSizeScopeProperty = AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); + internal static readonly AttachedProperty PrivateSharedSizeScopeProperty = + AvaloniaProperty.RegisterAttached("PrivateSharedSizeScope", null, inherits: true); + /// /// Defines the property. /// @@ -409,13 +417,21 @@ namespace Avalonia.Controls /// /// Sets the value of the IsSharedSizeScope attached property for a control. /// - /// The control. - /// The control's IsSharedSizeScope value. public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) { element.SetValue(IsSharedSizeScopeProperty, value); } + internal static SharedSizeScope GetPrivateSharedSizeScope(AvaloniaObject element) + { + return element.GetValue(PrivateSharedSizeScopeProperty); + } + + internal static void SetPrivateSharedSizeScope(AvaloniaObject element, SharedSizeScope value) + { + element.SetValue(PrivateSharedSizeScopeProperty, value); + } + /// /// Sets the value of the Column attached property for a control. /// From d97835ee624183e60113b25ea6b2d8e58872129b Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 28 May 2019 21:46:14 +0800 Subject: [PATCH 049/130] Add SharedSizeScope tests from #1945 Fix IsSharedSizeScope/PrivateSharedSizeScope so that it can be set on non-`Grid` controls. --- src/Avalonia.Controls/Grid/Grid.cs | 24 +- .../Avalonia.Controls.UnitTests/GridTests.cs | 268 +++++++++++++++++- 2 files changed, 286 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Grid/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs index 075d2c098f..c45d7c2461 100644 --- a/src/Avalonia.Controls/Grid/Grid.cs +++ b/src/Avalonia.Controls/Grid/Grid.cs @@ -177,7 +177,7 @@ namespace Avalonia.Controls static Grid() { ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); - IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopePropertyChanged); + IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopePropertyChanged); BoundsProperty.Changed.AddClassHandler(BoundsPropertyChanged); AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); @@ -196,20 +196,32 @@ namespace Avalonia.Controls grid._definitionsU[i].OnUserSizePropertyChanged(arg2); for (int i = 0; i < grid._definitionsV.Length; i++) grid._definitionsV[i].OnUserSizePropertyChanged(arg2); + + UpdateSharedSizeScopes(grid); } - private static void IsSharedSizeScopePropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) + private static void IsSharedSizeScopePropertyChanged(Control control, AvaloniaPropertyChangedEventArgs e) { if ((bool)e.NewValue) { - grid.PrivateSharedSizeScope = new SharedSizeScope(); + control.SetValue(Grid.PrivateSharedSizeScopeProperty, new SharedSizeScope()); } else { - grid.PrivateSharedSizeScope = null; + control.SetValue(Grid.PrivateSharedSizeScopeProperty, null); } } + static void UpdateSharedSizeScopes(Grid grid) + { + for (int i = 0; i < grid._definitionsU.Length; i++) + if (grid._definitionsU[i].SharedSizeGroup != null) + grid._definitionsU[i].UpdateSharedScope(); + for (int i = 0; i < grid._definitionsV.Length; i++) + if (grid._definitionsV[i].SharedSizeGroup != null) + grid._definitionsV[i].UpdateSharedScope(); + } + /// /// Defines the Column attached property. /// @@ -242,7 +254,7 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); internal static readonly AttachedProperty PrivateSharedSizeScopeProperty = - AvaloniaProperty.RegisterAttached("PrivateSharedSizeScope", null, inherits: true); + AvaloniaProperty.RegisterAttached("&&PrivateSharedSizeScope", null, inherits: true); /// /// Defines the property. @@ -614,6 +626,7 @@ namespace Avalonia.Controls { } + UpdateSharedSizeScopes(this); return (gridDesiredSize); } @@ -688,6 +701,7 @@ namespace Avalonia.Controls RowDefinitions[i].ActualHeight = GetFinalRowDefinitionHeight(i); } + UpdateSharedSizeScopes(this); return (arrangeSize); } diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index 50335c44ee..7e47f60eb5 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -1,6 +1,9 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Collections.Generic; +using System.Linq; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests @@ -179,5 +182,268 @@ namespace Avalonia.Controls.UnitTests Assert.False(target.IsMeasureValid); } + + [Fact] + public void All_Descendant_Grids_Are_Registered_When_Added_After_Setting_Scope() + { + var grids = new[] { new Grid(), new Grid(), new Grid() }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + Assert.All(grids, g => Assert.True(g.PrivateSharedSizeScope != null)); + } + + [Fact] + public void All_Descendant_Grids_Are_Registered_When_Setting_Scope() + { + var grids = new[] { new Grid(), new Grid(), new Grid() }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.Child = scope; + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + + Assert.All(grids, g => Assert.True(g.PrivateSharedSizeScope != null)); + } + + [Fact] + public void All_Descendant_Grids_Are_Unregistered_When_Resetting_Scope() + { + var grids = new[] { new Grid(), new Grid(), new Grid() }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + Assert.All(grids, g => Assert.True(g.PrivateSharedSizeScope != null)); + root.SetValue(Grid.IsSharedSizeScopeProperty, false); + Assert.All(grids, g => Assert.False(g.PrivateSharedSizeScope != null)); + Assert.Equal(null, root.GetValue(Grid.PrivateSharedSizeScopeProperty)); + } + + [Fact] + public void Size_Is_Propagated_Between_Grids() + { + var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); + } + + [Fact] + public void Size_Propagation_Is_Constrained_To_Innermost_Scope() + { + var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + var innerScope = new Panel(); + innerScope.Children.AddRange(grids); + innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); + + var outerGrid = CreateGrid(("A", new GridLength(0))); + var outerScope = new Panel(); + outerScope.Children.AddRange(new[] { outerGrid, innerScope }); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = outerScope; + + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + Assert.Equal(0, outerGrid.ColumnDefinitions[0].ActualWidth); + } + + [Fact] + public void Size_Is_Propagated_Between_Rows_And_Columns() + { + var grid = new Grid + { + ColumnDefinitions = new ColumnDefinitions("*,30"), + RowDefinitions = new RowDefinitions("*,10") + }; + + grid.ColumnDefinitions[1].SharedSizeGroup = "A"; + grid.RowDefinitions[1].SharedSizeGroup = "A"; + + var root = new TestRoot(); + root.Child = grid; + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + Assert.Equal(30, grid.RowDefinitions[1].ActualHeight); + } + + [Fact] + public void Size_Group_Changes_Are_Tracked() + { + var grids = new[] { + CreateGrid((null, new GridLength(0, GridUnitType.Auto)), (null, new GridLength())), + CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); + + grids[0].ColumnDefinitions[0].SharedSizeGroup = "A"; + + root.Measure(new Size(51, 51)); + root.Arrange(new Rect(new Point(), new Point(51, 51))); + Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); + + grids[0].ColumnDefinitions[0].SharedSizeGroup = null; + + root.Measure(new Size(52, 52)); + root.Arrange(new Rect(new Point(), new Point(52, 52))); + Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); + } + + [Fact] + public void Collection_Changes_Are_Tracked() + { + var grid = CreateGrid( + ("A", new GridLength(20)), + ("A", new GridLength(30)), + ("A", new GridLength(40)), + (null, new GridLength())); + + var scope = new Panel(); + scope.Children.Add(grid); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(40, cd.ActualWidth)); + + grid.ColumnDefinitions.RemoveAt(2); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); + + grid.ColumnDefinitions.Insert(1, new ColumnDefinition { Width = new GridLength(35), SharedSizeGroup = "A" }); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(35, cd.ActualWidth)); + + grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(10), SharedSizeGroup = "A" }; + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); + + grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(50), SharedSizeGroup = "A" }; + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); + } + + [Fact] + public void Size_Priorities_Are_Maintained() + { + var sizers = new List(); + var grid = CreateGrid( + ("A", new GridLength(20)), + ("A", new GridLength(20, GridUnitType.Auto)), + ("A", new GridLength(1, GridUnitType.Star)), + ("A", new GridLength(1, GridUnitType.Star)), + (null, new GridLength())); + for (int i = 0; i < 3; i++) + sizers.Add(AddSizer(grid, i, 6 + i * 6)); + var scope = new Panel(); + scope.Children.Add(grid); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + grid.Measure(new Size(100, 100)); + grid.Arrange(new Rect(new Point(), new Point(100, 100))); + // all in group are equal to the first fixed column + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(20, cd.ActualWidth)); + + grid.ColumnDefinitions[0].SharedSizeGroup = null; + + grid.Measure(new Size(100, 100)); + grid.Arrange(new Rect(new Point(), new Point(100, 100))); + // all in group are equal to width (MinWidth) of the sizer in the second column + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth)); + + grid.ColumnDefinitions[1].SharedSizeGroup = null; + + grid.Measure(new Size(double.PositiveInfinity, 100)); + grid.Arrange(new Rect(new Point(), new Point(100, 100))); + // with no constraint star columns default to the MinWidth of the sizer in the column + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 2 * 6, cd.ActualWidth)); + } + + // grid creators + private Grid CreateGrid(params string[] columnGroups) + { + return CreateGrid(columnGroups.Select(s => (s, ColumnDefinition.WidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + } + + private Grid CreateGrid(params (string name, GridLength width)[] columns) + { + return CreateGrid(columns.Select(c => + (c.name, c.width, ColumnDefinition.MinWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + } + + private Grid CreateGrid(params (string name, GridLength width, double minWidth)[] columns) + { + return CreateGrid(columns.Select(c => + (c.name, c.width, c.minWidth, ColumnDefinition.MaxWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + } + + private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns) + { + var columnDefinitions = new ColumnDefinitions(); + + columnDefinitions.AddRange( + columns.Select(c => new ColumnDefinition + { + SharedSizeGroup = c.name, + Width = c.width, + MinWidth = c.minWidth, + MaxWidth = c.maxWidth + }) + ); + var grid = new Grid + { + ColumnDefinitions = columnDefinitions + }; + + return grid; + } + + private Control AddSizer(Grid grid, int column, double size = 30) + { + var ctrl = new Control { MinWidth = size, MinHeight = size }; + ctrl.SetValue(Grid.ColumnProperty, column); + grid.Children.Add(ctrl); + return ctrl; + } } -} +} \ No newline at end of file From c70ffb564bc837d636c8c5f20ecc3bececf02719 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 28 May 2019 21:57:07 +0800 Subject: [PATCH 050/130] Restore unit tests part 2 --- src/Avalonia.Controls/Grid/DefinitionBase.cs | 5 +- src/Avalonia.Controls/Grid/Grid.cs | 5 + .../Avalonia.Controls.UnitTests/GridTests.cs | 263 --------- .../SharedSizeScopeTests.cs | 527 +++++++++--------- 4 files changed, 267 insertions(+), 533 deletions(-) diff --git a/src/Avalonia.Controls/Grid/DefinitionBase.cs b/src/Avalonia.Controls/Grid/DefinitionBase.cs index a59ab3cdb9..051ed49289 100644 --- a/src/Avalonia.Controls/Grid/DefinitionBase.cs +++ b/src/Avalonia.Controls/Grid/DefinitionBase.cs @@ -31,7 +31,6 @@ namespace Avalonia.Controls private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) internal SharedSizeScope _privateSharedSizeScope; private SharedSizeState _sharedState; // reference to shared state object this instance is registered with - private bool _successUpdateSharedScope; /// /// Defines the property. @@ -60,13 +59,11 @@ namespace Avalonia.Controls { if (_sharedState == null & SharedSizeGroup != null & - Parent?.PrivateSharedSizeScope != null & - !_successUpdateSharedScope) + Parent?.PrivateSharedSizeScope != null ) { _privateSharedSizeScope = Parent.PrivateSharedSizeScope; _sharedState = _privateSharedSizeScope.EnsureSharedState(SharedSizeGroup); _sharedState.AddMember(this); - _successUpdateSharedScope = true; } } diff --git a/src/Avalonia.Controls/Grid/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs index c45d7c2461..511853a982 100644 --- a/src/Avalonia.Controls/Grid/Grid.cs +++ b/src/Avalonia.Controls/Grid/Grid.cs @@ -35,6 +35,11 @@ namespace Avalonia.Controls internal int CellGroup3; internal int CellGroup4; + internal bool HasSharedSizeScope() + { + return this.GetValue(Grid.PrivateSharedSizeScopeProperty) != null; + } + /// /// Helper for Comparer methods. /// diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index 7e47f60eb5..7126075b9e 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -182,268 +182,5 @@ namespace Avalonia.Controls.UnitTests Assert.False(target.IsMeasureValid); } - - [Fact] - public void All_Descendant_Grids_Are_Registered_When_Added_After_Setting_Scope() - { - var grids = new[] { new Grid(), new Grid(), new Grid() }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - Assert.All(grids, g => Assert.True(g.PrivateSharedSizeScope != null)); - } - - [Fact] - public void All_Descendant_Grids_Are_Registered_When_Setting_Scope() - { - var grids = new[] { new Grid(), new Grid(), new Grid() }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.Child = scope; - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - - Assert.All(grids, g => Assert.True(g.PrivateSharedSizeScope != null)); - } - - [Fact] - public void All_Descendant_Grids_Are_Unregistered_When_Resetting_Scope() - { - var grids = new[] { new Grid(), new Grid(), new Grid() }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - Assert.All(grids, g => Assert.True(g.PrivateSharedSizeScope != null)); - root.SetValue(Grid.IsSharedSizeScopeProperty, false); - Assert.All(grids, g => Assert.False(g.PrivateSharedSizeScope != null)); - Assert.Equal(null, root.GetValue(Grid.PrivateSharedSizeScopeProperty)); - } - - [Fact] - public void Size_Is_Propagated_Between_Grids() - { - var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); - } - - [Fact] - public void Size_Propagation_Is_Constrained_To_Innermost_Scope() - { - var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - var innerScope = new Panel(); - innerScope.Children.AddRange(grids); - innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); - - var outerGrid = CreateGrid(("A", new GridLength(0))); - var outerScope = new Panel(); - outerScope.Children.AddRange(new[] { outerGrid, innerScope }); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = outerScope; - - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(0, outerGrid.ColumnDefinitions[0].ActualWidth); - } - - [Fact] - public void Size_Is_Propagated_Between_Rows_And_Columns() - { - var grid = new Grid - { - ColumnDefinitions = new ColumnDefinitions("*,30"), - RowDefinitions = new RowDefinitions("*,10") - }; - - grid.ColumnDefinitions[1].SharedSizeGroup = "A"; - grid.RowDefinitions[1].SharedSizeGroup = "A"; - - var root = new TestRoot(); - root.Child = grid; - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(30, grid.RowDefinitions[1].ActualHeight); - } - - [Fact] - public void Size_Group_Changes_Are_Tracked() - { - var grids = new[] { - CreateGrid((null, new GridLength(0, GridUnitType.Auto)), (null, new GridLength())), - CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); - - grids[0].ColumnDefinitions[0].SharedSizeGroup = "A"; - - root.Measure(new Size(51, 51)); - root.Arrange(new Rect(new Point(), new Point(51, 51))); - Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); - - grids[0].ColumnDefinitions[0].SharedSizeGroup = null; - - root.Measure(new Size(52, 52)); - root.Arrange(new Rect(new Point(), new Point(52, 52))); - Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); - } - - [Fact] - public void Collection_Changes_Are_Tracked() - { - var grid = CreateGrid( - ("A", new GridLength(20)), - ("A", new GridLength(30)), - ("A", new GridLength(40)), - (null, new GridLength())); - - var scope = new Panel(); - scope.Children.Add(grid); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(40, cd.ActualWidth)); - - grid.ColumnDefinitions.RemoveAt(2); - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); - - grid.ColumnDefinitions.Insert(1, new ColumnDefinition { Width = new GridLength(35), SharedSizeGroup = "A" }); - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(35, cd.ActualWidth)); - - grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(10), SharedSizeGroup = "A" }; - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); - - grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(50), SharedSizeGroup = "A" }; - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); - } - - [Fact] - public void Size_Priorities_Are_Maintained() - { - var sizers = new List(); - var grid = CreateGrid( - ("A", new GridLength(20)), - ("A", new GridLength(20, GridUnitType.Auto)), - ("A", new GridLength(1, GridUnitType.Star)), - ("A", new GridLength(1, GridUnitType.Star)), - (null, new GridLength())); - for (int i = 0; i < 3; i++) - sizers.Add(AddSizer(grid, i, 6 + i * 6)); - var scope = new Panel(); - scope.Children.Add(grid); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - grid.Measure(new Size(100, 100)); - grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // all in group are equal to the first fixed column - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(20, cd.ActualWidth)); - - grid.ColumnDefinitions[0].SharedSizeGroup = null; - - grid.Measure(new Size(100, 100)); - grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // all in group are equal to width (MinWidth) of the sizer in the second column - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth)); - - grid.ColumnDefinitions[1].SharedSizeGroup = null; - - grid.Measure(new Size(double.PositiveInfinity, 100)); - grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // with no constraint star columns default to the MinWidth of the sizer in the column - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 2 * 6, cd.ActualWidth)); - } - - // grid creators - private Grid CreateGrid(params string[] columnGroups) - { - return CreateGrid(columnGroups.Select(s => (s, ColumnDefinition.WidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - } - - private Grid CreateGrid(params (string name, GridLength width)[] columns) - { - return CreateGrid(columns.Select(c => - (c.name, c.width, ColumnDefinition.MinWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - } - - private Grid CreateGrid(params (string name, GridLength width, double minWidth)[] columns) - { - return CreateGrid(columns.Select(c => - (c.name, c.width, c.minWidth, ColumnDefinition.MaxWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - } - - private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns) - { - var columnDefinitions = new ColumnDefinitions(); - - columnDefinitions.AddRange( - columns.Select(c => new ColumnDefinition - { - SharedSizeGroup = c.name, - Width = c.width, - MinWidth = c.minWidth, - MaxWidth = c.maxWidth - }) - ); - var grid = new Grid - { - ColumnDefinitions = columnDefinitions - }; - - return grid; - } - - private Control AddSizer(Grid grid, int column, double size = 30) - { - var ctrl = new Control { MinWidth = size, MinHeight = size }; - ctrl.SetValue(Grid.ColumnProperty, column); - grid.Children.Add(ctrl); - return ctrl; - } } } \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs b/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs index 03eceb17f5..467c25bfc6 100644 --- a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs @@ -6,279 +6,274 @@ using Avalonia.Platform; using Avalonia.UnitTests; using Moq; - using Xunit; namespace Avalonia.Controls.UnitTests { public class SharedSizeScopeTests { - public SharedSizeScopeTests() + [Fact] + public void All_Descendant_Grids_Are_Registered_When_Added_After_Setting_Scope() + { + var grids = new[] { new Grid(), new Grid(), new Grid() }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); + } + + [Fact] + public void All_Descendant_Grids_Are_Registered_When_Setting_Scope() + { + var grids = new[] { new Grid(), new Grid(), new Grid() }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.Child = scope; + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + + Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); + } + + [Fact] + public void All_Descendant_Grids_Are_Unregistered_When_Resetting_Scope() + { + var grids = new[] { new Grid(), new Grid(), new Grid() }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); + root.SetValue(Grid.IsSharedSizeScopeProperty, false); + Assert.All(grids, g => Assert.False(g.HasSharedSizeScope())); + Assert.Equal(null, root.GetValue(Grid.PrivateSharedSizeScopeProperty)); + } + + [Fact] + public void Size_Is_Propagated_Between_Grids() + { + var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); + } + + [Fact] + public void Size_Propagation_Is_Constrained_To_Innermost_Scope() + { + var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + var innerScope = new Panel(); + innerScope.Children.AddRange(grids); + innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); + + var outerGrid = CreateGrid(("A", new GridLength(0))); + var outerScope = new Panel(); + outerScope.Children.AddRange(new[] { outerGrid, innerScope }); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = outerScope; + + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + Assert.Equal(0, outerGrid.ColumnDefinitions[0].ActualWidth); + } + + [Fact] + public void Size_Is_Propagated_Between_Rows_And_Columns() + { + var grid = new Grid + { + ColumnDefinitions = new ColumnDefinitions("*,30"), + RowDefinitions = new RowDefinitions("*,10") + }; + + grid.ColumnDefinitions[1].SharedSizeGroup = "A"; + grid.RowDefinitions[1].SharedSizeGroup = "A"; + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = grid; + + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + Assert.Equal(30, grid.RowDefinitions[1].ActualHeight); + } + + [Fact] + public void Size_Group_Changes_Are_Tracked() + { + var grids = new[] { + CreateGrid((null, new GridLength(0, GridUnitType.Auto)), (null, new GridLength())), + CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + var scope = new Panel(); + scope.Children.AddRange(grids); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); + + grids[0].ColumnDefinitions[0].SharedSizeGroup = "A"; + + root.Measure(new Size(51, 51)); + root.Arrange(new Rect(new Point(), new Point(51, 51))); + Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); + + grids[0].ColumnDefinitions[0].SharedSizeGroup = null; + + root.Measure(new Size(52, 52)); + root.Arrange(new Rect(new Point(), new Point(52, 52))); + Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); + } + + [Fact] + public void Collection_Changes_Are_Tracked() { + var grid = CreateGrid( + ("A", new GridLength(20)), + ("A", new GridLength(30)), + ("A", new GridLength(40)), + (null, new GridLength())); + + var scope = new Panel(); + scope.Children.Add(grid); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(40, cd.ActualWidth)); + + grid.ColumnDefinitions.RemoveAt(2); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); + + grid.ColumnDefinitions.Insert(1, new ColumnDefinition { Width = new GridLength(35), SharedSizeGroup = "A" }); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(35, cd.ActualWidth)); + + grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(10), SharedSizeGroup = "A" }; + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); + + grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(50), SharedSizeGroup = "A" }; + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); } - // [Fact] - // public void All_Descendant_Grids_Are_Registered_When_Added_After_Setting_Scope() - // { - // var grids = new[] { new Grid(), new Grid(), new Grid() }; - // var scope = new Panel(); - // scope.Children.AddRange(grids); - - // var root = new TestRoot(); - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - // root.Child = scope; - - // Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); - // } - - // [Fact] - // public void All_Descendant_Grids_Are_Registered_When_Setting_Scope() - // { - // var grids = new[] { new Grid(), new Grid(), new Grid() }; - // var scope = new Panel(); - // scope.Children.AddRange(grids); - - // var root = new TestRoot(); - // root.Child = scope; - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - - // Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); - // } - - // [Fact] - // public void All_Descendant_Grids_Are_Unregistered_When_Resetting_Scope() - // { - // var grids = new[] { new Grid(), new Grid(), new Grid() }; - // var scope = new Panel(); - // scope.Children.AddRange(grids); - - // var root = new TestRoot(); - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - // root.Child = scope; - - // Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); - // root.SetValue(Grid.IsSharedSizeScopeProperty, false); - // Assert.All(grids, g => Assert.False(g.HasSharedSizeScope())); - // Assert.Equal(null, root.GetValue(Grid.s_sharedSizeScopeHostProperty)); - // } - - // [Fact] - // public void Size_Is_Propagated_Between_Grids() - // { - // var grids = new[] { CreateGrid("A", null),CreateGrid(("A",new GridLength(30)), (null, new GridLength()))}; - // var scope = new Panel(); - // scope.Children.AddRange(grids); - - // var root = new TestRoot(); - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - // root.Child = scope; - - // root.Measure(new Size(50, 50)); - // root.Arrange(new Rect(new Point(), new Point(50, 50))); - // Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); - // } - - // [Fact] - // public void Size_Propagation_Is_Constrained_To_Innermost_Scope() - // { - // var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - // var innerScope = new Panel(); - // innerScope.Children.AddRange(grids); - // innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); - - // var outerGrid = CreateGrid(("A", new GridLength(0))); - // var outerScope = new Panel(); - // outerScope.Children.AddRange(new[] { outerGrid, innerScope }); - - // var root = new TestRoot(); - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - // root.Child = outerScope; - - // root.Measure(new Size(50, 50)); - // root.Arrange(new Rect(new Point(), new Point(50, 50))); - // Assert.Equal(0, outerGrid.ColumnDefinitions[0].ActualWidth); - // } - - // [Fact] - // public void Size_Is_Propagated_Between_Rows_And_Columns() - // { - // var grid = new Grid - // { - // ColumnDefinitions = new ColumnDefinitions("*,30"), - // RowDefinitions = new RowDefinitions("*,10") - // }; - - // grid.ColumnDefinitions[1].SharedSizeGroup = "A"; - // grid.RowDefinitions[1].SharedSizeGroup = "A"; - - // var root = new TestRoot(); - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - // root.Child = grid; - - // root.Measure(new Size(50, 50)); - // root.Arrange(new Rect(new Point(), new Point(50, 50))); - // Assert.Equal(30, grid.RowDefinitions[1].ActualHeight); - // } - - // [Fact] - // public void Size_Group_Changes_Are_Tracked() - // { - // var grids = new[] { - // CreateGrid((null, new GridLength(0, GridUnitType.Auto)), (null, new GridLength())), - // CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - // var scope = new Panel(); - // scope.Children.AddRange(grids); - - // var root = new TestRoot(); - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - // root.Child = scope; - - // root.Measure(new Size(50, 50)); - // root.Arrange(new Rect(new Point(), new Point(50, 50))); - // Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); - - // grids[0].ColumnDefinitions[0].SharedSizeGroup = "A"; - - // root.Measure(new Size(51, 51)); - // root.Arrange(new Rect(new Point(), new Point(51, 51))); - // Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); - - // grids[0].ColumnDefinitions[0].SharedSizeGroup = null; - - // root.Measure(new Size(52, 52)); - // root.Arrange(new Rect(new Point(), new Point(52, 52))); - // Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); - // } - - // [Fact] - // public void Collection_Changes_Are_Tracked() - // { - // var grid = CreateGrid( - // ("A", new GridLength(20)), - // ("A", new GridLength(30)), - // ("A", new GridLength(40)), - // (null, new GridLength())); - - // var scope = new Panel(); - // scope.Children.Add(grid); - - // var root = new TestRoot(); - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - // root.Child = scope; - - // grid.Measure(new Size(200, 200)); - // grid.Arrange(new Rect(new Point(), new Point(200, 200))); - // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(40, cd.ActualWidth)); - - // grid.ColumnDefinitions.RemoveAt(2); - - // grid.Measure(new Size(200, 200)); - // grid.Arrange(new Rect(new Point(), new Point(200, 200))); - // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); - - // grid.ColumnDefinitions.Insert(1, new ColumnDefinition { Width = new GridLength(35), SharedSizeGroup = "A" }); - - // grid.Measure(new Size(200, 200)); - // grid.Arrange(new Rect(new Point(), new Point(200, 200))); - // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(35, cd.ActualWidth)); - - // grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(10), SharedSizeGroup = "A" }; - - // grid.Measure(new Size(200, 200)); - // grid.Arrange(new Rect(new Point(), new Point(200, 200))); - // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); - - // grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(50), SharedSizeGroup = "A" }; - - // grid.Measure(new Size(200, 200)); - // grid.Arrange(new Rect(new Point(), new Point(200, 200))); - // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); - // } - - // [Fact] - // public void Size_Priorities_Are_Maintained() - // { - // var sizers = new List(); - // var grid = CreateGrid( - // ("A", new GridLength(20)), - // ("A", new GridLength(20, GridUnitType.Auto)), - // ("A", new GridLength(1, GridUnitType.Star)), - // ("A", new GridLength(1, GridUnitType.Star)), - // (null, new GridLength())); - // for (int i = 0; i < 3; i++) - // sizers.Add(AddSizer(grid, i, 6 + i * 6)); - // var scope = new Panel(); - // scope.Children.Add(grid); - - // var root = new TestRoot(); - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - // root.Child = scope; - - // grid.Measure(new Size(100, 100)); - // grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // // all in group are equal to the first fixed column - // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(20, cd.ActualWidth)); - - // grid.ColumnDefinitions[0].SharedSizeGroup = null; - - // grid.Measure(new Size(100, 100)); - // grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // // all in group are equal to width (MinWidth) of the sizer in the second column - // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth)); - - // grid.ColumnDefinitions[1].SharedSizeGroup = null; - - // grid.Measure(new Size(double.PositiveInfinity, 100)); - // grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // // with no constraint star columns default to the MinWidth of the sizer in the column - // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 2 * 6, cd.ActualWidth)); - // } - - // // grid creators - // private Grid CreateGrid(params string[] columnGroups) - // { - // return CreateGrid(columnGroups.Select(s => (s, ColumnDefinition.WidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - // } - - // private Grid CreateGrid(params (string name, GridLength width)[] columns) - // { - // return CreateGrid(columns.Select(c => - // (c.name, c.width, ColumnDefinition.MinWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - // } - - // private Grid CreateGrid(params (string name, GridLength width, double minWidth)[] columns) - // { - // return CreateGrid(columns.Select(c => - // (c.name, c.width, c.minWidth, ColumnDefinition.MaxWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - // } - - // private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns) - // { - // var columnDefinitions = new ColumnDefinitions(); - - // columnDefinitions.AddRange( - // columns.Select(c => new ColumnDefinition - // { - // SharedSizeGroup = c.name, - // Width = c.width, - // MinWidth = c.minWidth, - // MaxWidth = c.maxWidth - // }) - // ); - // var grid = new Grid - // { - // ColumnDefinitions = columnDefinitions - // }; - - // return grid; - // } - - // private Control AddSizer(Grid grid, int column, double size = 30) - // { - // var ctrl = new Control { MinWidth = size, MinHeight = size }; - // ctrl.SetValue(Grid.ColumnProperty,column); - // grid.Children.Add(ctrl); - // return ctrl; - // } + [Fact] + public void Size_Priorities_Are_Maintained() + { + var sizers = new List(); + var grid = CreateGrid( + ("A", new GridLength(20)), + ("A", new GridLength(20, GridUnitType.Auto)), + ("A", new GridLength(1, GridUnitType.Star)), + ("A", new GridLength(1, GridUnitType.Star)), + (null, new GridLength())); + for (int i = 0; i < 3; i++) + sizers.Add(AddSizer(grid, i, 6 + i * 6)); + var scope = new Panel(); + scope.Children.Add(grid); + + var root = new TestRoot(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Child = scope; + + grid.Measure(new Size(100, 100)); + grid.Arrange(new Rect(new Point(), new Point(100, 100))); + // all in group are equal to the first fixed column + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(20, cd.ActualWidth)); + + grid.ColumnDefinitions[0].SharedSizeGroup = null; + + grid.Measure(new Size(100, 100)); + grid.Arrange(new Rect(new Point(), new Point(100, 100))); + // all in group are equal to width (MinWidth) of the sizer in the second column + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth)); + + grid.ColumnDefinitions[1].SharedSizeGroup = null; + + grid.Measure(new Size(double.PositiveInfinity, 100)); + grid.Arrange(new Rect(new Point(), new Point(100, 100))); + // with no constraint star columns default to the MinWidth of the sizer in the column + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 2 * 6, cd.ActualWidth)); + } + + // grid creators + private Grid CreateGrid(params string[] columnGroups) + { + return CreateGrid(columnGroups.Select(s => (s, ColumnDefinition.WidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + } + + private Grid CreateGrid(params (string name, GridLength width)[] columns) + { + return CreateGrid(columns.Select(c => + (c.name, c.width, ColumnDefinition.MinWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + } + + private Grid CreateGrid(params (string name, GridLength width, double minWidth)[] columns) + { + return CreateGrid(columns.Select(c => + (c.name, c.width, c.minWidth, ColumnDefinition.MaxWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + } + + private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns) + { + var columnDefinitions = new ColumnDefinitions(); + + columnDefinitions.AddRange( + columns.Select(c => new ColumnDefinition + { + SharedSizeGroup = c.name, + Width = c.width, + MinWidth = c.minWidth, + MaxWidth = c.maxWidth + }) + ); + var grid = new Grid + { + ColumnDefinitions = columnDefinitions + }; + + return grid; + } + + private Control AddSizer(Grid grid, int column, double size = 30) + { + var ctrl = new Control { MinWidth = size, MinHeight = size }; + ctrl.SetValue(Grid.ColumnProperty, column); + grid.Children.Add(ctrl); + return ctrl; + } } -} +} \ No newline at end of file From 6360de1921e157fde03f6e9c1d25e4094ec3fcb7 Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 29 May 2019 01:28:37 +0300 Subject: [PATCH 051/130] Derive transitioning control from ContentControl --- src/Avalonia.ReactiveUI/RoutedViewHost.cs | 2 +- ...trol.cs => TransitioningContentControl.cs} | 14 ++-- src/Avalonia.ReactiveUI/ViewModelViewHost.cs | 2 +- .../TransitioningContentControlTest.cs | 64 +++++++++++++++++++ 4 files changed, 76 insertions(+), 6 deletions(-) rename src/Avalonia.ReactiveUI/{TransitioningUserControl.cs => TransitioningContentControl.cs} (87%) create mode 100644 tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index bf295aafca..05edeea683 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -53,7 +53,7 @@ namespace Avalonia.ReactiveUI /// ReactiveUI routing documentation website for more info. /// /// - public class RoutedViewHost : TransitioningUserControl, IActivatable, IEnableLogger + public class RoutedViewHost : TransitioningContentControl, IActivatable, IEnableLogger { /// /// for the property. diff --git a/src/Avalonia.ReactiveUI/TransitioningUserControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs similarity index 87% rename from src/Avalonia.ReactiveUI/TransitioningUserControl.cs rename to src/Avalonia.ReactiveUI/TransitioningContentControl.cs index fb5258f2e4..db8f3c964c 100644 --- a/src/Avalonia.ReactiveUI/TransitioningUserControl.cs +++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs @@ -11,27 +11,27 @@ namespace Avalonia.ReactiveUI /// /// A ContentControl that animates the transition when its content is changed. /// - public class TransitioningUserControl : UserControl + public class TransitioningContentControl : ContentControl, IStyleable { /// /// for the property. /// public static readonly AvaloniaProperty FadeInAnimationProperty = - AvaloniaProperty.Register(nameof(DefaultContent), + AvaloniaProperty.Register(nameof(DefaultContent), CreateOpacityAnimation(0d, 1d, TimeSpan.FromSeconds(0.25))); /// /// for the property. /// public static readonly AvaloniaProperty FadeOutAnimationProperty = - AvaloniaProperty.Register(nameof(DefaultContent), + AvaloniaProperty.Register(nameof(DefaultContent), CreateOpacityAnimation(1d, 0d, TimeSpan.FromSeconds(0.25))); /// /// for the property. /// public static readonly AvaloniaProperty DefaultContentProperty = - AvaloniaProperty.Register(nameof(DefaultContent)); + AvaloniaProperty.Register(nameof(DefaultContent)); /// /// Gets or sets the animation played when content appears. @@ -68,6 +68,12 @@ namespace Avalonia.ReactiveUI get => base.Content; set => UpdateContentWithTransition(value); } + + /// + /// TransitioningContentControl uses the default ContentControl + /// template from Avalonia default theme. + /// + Type IStyleable.StyleKey => typeof(ContentControl); /// /// Updates the content with transitions. diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs index 6f80bff4b8..5cfa464c37 100644 --- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -13,7 +13,7 @@ namespace Avalonia.ReactiveUI /// the ViewModel property and display it. This control is very useful /// inside a DataTemplate to display the View associated with a ViewModel. /// - public class ViewModelViewHost : TransitioningUserControl, IViewFor, IEnableLogger + public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger { /// /// for the property. diff --git a/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs new file mode 100644 index 0000000000..5ffcede5dd --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs @@ -0,0 +1,64 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System.Linq; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using ReactiveUI; +using Splat; +using Xunit; + +namespace Avalonia.ReactiveUI.UnitTests +{ + public class TransitioningContentControlTest + { + [Fact] + public void Transitioning_Control_Should_Derive_Template_From_Content_Control() + { + var target = new TransitioningContentControl(); + var stylable = (IStyledElement)target; + Assert.Equal(typeof(ContentControl),stylable.StyleKey); + } + + [Fact] + public void Transitioning_Control_Template_Should_Be_Instantiated() + { + var target = new TransitioningContentControl + { + FadeInAnimation = null, + FadeOutAnimation = null, + Template = GetTemplate(), + Content = "Foo" + }; + target.ApplyTemplate(); + ((ContentPresenter)target.Presenter).UpdateChild(); + + var child = ((IVisual)target).VisualChildren.Single(); + Assert.IsType(child); + child = child.VisualChildren.Single(); + Assert.IsType(child); + child = child.VisualChildren.Single(); + Assert.IsType(child); + } + + private FuncControlTemplate GetTemplate() + { + return new FuncControlTemplate(parent => + { + return new Border + { + Background = new Media.SolidColorBrush(0xffffffff), + Child = new ContentPresenter + { + Name = "PART_ContentPresenter", + [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], + [~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], + } + }; + }); + } + } +} \ No newline at end of file From f0574cbf1a570f6530eb0349967ce5740dccf9ef Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 29 May 2019 02:10:03 +0300 Subject: [PATCH 052/130] Use PageTransition, fix possible bug with CrossFade --- .../TransitioningContentControl.cs | 82 +++---------------- src/Avalonia.Visuals/Animation/CrossFade.cs | 8 +- .../RoutedViewHostTest.cs | 3 +- .../TransitioningContentControlTest.cs | 3 +- .../ViewModelViewHostTest.cs | 3 +- 5 files changed, 19 insertions(+), 80 deletions(-) diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs index db8f3c964c..68d69bc95f 100644 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs @@ -14,18 +14,11 @@ namespace Avalonia.ReactiveUI public class TransitioningContentControl : ContentControl, IStyleable { /// - /// for the property. + /// for the property. /// - public static readonly AvaloniaProperty FadeInAnimationProperty = - AvaloniaProperty.Register(nameof(DefaultContent), - CreateOpacityAnimation(0d, 1d, TimeSpan.FromSeconds(0.25))); - - /// - /// for the property. - /// - public static readonly AvaloniaProperty FadeOutAnimationProperty = - AvaloniaProperty.Register(nameof(DefaultContent), - CreateOpacityAnimation(1d, 0d, TimeSpan.FromSeconds(0.25))); + public static readonly AvaloniaProperty PageTransitionProperty = + AvaloniaProperty.Register(nameof(DefaultContent), + new CrossFade(TimeSpan.FromSeconds(0.5))); /// /// for the property. @@ -34,23 +27,14 @@ namespace Avalonia.ReactiveUI AvaloniaProperty.Register(nameof(DefaultContent)); /// - /// Gets or sets the animation played when content appears. + /// Gets or sets the animation played when content appears and disappears. /// - public IAnimation FadeInAnimation + public IPageTransition PageTransition { - get => GetValue(FadeInAnimationProperty); - set => SetValue(FadeInAnimationProperty, value); + get => GetValue(PageTransitionProperty); + set => SetValue(PageTransitionProperty, value); } - /// - /// Gets or sets the animation played when content disappears. - /// - public IAnimation FadeOutAnimation - { - get => GetValue(FadeOutAnimationProperty); - set => SetValue(FadeOutAnimationProperty, value); - } - /// /// Gets or sets the content displayed whenever there is no page currently routed. /// @@ -81,53 +65,11 @@ namespace Avalonia.ReactiveUI /// New content to set. private async void UpdateContentWithTransition(object content) { - if (FadeOutAnimation != null) - await FadeOutAnimation.RunAsync(this, Clock); + if (PageTransition != null) + await PageTransition.Start(this, null, true); base.Content = content; - if (FadeInAnimation != null) - await FadeInAnimation.RunAsync(this, Clock); - } - - /// - /// Creates opacity animation for this routed view host. - /// - /// Opacity to start from. - /// Opacity to finish with. - /// Duration of the animation. - /// Animation object instance. - private static IAnimation CreateOpacityAnimation(double from, double to, TimeSpan duration) - { - return new Avalonia.Animation.Animation - { - Duration = duration, - Children = - { - new KeyFrame - { - Setters = - { - new Setter - { - Property = OpacityProperty, - Value = from - } - }, - Cue = new Cue(0d) - }, - new KeyFrame - { - Setters = - { - new Setter - { - Property = OpacityProperty, - Value = to - } - }, - Cue = new Cue(1d) - } - } - }; + if (PageTransition != null) + await PageTransition.Start(null, this, true); } } } \ No newline at end of file diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs index 6b8cb8b755..614d828259 100644 --- a/src/Avalonia.Visuals/Animation/CrossFade.cs +++ b/src/Avalonia.Visuals/Animation/CrossFade.cs @@ -14,8 +14,8 @@ namespace Avalonia.Animation /// public class CrossFade : IPageTransition { - private Animation _fadeOutAnimation; - private Animation _fadeInAnimation; + private readonly Animation _fadeOutAnimation; + private readonly Animation _fadeInAnimation; /// /// Initializes a new instance of the class. @@ -61,10 +61,10 @@ namespace Avalonia.Animation new Setter { Property = Visual.OpacityProperty, - Value = 0d + Value = 1d } }, - Cue = new Cue(0d) + Cue = new Cue(1d) } } diff --git a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs index e873c60e36..8c5b5083e8 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs @@ -62,8 +62,7 @@ namespace Avalonia.ReactiveUI.UnitTests { Router = screen.Router, DefaultContent = defaultContent, - FadeOutAnimation = null, - FadeInAnimation = null + PageTransition = null }; var root = new TestRoot diff --git a/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs index 5ffcede5dd..f09eea5ec5 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs @@ -28,8 +28,7 @@ namespace Avalonia.ReactiveUI.UnitTests { var target = new TransitioningContentControl { - FadeInAnimation = null, - FadeOutAnimation = null, + PageTransition = null, Template = GetTemplate(), Content = "Foo" }; diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs index a21bf34ef5..8bed5adcd4 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs @@ -33,8 +33,7 @@ namespace Avalonia.ReactiveUI.UnitTests var host = new ViewModelViewHost { DefaultContent = defaultContent, - FadeOutAnimation = null, - FadeInAnimation = null + PageTransition = null }; var root = new TestRoot From 12ebd106cbed16c77c64d53b16def9bc929fa7dc Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 29 May 2019 02:12:03 +0300 Subject: [PATCH 053/130] Typo fix --- src/Avalonia.ReactiveUI/TransitioningContentControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs index 68d69bc95f..1bec5fc365 100644 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs @@ -17,7 +17,7 @@ namespace Avalonia.ReactiveUI /// for the property. /// public static readonly AvaloniaProperty PageTransitionProperty = - AvaloniaProperty.Register(nameof(DefaultContent), + AvaloniaProperty.Register(nameof(PageTransition), new CrossFade(TimeSpan.FromSeconds(0.5))); /// From 3764e61bc9e914158feaeaddc1ea8da8e2ebb846 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 01:16:19 +0800 Subject: [PATCH 054/130] Revert back to WPF and start from scratch, using thepower of Hindsight~ --- .../{Grid => }/ColumnDefinition.cs | 6 - .../{Grid => }/ColumnDefinitions.cs | 0 src/Avalonia.Controls/DefinitionBase.cs | 992 ++++ src/Avalonia.Controls/Grid.cs | 4424 +++++++++++++++++ src/Avalonia.Controls/Grid/DefinitionBase.cs | 339 -- src/Avalonia.Controls/Grid/Grid.cs | 2378 --------- src/Avalonia.Controls/Grid/GridCellCache.cs | 26 - src/Avalonia.Controls/Grid/GridLength.cs | 220 - .../Grid/GridLinesRenderer.cs | 91 - src/Avalonia.Controls/Grid/GridSpanKey.cs | 69 - src/Avalonia.Controls/Grid/GridSplitter.cs | 209 - .../Grid/LayoutTimeSizeType.cs | 17 - .../Grid/MaxRatioComparer.cs | 31 - .../Grid/MaxRatioIndexComparer.cs | 46 - .../Grid/MinRatioComparer.cs | 30 - .../Grid/MinRatioIndexComparer.cs | 46 - .../Grid/RoundingErrorIndexComparer.cs | 36 - src/Avalonia.Controls/Grid/SharedSizeScope.cs | 43 - src/Avalonia.Controls/Grid/SharedSizeState.cs | 209 - .../Grid/SpanMaxDistributionOrderComparer.cs | 46 - .../SpanPreferredDistributionOrderComparer.cs | 46 - .../Grid/StarWeightComparer.cs | 29 - .../Grid/StarWeightIndexComparer.cs | 47 - .../{Grid => }/RowDefinition.cs | 7 - .../{Grid => }/RowDefinitions.cs | 0 25 files changed, 5416 insertions(+), 3971 deletions(-) rename src/Avalonia.Controls/{Grid => }/ColumnDefinition.cs (93%) rename src/Avalonia.Controls/{Grid => }/ColumnDefinitions.cs (100%) create mode 100644 src/Avalonia.Controls/DefinitionBase.cs create mode 100644 src/Avalonia.Controls/Grid.cs delete mode 100644 src/Avalonia.Controls/Grid/DefinitionBase.cs delete mode 100644 src/Avalonia.Controls/Grid/Grid.cs delete mode 100644 src/Avalonia.Controls/Grid/GridCellCache.cs delete mode 100644 src/Avalonia.Controls/Grid/GridLength.cs delete mode 100644 src/Avalonia.Controls/Grid/GridLinesRenderer.cs delete mode 100644 src/Avalonia.Controls/Grid/GridSpanKey.cs delete mode 100644 src/Avalonia.Controls/Grid/GridSplitter.cs delete mode 100644 src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs delete mode 100644 src/Avalonia.Controls/Grid/MaxRatioComparer.cs delete mode 100644 src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs delete mode 100644 src/Avalonia.Controls/Grid/MinRatioComparer.cs delete mode 100644 src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs delete mode 100644 src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs delete mode 100644 src/Avalonia.Controls/Grid/SharedSizeScope.cs delete mode 100644 src/Avalonia.Controls/Grid/SharedSizeState.cs delete mode 100644 src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs delete mode 100644 src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs delete mode 100644 src/Avalonia.Controls/Grid/StarWeightComparer.cs delete mode 100644 src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs rename src/Avalonia.Controls/{Grid => }/RowDefinition.cs (93%) rename src/Avalonia.Controls/{Grid => }/RowDefinitions.cs (100%) diff --git a/src/Avalonia.Controls/Grid/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs similarity index 93% rename from src/Avalonia.Controls/Grid/ColumnDefinition.cs rename to src/Avalonia.Controls/ColumnDefinition.cs index 015484dbcc..d316881a05 100644 --- a/src/Avalonia.Controls/Grid/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -87,11 +87,5 @@ namespace Avalonia.Controls get { return GetValue(WidthProperty); } set { SetValue(WidthProperty, value); } } - - internal override GridLength UserSizeValueCache => this.Width; - - internal override double UserMinSizeValueCache => this.MinWidth; - - internal override double UserMaxSizeValueCache => this.MaxWidth; } } diff --git a/src/Avalonia.Controls/Grid/ColumnDefinitions.cs b/src/Avalonia.Controls/ColumnDefinitions.cs similarity index 100% rename from src/Avalonia.Controls/Grid/ColumnDefinitions.cs rename to src/Avalonia.Controls/ColumnDefinitions.cs diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs new file mode 100644 index 0000000000..4878523a70 --- /dev/null +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -0,0 +1,992 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// Specs +// Grid : Grid.mht +// Size Sharing: Size Information Sharing.doc +// +// Misc +// Grid Tutorial: Grid Tutorial.mht +// +// Description: Implementation of base abstract class for ColumnDefinition +// and RowDefinition. +// + +using MS.Internal; +using MS.Internal.KnownBoxes; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Security.Permissions; +using System.Windows; +using System.Windows.Threading; + +namespace System.Windows.Controls +{ + /// + /// DefinitionBase provides core functionality used internally by Grid + /// and ColumnDefinitionCollection / RowDefinitionCollection + /// + [Localizability(LocalizationCategory.Ignore)] + public abstract class DefinitionBase : FrameworkContentElement + { + //------------------------------------------------------ + // + // Constructors + // + //------------------------------------------------------ + + #region Constructors + + internal DefinitionBase(bool isColumnDefinition) + { + _isColumnDefinition = isColumnDefinition; + _parentIndex = -1; + } + + #endregion Constructors + + //------------------------------------------------------ + // + // Public Properties + // + //------------------------------------------------------ + + #region Public Properties + + /// + /// SharedSizeGroup property. + /// + public string SharedSizeGroup + { + get { return (string) GetValue(SharedSizeGroupProperty); } + set { SetValue(SharedSizeGroupProperty, value); } + } + + #endregion Public Properties + + //------------------------------------------------------ + // + // Internal Methods + // + //------------------------------------------------------ + + #region Internal Methods + + /// + /// Callback to notify about entering model tree. + /// + internal void OnEnterParentTree() + { + if (_sharedState == null) + { + // start with getting SharedSizeGroup value. + // this property is NOT inhereted which should result in better overall perf. + string sharedSizeGroupId = SharedSizeGroup; + if (sharedSizeGroupId != null) + { + SharedSizeScope privateSharedSizeScope = PrivateSharedSizeScope; + if (privateSharedSizeScope != null) + { + _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + _sharedState.AddMember(this); + } + } + } + } + + /// + /// Callback to notify about exitting model tree. + /// + internal void OnExitParentTree() + { + _offset = 0; + if (_sharedState != null) + { + _sharedState.RemoveMember(this); + _sharedState = null; + } + } + + /// + /// Performs action preparing definition to enter layout calculation mode. + /// + internal void OnBeforeLayout(Grid grid) + { + // reset layout state. + _minSize = 0; + LayoutWasUpdated = true; + + // defer verification for shared definitions + if (_sharedState != null) { _sharedState.EnsureDeferredValidation(grid); } + } + + /// + /// Updates min size. + /// + /// New size. + internal void UpdateMinSize(double minSize) + { + _minSize = Math.Max(_minSize, minSize); + } + + /// + /// Sets min size. + /// + /// New size. + internal void SetMinSize(double minSize) + { + _minSize = minSize; + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static void OnUserSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase) d; + + if (definition.InParentLogicalTree) + { + if (definition._sharedState != null) + { + definition._sharedState.Invalidate(); + } + else + { + Grid parentGrid = (Grid) definition.Parent; + + if (((GridLength) e.OldValue).GridUnitType != ((GridLength) e.NewValue).GridUnitType) + { + parentGrid.Invalidate(); + } + else + { + parentGrid.InvalidateMeasure(); + } + } + } + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserSizePropertyValueValid(object value) + { + return (((GridLength)value).Value >= 0); + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static void OnUserMinSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase) d; + + if (definition.InParentLogicalTree) + { + Grid parentGrid = (Grid) definition.Parent; + parentGrid.InvalidateMeasure(); + } + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserMinSizePropertyValueValid(object value) + { + double v = (double)value; + return (!DoubleUtil.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static void OnUserMaxSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase) d; + + if (definition.InParentLogicalTree) + { + Grid parentGrid = (Grid) definition.Parent; + parentGrid.InvalidateMeasure(); + } + } + + /// + /// + /// + /// + /// This method needs to be internal to be accessable from derived classes. + /// + internal static bool IsUserMaxSizePropertyValueValid(object value) + { + double v = (double)value; + return (!DoubleUtil.IsNaN(v) && v >= 0.0d); + } + + /// + /// + /// + /// + /// This method reflects Grid.SharedScopeProperty state by setting / clearing + /// dynamic property PrivateSharedSizeScopeProperty. Value of PrivateSharedSizeScopeProperty + /// is a collection of SharedSizeState objects for the scope. + /// Also PrivateSharedSizeScopeProperty is FrameworkPropertyMetadataOptions.Inherits property. So that all children + /// elements belonging to a certain scope can easily access SharedSizeState collection. As well + /// as been norified about enter / exit a scope. + /// + internal static void OnIsSharedSizeScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + // is it possible to optimize here something like this: + // if ((bool)d.GetValue(Grid.IsSharedSizeScopeProperty) == (d.GetLocalValue(PrivateSharedSizeScopeProperty) != null) + // { /* do nothing */ } + if ((bool) e.NewValue) + { + SharedSizeScope sharedStatesCollection = new SharedSizeScope(); + d.SetValue(PrivateSharedSizeScopeProperty, sharedStatesCollection); + } + else + { + d.ClearValue(PrivateSharedSizeScopeProperty); + } + } + + #endregion Internal Methods + + //------------------------------------------------------ + // + // Internal Properties + // + //------------------------------------------------------ + + #region Internal Properties + + /// + /// Returns true if this definition is a part of shared group. + /// + internal bool IsShared + { + get { return (_sharedState != null); } + } + + /// + /// Internal accessor to user size field. + /// + internal GridLength UserSize + { + get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); } + } + + /// + /// Internal accessor to user min size field. + /// + internal double UserMinSize + { + get { return (UserMinSizeValueCache); } + } + + /// + /// Internal accessor to user max size field. + /// + internal double UserMaxSize + { + get { return (UserMaxSizeValueCache); } + } + + /// + /// DefinitionBase's index in the parents collection. + /// + internal int Index + { + get + { + return (_parentIndex); + } + set + { + Debug.Assert(value >= -1 && _parentIndex != value); + _parentIndex = value; + } + } + + /// + /// Layout-time user size type. + /// + internal Grid.LayoutTimeSizeType SizeType + { + get { return (_sizeType); } + set { _sizeType = value; } + } + + /// + /// Returns or sets measure size for the definition. + /// + internal double MeasureSize + { + get { return (_measureSize); } + set { _measureSize = value; } + } + + /// + /// Returns definition's layout time type sensitive preferred size. + /// + /// + /// Returned value is guaranteed to be true preferred size. + /// + internal double PreferredSize + { + get + { + double preferredSize = MinSize; + if ( _sizeType != Grid.LayoutTimeSizeType.Auto + && preferredSize < _measureSize ) + { + preferredSize = _measureSize; + } + return (preferredSize); + } + } + + /// + /// Returns or sets size cache for the definition. + /// + internal double SizeCache + { + get { return (_sizeCache); } + set { _sizeCache = value; } + } + + /// + /// Returns min size. + /// + internal double MinSize + { + get + { + double minSize = _minSize; + if ( UseSharedMinimum + && _sharedState != null + && minSize < _sharedState.MinSize ) + { + minSize = _sharedState.MinSize; + } + return (minSize); + } + } + + /// + /// Returns min size, always taking into account shared state. + /// + internal double MinSizeForArrange + { + get + { + double minSize = _minSize; + if ( _sharedState != null + && (UseSharedMinimum || !LayoutWasUpdated) + && minSize < _sharedState.MinSize ) + { + minSize = _sharedState.MinSize; + } + return (minSize); + } + } + + /// + /// Offset. + /// + internal double FinalOffset + { + get { return _offset; } + set { _offset = value; } + } + + /// + /// Internal helper to access up-to-date UserSize property value. + /// + internal GridLength UserSizeValueCache + { + get + { + return (GridLength) GetValue( + _isColumnDefinition ? + ColumnDefinition.WidthProperty : + RowDefinition.HeightProperty); + } + } + + /// + /// Internal helper to access up-to-date UserMinSize property value. + /// + internal double UserMinSizeValueCache + { + get + { + return (double) GetValue( + _isColumnDefinition ? + ColumnDefinition.MinWidthProperty : + RowDefinition.MinHeightProperty); + } + } + + /// + /// Internal helper to access up-to-date UserMaxSize property value. + /// + internal double UserMaxSizeValueCache + { + get + { + return (double) GetValue( + _isColumnDefinition ? + ColumnDefinition.MaxWidthProperty : + RowDefinition.MaxHeightProperty); + } + } + + /// + /// Protected. Returns true if this DefinitionBase instance is in parent's logical tree. + /// + internal bool InParentLogicalTree + { + get { return (_parentIndex != -1); } + } + + #endregion Internal Properties + + //------------------------------------------------------ + // + // Private Methods + // + //------------------------------------------------------ + + #region Private Methods + + /// + /// SetFlags is used to set or unset one or multiple + /// flags on the object. + /// + private void SetFlags(bool value, Flags flags) + { + _flags = value ? (_flags | flags) : (_flags & (~flags)); + } + + /// + /// CheckFlagsAnd returns true if all the flags in the + /// given bitmask are set on the object. + /// + private bool CheckFlagsAnd(Flags flags) + { + return ((_flags & flags) == flags); + } + + /// + /// + /// + private static void OnSharedSizeGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase) d; + + if (definition.InParentLogicalTree) + { + string sharedSizeGroupId = (string) e.NewValue; + + if (definition._sharedState != null) + { + // if definition is already registered AND shared size group id is changing, + // then un-register the definition from the current shared size state object. + definition._sharedState.RemoveMember(definition); + definition._sharedState = null; + } + + if ((definition._sharedState == null) && (sharedSizeGroupId != null)) + { + SharedSizeScope privateSharedSizeScope = definition.PrivateSharedSizeScope; + if (privateSharedSizeScope != null) + { + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + definition._sharedState.AddMember(definition); + } + } + } + } + + /// + /// + /// + /// + /// Verifies that Shared Size Group Property string + /// a) not empty. + /// b) contains only letters, digits and underscore ('_'). + /// c) does not start with a digit. + /// + private static bool SharedSizeGroupPropertyValueValid(object value) + { + // null is default value + if (value == null) + { + return (true); + } + + string id = (string)value; + + if (id != string.Empty) + { + int i = -1; + while (++i < id.Length) + { + bool isDigit = Char.IsDigit(id[i]); + + if ( (i == 0 && isDigit) + || !( isDigit + || Char.IsLetter(id[i]) + || '_' == id[i] ) ) + { + break; + } + } + + if (i == id.Length) + { + return (true); + } + } + + return (false); + } + + /// + /// + /// + /// + /// OnPrivateSharedSizeScopePropertyChanged is called when new scope enters or + /// existing scope just left. In both cases if the DefinitionBase object is already registered + /// in SharedSizeState, it should un-register and register itself in a new one. + /// + private static void OnPrivateSharedSizeScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + DefinitionBase definition = (DefinitionBase)d; + + if (definition.InParentLogicalTree) + { + SharedSizeScope privateSharedSizeScope = (SharedSizeScope) e.NewValue; + + if (definition._sharedState != null) + { + // if definition is already registered And shared size scope is changing, + // then un-register the definition from the current shared size state object. + definition._sharedState.RemoveMember(definition); + definition._sharedState = null; + } + + if ((definition._sharedState == null) && (privateSharedSizeScope != null)) + { + string sharedSizeGroup = definition.SharedSizeGroup; + if (sharedSizeGroup != null) + { + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(definition.SharedSizeGroup); + definition._sharedState.AddMember(definition); + } + } + } + } + + #endregion Private Methods + + //------------------------------------------------------ + // + // Private Properties + // + //------------------------------------------------------ + + #region Private Properties + + /// + /// Private getter of shared state collection dynamic property. + /// + private SharedSizeScope PrivateSharedSizeScope + { + get { return (SharedSizeScope) GetValue(PrivateSharedSizeScopeProperty); } + } + + /// + /// Convenience accessor to UseSharedMinimum flag + /// + private bool UseSharedMinimum + { + get { return (CheckFlagsAnd(Flags.UseSharedMinimum)); } + set { SetFlags(value, Flags.UseSharedMinimum); } + } + + /// + /// Convenience accessor to LayoutWasUpdated flag + /// + private bool LayoutWasUpdated + { + get { return (CheckFlagsAnd(Flags.LayoutWasUpdated)); } + set { SetFlags(value, Flags.LayoutWasUpdated); } + } + + #endregion Private Properties + + //------------------------------------------------------ + // + // Private Fields + // + //------------------------------------------------------ + + #region Private Fields + private readonly bool _isColumnDefinition; // when "true", this is a ColumnDefinition; when "false" this is a RowDefinition (faster than a type check) + private Flags _flags; // flags reflecting various aspects of internal state + private int _parentIndex; // this instance's index in parent's children collection + + private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" + + private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's + private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure + private double _sizeCache; // cache used for various purposes (sorting, caching, etc) during calculations + private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) + + private SharedSizeState _sharedState; // reference to shared state object this instance is registered with + + internal const bool ThisIsColumnDefinition = true; + internal const bool ThisIsRowDefinition = false; + + #endregion Private Fields + + //------------------------------------------------------ + // + // Private Structures / Classes + // + //------------------------------------------------------ + + #region Private Structures Classes + + [System.Flags] + private enum Flags : byte + { + // + // bool flags + // + UseSharedMinimum = 0x00000020, // when "1", definition will take into account shared state's minimum + LayoutWasUpdated = 0x00000040, // set to "1" every time the parent grid is measured + } + + /// + /// Collection of shared states objects for a single scope + /// + private class SharedSizeScope + { + /// + /// Returns SharedSizeState object for a given group. + /// Creates a new StatedState object if necessary. + /// + internal SharedSizeState EnsureSharedState(string sharedSizeGroup) + { + // check that sharedSizeGroup is not default + Debug.Assert(sharedSizeGroup != null); + + SharedSizeState sharedState = _registry[sharedSizeGroup] as SharedSizeState; + if (sharedState == null) + { + sharedState = new SharedSizeState(this, sharedSizeGroup); + _registry[sharedSizeGroup] = sharedState; + } + return (sharedState); + } + + /// + /// Removes an entry in the registry by the given key. + /// + internal void Remove(object key) + { + Debug.Assert(_registry.Contains(key)); + _registry.Remove(key); + } + + private Hashtable _registry = new Hashtable(); // storage for shared state objects + } + + /// + /// Implementation of per shared group state object + /// + private class SharedSizeState + { + /// + /// Default ctor. + /// + internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) + { + Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null); + _sharedSizeScope = sharedSizeScope; + _sharedSizeGroupId = sharedSizeGroupId; + _registry = new List(); + _layoutUpdated = new EventHandler(OnLayoutUpdated); + _broadcastInvalidation = true; + } + + /// + /// Adds / registers a definition instance. + /// + internal void AddMember(DefinitionBase member) + { + Debug.Assert(!_registry.Contains(member)); + _registry.Add(member); + Invalidate(); + } + + /// + /// Removes / un-registers a definition instance. + /// + /// + /// If the collection of registered definitions becomes empty + /// instantiates self removal from owner's collection. + /// + internal void RemoveMember(DefinitionBase member) + { + Invalidate(); + _registry.Remove(member); + + if (_registry.Count == 0) + { + _sharedSizeScope.Remove(_sharedSizeGroupId); + } + } + + /// + /// Propogates invalidations for all registered definitions. + /// Resets its own state. + /// + internal void Invalidate() + { + _userSizeValid = false; + + if (_broadcastInvalidation) + { + for (int i = 0, count = _registry.Count; i < count; ++i) + { + Grid parentGrid = (Grid)(_registry[i].Parent); + parentGrid.Invalidate(); + } + _broadcastInvalidation = false; + } + } + + /// + /// Makes sure that one and only one layout updated handler is registered for this shared state. + /// + internal void EnsureDeferredValidation(UIElement layoutUpdatedHost) + { + if (_layoutUpdatedHost == null) + { + _layoutUpdatedHost = layoutUpdatedHost; + _layoutUpdatedHost.LayoutUpdated += _layoutUpdated; + } + } + + /// + /// DefinitionBase's specific code. + /// + internal double MinSize + { + get + { + if (!_userSizeValid) { EnsureUserSizeValid(); } + return (_minSize); + } + } + + /// + /// DefinitionBase's specific code. + /// + internal GridLength UserSize + { + get + { + if (!_userSizeValid) { EnsureUserSizeValid(); } + return (_userSize); + } + } + + private void EnsureUserSizeValid() + { + _userSize = new GridLength(1, GridUnitType.Auto); + + for (int i = 0, count = _registry.Count; i < count; ++i) + { + Debug.Assert( _userSize.GridUnitType == GridUnitType.Auto + || _userSize.GridUnitType == GridUnitType.Pixel ); + + GridLength currentGridLength = _registry[i].UserSizeValueCache; + if (currentGridLength.GridUnitType == GridUnitType.Pixel) + { + if (_userSize.GridUnitType == GridUnitType.Auto) + { + _userSize = currentGridLength; + } + else if (_userSize.Value < currentGridLength.Value) + { + _userSize = currentGridLength; + } + } + } + // taking maximum with user size effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + _minSize = _userSize.IsAbsolute ? _userSize.Value : 0.0; + + _userSizeValid = true; + } + + /// + /// OnLayoutUpdated handler. Validates that all participating definitions + /// have updated min size value. Forces another layout update cycle if needed. + /// + private void OnLayoutUpdated(object sender, EventArgs e) + { + double sharedMinSize = 0; + + // accumulate min size of all participating definitions + for (int i = 0, count = _registry.Count; i < count; ++i) + { + sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); + } + + bool sharedMinSizeChanged = !DoubleUtil.AreClose(_minSize, sharedMinSize); + + // compare accumulated min size with min sizes of the individual definitions + for (int i = 0, count = _registry.Count; i < count; ++i) + { + DefinitionBase definitionBase = _registry[i]; + + if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) + { + // if definition's min size is different, then need to re-measure + if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.MinSize)) + { + Grid parentGrid = (Grid)definitionBase.Parent; + parentGrid.InvalidateMeasure(); + definitionBase.UseSharedMinimum = true; + } + else + { + definitionBase.UseSharedMinimum = false; + + // if measure is valid then also need to check arrange. + // Note: definitionBase.SizeCache is volatile but at this point + // it contains up-to-date final size + if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.SizeCache)) + { + Grid parentGrid = (Grid)definitionBase.Parent; + parentGrid.InvalidateArrange(); + } + } + + definitionBase.LayoutWasUpdated = false; + } + } + + _minSize = sharedMinSize; + + _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; + _layoutUpdatedHost = null; + + _broadcastInvalidation = true; + } + + private readonly SharedSizeScope _sharedSizeScope; // the scope this state belongs to + private readonly string _sharedSizeGroupId; // Id of the shared size group this object is servicing + private readonly List _registry; // registry of participating definitions + private readonly EventHandler _layoutUpdated; // instance event handler for layout updated event + private UIElement _layoutUpdatedHost; // UIElement for which layout updated event handler is registered + private bool _broadcastInvalidation; // "true" when broadcasting of invalidation is needed + private bool _userSizeValid; // "true" when _userSize is up to date + private GridLength _userSize; // shared state + private double _minSize; // shared state + } + + #endregion Private Structures Classes + + //------------------------------------------------------ + // + // Properties + // + //------------------------------------------------------ + + #region Properties + + /// + /// Private shared size scope property holds a collection of shared state objects for the a given shared size scope. + /// + /// + internal static readonly DependencyProperty PrivateSharedSizeScopeProperty = + DependencyProperty.RegisterAttached( + "PrivateSharedSizeScope", + typeof(SharedSizeScope), + typeof(DefinitionBase), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.Inherits)); + + /// + /// Shared size group property marks column / row definition as belonging to a group "Foo" or "Bar". + /// + /// + /// Value of the Shared Size Group Property must satisfy the following rules: + /// + /// + /// String must not be empty. + /// + /// + /// String must consist of letters, digits and underscore ('_') only. + /// + /// + /// String must not start with a digit. + /// + /// + /// + public static readonly DependencyProperty SharedSizeGroupProperty = + DependencyProperty.Register( + "SharedSizeGroup", + typeof(string), + typeof(DefinitionBase), + new FrameworkPropertyMetadata(new PropertyChangedCallback(OnSharedSizeGroupPropertyChanged)), + new ValidateValueCallback(SharedSizeGroupPropertyValueValid)); + + /// + /// Static ctor. Used for static registration of properties. + /// + static DefinitionBase() + { + PrivateSharedSizeScopeProperty.OverrideMetadata( + typeof(DefinitionBase), + new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPrivateSharedSizeScopePropertyChanged))); + } + + #endregion Properties + } +} diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs new file mode 100644 index 0000000000..fa310b73ba --- /dev/null +++ b/src/Avalonia.Controls/Grid.cs @@ -0,0 +1,4424 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// Description: Grid implementation. +// +// Specs +// Grid : Grid.mht +// Size Sharing: Size Information Sharing.doc +// +// Misc +// Grid Tutorial: Grid Tutorial.mht +// + +using MS.Internal; +using MS.Internal.Controls; +using MS.Internal.PresentationFramework; +using MS.Internal.Telemetry.PresentationFramework; +using MS.Utility; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Threading; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Markup; + +#pragma warning disable 1634, 1691 // suppressing PreSharp warnings + +namespace System.Windows.Controls +{ + /// + /// Grid + /// + public class Grid : Panel, IAddChild + { + //------------------------------------------------------ + // + // Constructors + // + //------------------------------------------------------ + + #region Constructors + + static Grid() + { + ControlsTraceLogger.AddControl(TelemetryControls.Grid); + } + + /// + /// Default constructor. + /// + public Grid() + { + SetFlags((bool) ShowGridLinesProperty.GetDefaultValue(DependencyObjectType), Flags.ShowGridLinesPropertyValue); + } + + #endregion Constructors + + //------------------------------------------------------ + // + // Public Methods + // + //------------------------------------------------------ + + #region Public Methods + + /// + /// + /// + void IAddChild.AddChild(object value) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + UIElement cell = value as UIElement; + if (cell != null) + { + Children.Add(cell); + return; + } + + throw (new ArgumentException(SR.Get(SRID.Grid_UnexpectedParameterType, value.GetType(), typeof(UIElement)), "value")); + } + + /// + /// + /// + void IAddChild.AddText(string text) + { + XamlSerializerUtil.ThrowIfNonWhiteSpaceInAddText(text, this); + } + + /// + /// + /// + protected internal override IEnumerator LogicalChildren + { + get + { + // empty panel or a panel being used as the items + // host has *no* logical children; give empty enumerator + bool noChildren = (base.VisualChildrenCount == 0) || IsItemsHost; + + if (noChildren) + { + ExtendedData extData = ExtData; + + if ( extData == null + || ( (extData.ColumnDefinitions == null || extData.ColumnDefinitions.Count == 0) + && (extData.RowDefinitions == null || extData.RowDefinitions.Count == 0) ) + ) + { + // grid is empty + return EmptyEnumerator.Instance; + } + } + + return (new GridChildrenCollectionEnumeratorSimple(this, !noChildren)); + } + } + + /// + /// Helper for setting Column property on a UIElement. + /// + /// UIElement to set Column property on. + /// Column property value. + public static void SetColumn(UIElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(ColumnProperty, value); + } + + /// + /// Helper for reading Column property from a UIElement. + /// + /// UIElement to read Column property from. + /// Column property value. + [AttachedPropertyBrowsableForChildren()] + public static int GetColumn(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((int)element.GetValue(ColumnProperty)); + } + + /// + /// Helper for setting Row property on a UIElement. + /// + /// UIElement to set Row property on. + /// Row property value. + public static void SetRow(UIElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(RowProperty, value); + } + + /// + /// Helper for reading Row property from a UIElement. + /// + /// UIElement to read Row property from. + /// Row property value. + [AttachedPropertyBrowsableForChildren()] + public static int GetRow(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((int)element.GetValue(RowProperty)); + } + + /// + /// Helper for setting ColumnSpan property on a UIElement. + /// + /// UIElement to set ColumnSpan property on. + /// ColumnSpan property value. + public static void SetColumnSpan(UIElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(ColumnSpanProperty, value); + } + + /// + /// Helper for reading ColumnSpan property from a UIElement. + /// + /// UIElement to read ColumnSpan property from. + /// ColumnSpan property value. + [AttachedPropertyBrowsableForChildren()] + public static int GetColumnSpan(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((int)element.GetValue(ColumnSpanProperty)); + } + + /// + /// Helper for setting RowSpan property on a UIElement. + /// + /// UIElement to set RowSpan property on. + /// RowSpan property value. + public static void SetRowSpan(UIElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(RowSpanProperty, value); + } + + /// + /// Helper for reading RowSpan property from a UIElement. + /// + /// UIElement to read RowSpan property from. + /// RowSpan property value. + [AttachedPropertyBrowsableForChildren()] + public static int GetRowSpan(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((int)element.GetValue(RowSpanProperty)); + } + + /// + /// Helper for setting IsSharedSizeScope property on a UIElement. + /// + /// UIElement to set IsSharedSizeScope property on. + /// IsSharedSizeScope property value. + public static void SetIsSharedSizeScope(UIElement element, bool value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(IsSharedSizeScopeProperty, value); + } + + /// + /// Helper for reading IsSharedSizeScope property from a UIElement. + /// + /// UIElement to read IsSharedSizeScope property from. + /// IsSharedSizeScope property value. + public static bool GetIsSharedSizeScope(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return ((bool)element.GetValue(IsSharedSizeScopeProperty)); + } + + #endregion Public Methods + + //------------------------------------------------------ + // + // Public Properties + // + //------------------------------------------------------ + + #region Public Properties + + /// + /// ShowGridLines property. + /// + public bool ShowGridLines + { + get { return (CheckFlagsAnd(Flags.ShowGridLinesPropertyValue)); } + set { SetValue(ShowGridLinesProperty, value); } + } + + /// + /// Returns a ColumnDefinitionCollection of column definitions. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public ColumnDefinitionCollection ColumnDefinitions + { + get + { + if (_data == null) { _data = new ExtendedData(); } + if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitionCollection(this); } + + return (_data.ColumnDefinitions); + } + } + + /// + /// Returns a RowDefinitionCollection of row definitions. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public RowDefinitionCollection RowDefinitions + { + get + { + if (_data == null) { _data = new ExtendedData(); } + if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitionCollection(this); } + + return (_data.RowDefinitions); + } + } + + #endregion Public Properties + + //------------------------------------------------------ + // + // Protected Methods + // + //------------------------------------------------------ + + #region Protected Methods + + /// + /// Derived class must implement to support Visual children. The method must return + /// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1. + /// + /// By default a Visual does not have any children. + /// + /// Remark: + /// During this virtual call it is not valid to modify the Visual tree. + /// + protected override Visual GetVisualChild(int index) + { + // because "base.Count + 1" for GridLinesRenderer + // argument checking done at the base class + if(index == base.VisualChildrenCount) + { + if (_gridLinesRenderer == null) + { + throw new ArgumentOutOfRangeException("index", index, SR.Get(SRID.Visual_ArgumentOutOfRange)); + } + return _gridLinesRenderer; + } + else return base.GetVisualChild(index); + } + + /// + /// Derived classes override this property to enable the Visual code to enumerate + /// the Visual children. Derived classes need to return the number of children + /// from this method. + /// + /// By default a Visual does not have any children. + /// + /// Remark: During this virtual method the Visual tree must not be modified. + /// + protected override int VisualChildrenCount + { + //since GridLinesRenderer has not been added as a child, so we do not subtract + get { return base.VisualChildrenCount + (_gridLinesRenderer != null ? 1 : 0); } + } + + + /// + /// Content measurement. + /// + /// Constraint + /// Desired size + protected override Size MeasureOverride(Size constraint) + { + Size gridDesiredSize; + ExtendedData extData = ExtData; + + try + { + EnterCounterScope(Counters.MeasureOverride); + + ListenToNotifications = true; + MeasureOverrideInProgress = true; + + if (extData == null) + { + gridDesiredSize = new Size(); + UIElementCollection children = InternalChildren; + + for (int i = 0, count = children.Count; i < count; ++i) + { + UIElement child = children[i]; + if (child != null) + { + child.Measure(constraint); + gridDesiredSize.Width = Math.Max(gridDesiredSize.Width, child.DesiredSize.Width); + gridDesiredSize.Height = Math.Max(gridDesiredSize.Height, child.DesiredSize.Height); + } + } + } + else + { + { + bool sizeToContentU = double.IsPositiveInfinity(constraint.Width); + bool sizeToContentV = double.IsPositiveInfinity(constraint.Height); + + // Clear index information and rounding errors + if (RowDefinitionCollectionDirty || ColumnDefinitionCollectionDirty) + { + if (_definitionIndices != null) + { + Array.Clear(_definitionIndices, 0, _definitionIndices.Length); + _definitionIndices = null; + } + + if (UseLayoutRounding) + { + if (_roundingErrors != null) + { + Array.Clear(_roundingErrors, 0, _roundingErrors.Length); + _roundingErrors = null; + } + } + } + + ValidateDefinitionsUStructure(); + ValidateDefinitionsLayout(DefinitionsU, sizeToContentU); + + ValidateDefinitionsVStructure(); + ValidateDefinitionsLayout(DefinitionsV, sizeToContentV); + + CellsStructureDirty |= (SizeToContentU != sizeToContentU) || (SizeToContentV != sizeToContentV); + + SizeToContentU = sizeToContentU; + SizeToContentV = sizeToContentV; + } + + ValidateCells(); + + Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); + + // Grid classifies cells into four groups depending on + // the column / row type a cell belongs to (number corresponds to + // group number): + // + // Px Auto Star + // +--------+--------+--------+ + // | | | | + // Px | 1 | 1 | 3 | + // | | | | + // +--------+--------+--------+ + // | | | | + // Auto | 1 | 1 | 3 | + // | | | | + // +--------+--------+--------+ + // | | | | + // Star | 4 | 2 | 4 | + // | | | | + // +--------+--------+--------+ + // + // The group number indicates the order in which cells are measured. + // Certain order is necessary to be able to dynamically resolve star + // columns / rows sizes which are used as input for measuring of + // the cells belonging to them. + // + // However, there are cases when topology of a grid causes cyclical + // size dependences. For example: + // + // + // column width="Auto" column width="*" + // +----------------------+----------------------+ + // | | | + // | | | + // | | | + // | | | + // row height="Auto" | | cell 1 2 | + // | | | + // | | | + // | | | + // | | | + // +----------------------+----------------------+ + // | | | + // | | | + // | | | + // | | | + // row height="*" | cell 2 1 | | + // | | | + // | | | + // | | | + // | | | + // +----------------------+----------------------+ + // + // In order to accurately calculate constraint width for "cell 1 2" + // (which is the remaining of grid's available width and calculated + // value of Auto column), "cell 2 1" needs to be calculated first, + // as it contributes to the Auto column's calculated value. + // At the same time in order to accurately calculate constraint + // height for "cell 2 1", "cell 1 2" needs to be calcualted first, + // as it contributes to Auto row height, which is used in the + // computation of Star row resolved height. + // + // to "break" this cyclical dependency we are making (arbitrary) + // decision to treat cells like "cell 2 1" as if they appear in Auto + // rows. And then recalculate them one more time when star row + // heights are resolved. + // + // (Or more strictly) the code below implement the following logic: + // + // +---------+ + // | enter | + // +---------+ + // | + // V + // +----------------+ + // | Measure Group1 | + // +----------------+ + // | + // V + // / - \ + // / \ + // Y / Can \ N + // +--------| Resolve |-----------+ + // | \ StarsV? / | + // | \ / | + // | \ - / | + // V V + // +----------------+ / - \ + // | Resolve StarsV | / \ + // +----------------+ Y / Can \ N + // | +----| Resolve |------+ + // V | \ StarsU? / | + // +----------------+ | \ / | + // | Measure Group2 | | \ - / | + // +----------------+ | V + // | | +-----------------+ + // V | | Measure Group2' | + // +----------------+ | +-----------------+ + // | Resolve StarsU | | | + // +----------------+ V V + // | +----------------+ +----------------+ + // V | Resolve StarsU | | Resolve StarsU | + // +----------------+ +----------------+ +----------------+ + // | Measure Group3 | | | + // +----------------+ V V + // | +----------------+ +----------------+ + // | | Measure Group3 | | Measure Group3 | + // | +----------------+ +----------------+ + // | | | + // | V V + // | +----------------+ +----------------+ + // | | Resolve StarsV | | Resolve StarsV | + // | +----------------+ +----------------+ + // | | | + // | | V + // | | +------------------+ + // | | | Measure Group2'' | + // | | +------------------+ + // | | | + // +----------------------+-------------------------+ + // | + // V + // +----------------+ + // | Measure Group4 | + // +----------------+ + // | + // V + // +--------+ + // | exit | + // +--------+ + // + // where: + // * all [Measure GroupN] - regular children measure process - + // each cell is measured given contraint size as an input + // and each cell's desired size is accumulated on the + // corresponding column / row; + // * [Measure Group2'] - is when each cell is measured with + // infinit height as a constraint and a cell's desired + // height is ignored; + // * [Measure Groups''] - is when each cell is measured (second + // time during single Grid.MeasureOverride) regularly but its + // returned width is ignored; + // + // This algorithm is believed to be as close to ideal as possible. + // It has the following drawbacks: + // * cells belonging to Group2 can be called to measure twice; + // * iff during second measure a cell belonging to Group2 returns + // desired width greater than desired width returned the first + // time, such a cell is going to be clipped, even though it + // appears in Auto column. + // + + MeasureCellsGroup(extData.CellGroup1, constraint, false, false); + + { + // after Group1 is measured, only Group3 may have cells belonging to Auto rows. + bool canResolveStarsV = !HasGroup3CellsInAutoRows; + + if (canResolveStarsV) + { + if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } + MeasureCellsGroup(extData.CellGroup2, constraint, false, false); + if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } + MeasureCellsGroup(extData.CellGroup3, constraint, false, false); + } + else + { + // if at least one cell exists in Group2, it must be measured before + // StarsU can be resolved. + bool canResolveStarsU = extData.CellGroup2 > PrivateCells.Length; + if (canResolveStarsU) + { + if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } + MeasureCellsGroup(extData.CellGroup3, constraint, false, false); + if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } + } + else + { + // This is a revision to the algorithm employed for the cyclic + // dependency case described above. We now repeatedly + // measure Group3 and Group2 until their sizes settle. We + // also use a count heuristic to break a loop in case of one. + + bool hasDesiredSizeUChanged = false; + int cnt=0; + + // Cache Group2MinWidths & Group3MinHeights + double[] group2MinSizes = CacheMinSizes(extData.CellGroup2, false); + double[] group3MinSizes = CacheMinSizes(extData.CellGroup3, true); + + MeasureCellsGroup(extData.CellGroup2, constraint, false, true); + + do + { + if (hasDesiredSizeUChanged) + { + // Reset cached Group3Heights + ApplyCachedMinSizes(group3MinSizes, true); + } + + if (HasStarCellsU) { ResolveStar(DefinitionsU, constraint.Width); } + MeasureCellsGroup(extData.CellGroup3, constraint, false, false); + + // Reset cached Group2Widths + ApplyCachedMinSizes(group2MinSizes, false); + + if (HasStarCellsV) { ResolveStar(DefinitionsV, constraint.Height); } + MeasureCellsGroup(extData.CellGroup2, constraint, cnt == c_layoutLoopMaxCount, false, out hasDesiredSizeUChanged); + } + while (hasDesiredSizeUChanged && ++cnt <= c_layoutLoopMaxCount); + } + } + } + + MeasureCellsGroup(extData.CellGroup4, constraint, false, false); + + EnterCounter(Counters._CalculateDesiredSize); + gridDesiredSize = new Size( + CalculateDesiredSize(DefinitionsU), + CalculateDesiredSize(DefinitionsV)); + ExitCounter(Counters._CalculateDesiredSize); + } + } + finally + { + MeasureOverrideInProgress = false; + ExitCounterScope(Counters.MeasureOverride); + } + + return (gridDesiredSize); + } + + /// + /// Content arrangement. + /// + /// Arrange size + protected override Size ArrangeOverride(Size arrangeSize) + { + try + { + EnterCounterScope(Counters.ArrangeOverride); + + ArrangeOverrideInProgress = true; + + if (_data == null) + { + UIElementCollection children = InternalChildren; + + for (int i = 0, count = children.Count; i < count; ++i) + { + UIElement child = children[i]; + if (child != null) + { + child.Arrange(new Rect(arrangeSize)); + } + } + } + else + { + Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); + + EnterCounter(Counters._SetFinalSize); + + SetFinalSize(DefinitionsU, arrangeSize.Width, true); + SetFinalSize(DefinitionsV, arrangeSize.Height, false); + + ExitCounter(Counters._SetFinalSize); + + UIElementCollection children = InternalChildren; + + for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) + { + UIElement cell = children[currentCell]; + if (cell == null) + { + continue; + } + + int columnIndex = PrivateCells[currentCell].ColumnIndex; + int rowIndex = PrivateCells[currentCell].RowIndex; + int columnSpan = PrivateCells[currentCell].ColumnSpan; + int rowSpan = PrivateCells[currentCell].RowSpan; + + Rect cellRect = new Rect( + columnIndex == 0 ? 0.0 : DefinitionsU[columnIndex].FinalOffset, + rowIndex == 0 ? 0.0 : DefinitionsV[rowIndex].FinalOffset, + GetFinalSizeForRange(DefinitionsU, columnIndex, columnSpan), + GetFinalSizeForRange(DefinitionsV, rowIndex, rowSpan) ); + + EnterCounter(Counters._ArrangeChildHelper2); + cell.Arrange(cellRect); + ExitCounter(Counters._ArrangeChildHelper2); + } + + // update render bound on grid lines renderer visual + GridLinesRenderer gridLinesRenderer = EnsureGridLinesRenderer(); + if (gridLinesRenderer != null) + { + gridLinesRenderer.UpdateRenderBounds(arrangeSize); + } + } + } + finally + { + SetValid(); + ArrangeOverrideInProgress = false; + ExitCounterScope(Counters.ArrangeOverride); + } + return (arrangeSize); + } + + /// + /// + /// + protected internal override void OnVisualChildrenChanged( + DependencyObject visualAdded, + DependencyObject visualRemoved) + { + CellsStructureDirty = true; + + base.OnVisualChildrenChanged(visualAdded, visualRemoved); + } + + #endregion Protected Methods + + //------------------------------------------------------ + // + // Internal Methods + // + //------------------------------------------------------ + + #region Internal Methods + + /// + /// Invalidates grid caches and makes the grid dirty for measure. + /// + internal void Invalidate() + { + CellsStructureDirty = true; + InvalidateMeasure(); + } + + /// + /// Returns final width for a column. + /// + /// + /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data. + /// + internal double GetFinalColumnDefinitionWidth(int columnIndex) + { + double value = 0.0; + + Invariant.Assert(_data != null); + + // actual value calculations require structure to be up-to-date + if (!ColumnDefinitionCollectionDirty) + { + DefinitionBase[] definitions = DefinitionsU; + value = definitions[(columnIndex + 1) % definitions.Length].FinalOffset; + if (columnIndex != 0) { value -= definitions[columnIndex].FinalOffset; } + } + return (value); + } + + /// + /// Returns final height for a row. + /// + /// + /// Used from public RowDefinition ActualHeight. Calculates final height using offset data. + /// + internal double GetFinalRowDefinitionHeight(int rowIndex) + { + double value = 0.0; + + Invariant.Assert(_data != null); + + // actual value calculations require structure to be up-to-date + if (!RowDefinitionCollectionDirty) + { + DefinitionBase[] definitions = DefinitionsV; + value = definitions[(rowIndex + 1) % definitions.Length].FinalOffset; + if (rowIndex != 0) { value -= definitions[rowIndex].FinalOffset; } + } + return (value); + } + + #endregion Internal Methods + + //------------------------------------------------------ + // + // Internal Properties + // + //------------------------------------------------------ + + #region Internal Properties + + /// + /// Convenience accessor to MeasureOverrideInProgress bit flag. + /// + internal bool MeasureOverrideInProgress + { + get { return (CheckFlagsAnd(Flags.MeasureOverrideInProgress)); } + set { SetFlags(value, Flags.MeasureOverrideInProgress); } + } + + /// + /// Convenience accessor to ArrangeOverrideInProgress bit flag. + /// + internal bool ArrangeOverrideInProgress + { + get { return (CheckFlagsAnd(Flags.ArrangeOverrideInProgress)); } + set { SetFlags(value, Flags.ArrangeOverrideInProgress); } + } + + /// + /// Convenience accessor to ValidDefinitionsUStructure bit flag. + /// + internal bool ColumnDefinitionCollectionDirty + { + get { return (!CheckFlagsAnd(Flags.ValidDefinitionsUStructure)); } + set { SetFlags(!value, Flags.ValidDefinitionsUStructure); } + } + + /// + /// Convenience accessor to ValidDefinitionsVStructure bit flag. + /// + internal bool RowDefinitionCollectionDirty + { + get { return (!CheckFlagsAnd(Flags.ValidDefinitionsVStructure)); } + set { SetFlags(!value, Flags.ValidDefinitionsVStructure); } + } + + #endregion Internal Properties + + //------------------------------------------------------ + // + // Private Methods + // + //------------------------------------------------------ + + #region Private Methods + + /// + /// Lays out cells according to rows and columns, and creates lookup grids. + /// + private void ValidateCells() + { + EnterCounter(Counters._ValidateCells); + + if (CellsStructureDirty) + { + ValidateCellsCore(); + CellsStructureDirty = false; + } + + ExitCounter(Counters._ValidateCells); + } + + /// + /// ValidateCellsCore + /// + private void ValidateCellsCore() + { + UIElementCollection children = InternalChildren; + ExtendedData extData = ExtData; + + extData.CellCachesCollection = new CellCache[children.Count]; + extData.CellGroup1 = int.MaxValue; + extData.CellGroup2 = int.MaxValue; + extData.CellGroup3 = int.MaxValue; + extData.CellGroup4 = int.MaxValue; + + bool hasStarCellsU = false; + bool hasStarCellsV = false; + bool hasGroup3CellsInAutoRows = false; + + for (int i = PrivateCells.Length - 1; i >= 0; --i) + { + UIElement child = children[i]; + if (child == null) + { + continue; + } + + CellCache cell = new CellCache(); + + // + // read and cache child positioning properties + // + + // read indices from the corresponding properties + // clamp to value < number_of_columns + // column >= 0 is guaranteed by property value validation callback + cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Length - 1); + // clamp to value < number_of_rows + // row >= 0 is guaranteed by property value validation callback + cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Length - 1); + + // read span properties + // clamp to not exceed beyond right side of the grid + // column_span > 0 is guaranteed by property value validation callback + cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Length - cell.ColumnIndex); + + // clamp to not exceed beyond bottom side of the grid + // row_span > 0 is guaranteed by property value validation callback + cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Length - cell.RowIndex); + + Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Length); + Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Length); + + // + // calculate and cache length types for the child + // + + cell.SizeTypeU = GetLengthTypeForRange(DefinitionsU, cell.ColumnIndex, cell.ColumnSpan); + cell.SizeTypeV = GetLengthTypeForRange(DefinitionsV, cell.RowIndex, cell.RowSpan); + + hasStarCellsU |= cell.IsStarU; + hasStarCellsV |= cell.IsStarV; + + // + // distribute cells into four groups. + // + + if (!cell.IsStarV) + { + if (!cell.IsStarU) + { + cell.Next = extData.CellGroup1; + extData.CellGroup1 = i; + } + else + { + cell.Next = extData.CellGroup3; + extData.CellGroup3 = i; + + // remember if this cell belongs to auto row + hasGroup3CellsInAutoRows |= cell.IsAutoV; + } + } + else + { + if ( cell.IsAutoU + // note below: if spans through Star column it is NOT Auto + && !cell.IsStarU ) + { + cell.Next = extData.CellGroup2; + extData.CellGroup2 = i; + } + else + { + cell.Next = extData.CellGroup4; + extData.CellGroup4 = i; + } + } + + PrivateCells[i] = cell; + } + + HasStarCellsU = hasStarCellsU; + HasStarCellsV = hasStarCellsV; + HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows; + } + + /// + /// Initializes DefinitionsU memeber either to user supplied ColumnDefinitions collection + /// or to a default single element collection. DefinitionsU gets trimmed to size. + /// + /// + /// This is one of two methods, where ColumnDefinitions and DefinitionsU are directly accessed. + /// All the rest measure / arrange / render code must use DefinitionsU. + /// + private void ValidateDefinitionsUStructure() + { + EnterCounter(Counters._ValidateColsStructure); + + if (ColumnDefinitionCollectionDirty) + { + ExtendedData extData = ExtData; + + if (extData.ColumnDefinitions == null) + { + if (extData.DefinitionsU == null) + { + extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() }; + } + } + else + { + extData.ColumnDefinitions.InternalTrimToSize(); + + if (extData.ColumnDefinitions.InternalCount == 0) + { + // if column definitions collection is empty + // mockup array with one column + extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() }; + } + else + { + extData.DefinitionsU = extData.ColumnDefinitions.InternalItems; + } + } + + ColumnDefinitionCollectionDirty = false; + } + + Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Length > 0); + + ExitCounter(Counters._ValidateColsStructure); + } + + /// + /// Initializes DefinitionsV memeber either to user supplied RowDefinitions collection + /// or to a default single element collection. DefinitionsV gets trimmed to size. + /// + /// + /// This is one of two methods, where RowDefinitions and DefinitionsV are directly accessed. + /// All the rest measure / arrange / render code must use DefinitionsV. + /// + private void ValidateDefinitionsVStructure() + { + EnterCounter(Counters._ValidateRowsStructure); + + if (RowDefinitionCollectionDirty) + { + ExtendedData extData = ExtData; + + if (extData.RowDefinitions == null) + { + if (extData.DefinitionsV == null) + { + extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() }; + } + } + else + { + extData.RowDefinitions.InternalTrimToSize(); + + if (extData.RowDefinitions.InternalCount == 0) + { + // if row definitions collection is empty + // mockup array with one row + extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() }; + } + else + { + extData.DefinitionsV = extData.RowDefinitions.InternalItems; + } + } + + RowDefinitionCollectionDirty = false; + } + + Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Length > 0); + + ExitCounter(Counters._ValidateRowsStructure); + } + + /// + /// Validates layout time size type information on given array of definitions. + /// Sets MinSize and MeasureSizes. + /// + /// Array of definitions to update. + /// if "true" then star definitions are treated as Auto. + private void ValidateDefinitionsLayout( + DefinitionBase[] definitions, + bool treatStarAsAuto) + { + for (int i = 0; i < definitions.Length; ++i) + { + definitions[i].OnBeforeLayout(this); + + double userMinSize = definitions[i].UserMinSize; + double userMaxSize = definitions[i].UserMaxSize; + double userSize = 0; + + switch (definitions[i].UserSize.GridUnitType) + { + case (GridUnitType.Pixel): + definitions[i].SizeType = LayoutTimeSizeType.Pixel; + userSize = definitions[i].UserSize.Value; + // this was brought with NewLayout and defeats squishy behavior + userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); + break; + case (GridUnitType.Auto): + definitions[i].SizeType = LayoutTimeSizeType.Auto; + userSize = double.PositiveInfinity; + break; + case (GridUnitType.Star): + if (treatStarAsAuto) + { + definitions[i].SizeType = LayoutTimeSizeType.Auto; + userSize = double.PositiveInfinity; + } + else + { + definitions[i].SizeType = LayoutTimeSizeType.Star; + userSize = double.PositiveInfinity; + } + break; + default: + Debug.Assert(false); + break; + } + + definitions[i].UpdateMinSize(userMinSize); + definitions[i].MeasureSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); + } + } + + private double[] CacheMinSizes(int cellsHead, bool isRows) + { + double[] minSizes = isRows ? new double[DefinitionsV.Length] : new double[DefinitionsU.Length]; + + for (int j=0; j + /// Measures one group of cells. + /// + /// Head index of the cells chain. + /// Reference size for spanned cells + /// calculations. + /// When "true" cells' desired + /// width is not registered in columns. + /// Passed through to MeasureCell. + /// When "true" cells' desired height is not registered in rows. + private void MeasureCellsGroup( + int cellsHead, + Size referenceSize, + bool ignoreDesiredSizeU, + bool forceInfinityV, + out bool hasDesiredSizeUChanged) + { + hasDesiredSizeUChanged = false; + + if (cellsHead >= PrivateCells.Length) + { + return; + } + + UIElementCollection children = InternalChildren; + Hashtable spanStore = null; + bool ignoreDesiredSizeV = forceInfinityV; + + int i = cellsHead; + do + { + double oldWidth = children[i].DesiredSize.Width; + + MeasureCell(i, forceInfinityV); + + hasDesiredSizeUChanged |= !DoubleUtil.AreClose(oldWidth, children[i].DesiredSize.Width); + + if (!ignoreDesiredSizeU) + { + if (PrivateCells[i].ColumnSpan == 1) + { + DefinitionsU[PrivateCells[i].ColumnIndex].UpdateMinSize(Math.Min(children[i].DesiredSize.Width, DefinitionsU[PrivateCells[i].ColumnIndex].UserMaxSize)); + } + else + { + RegisterSpan( + ref spanStore, + PrivateCells[i].ColumnIndex, + PrivateCells[i].ColumnSpan, + true, + children[i].DesiredSize.Width); + } + } + + if (!ignoreDesiredSizeV) + { + if (PrivateCells[i].RowSpan == 1) + { + DefinitionsV[PrivateCells[i].RowIndex].UpdateMinSize(Math.Min(children[i].DesiredSize.Height, DefinitionsV[PrivateCells[i].RowIndex].UserMaxSize)); + } + else + { + RegisterSpan( + ref spanStore, + PrivateCells[i].RowIndex, + PrivateCells[i].RowSpan, + false, + children[i].DesiredSize.Height); + } + } + + i = PrivateCells[i].Next; + } while (i < PrivateCells.Length); + + if (spanStore != null) + { + foreach (DictionaryEntry e in spanStore) + { + SpanKey key = (SpanKey)e.Key; + double requestedSize = (double)e.Value; + + EnsureMinSizeInDefinitionRange( + key.U ? DefinitionsU : DefinitionsV, + key.Start, + key.Count, + requestedSize, + key.U ? referenceSize.Width : referenceSize.Height); + } + } + } + + /// + /// Helper method to register a span information for delayed processing. + /// + /// Reference to a hashtable object used as storage. + /// Span starting index. + /// Span count. + /// true if this is a column span. false if this is a row span. + /// Value to store. If an entry already exists the biggest value is stored. + private static void RegisterSpan( + ref Hashtable store, + int start, + int count, + bool u, + double value) + { + if (store == null) + { + store = new Hashtable(); + } + + SpanKey key = new SpanKey(start, count, u); + object o = store[key]; + + if ( o == null + || value > (double)o ) + { + store[key] = value; + } + } + + /// + /// Takes care of measuring a single cell. + /// + /// Index of the cell to measure. + /// If "true" then cell is always + /// calculated to infinite height. + private void MeasureCell( + int cell, + bool forceInfinityV) + { + EnterCounter(Counters._MeasureCell); + + double cellMeasureWidth; + double cellMeasureHeight; + + if ( PrivateCells[cell].IsAutoU + && !PrivateCells[cell].IsStarU ) + { + // if cell belongs to at least one Auto column and not a single Star column + // then it should be calculated "to content", thus it is possible to "shortcut" + // calculations and simply assign PositiveInfinity here. + cellMeasureWidth = double.PositiveInfinity; + } + else + { + // otherwise... + cellMeasureWidth = GetMeasureSizeForRange( + DefinitionsU, + PrivateCells[cell].ColumnIndex, + PrivateCells[cell].ColumnSpan); + } + + if (forceInfinityV) + { + cellMeasureHeight = double.PositiveInfinity; + } + else if ( PrivateCells[cell].IsAutoV + && !PrivateCells[cell].IsStarV ) + { + // if cell belongs to at least one Auto row and not a single Star row + // then it should be calculated "to content", thus it is possible to "shortcut" + // calculations and simply assign PositiveInfinity here. + cellMeasureHeight = double.PositiveInfinity; + } + else + { + cellMeasureHeight = GetMeasureSizeForRange( + DefinitionsV, + PrivateCells[cell].RowIndex, + PrivateCells[cell].RowSpan); + } + + EnterCounter(Counters.__MeasureChild); + UIElement child = InternalChildren[cell]; + if (child != null) + { + Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); + child.Measure(childConstraint); + } + ExitCounter(Counters.__MeasureChild); + + ExitCounter(Counters._MeasureCell); + } + + + /// + /// Calculates one dimensional measure size for given definitions' range. + /// + /// Source array of definitions to read values from. + /// Starting index of the range. + /// Number of definitions included in the range. + /// Calculated measure size. + /// + /// For "Auto" definitions MinWidth is used in place of PreferredSize. + /// + private double GetMeasureSizeForRange( + DefinitionBase[] definitions, + int start, + int count) + { + Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); + + double measureSize = 0; + int i = start + count - 1; + + do + { + measureSize += (definitions[i].SizeType == LayoutTimeSizeType.Auto) + ? definitions[i].MinSize + : definitions[i].MeasureSize; + } while (--i >= start); + + return (measureSize); + } + + /// + /// Accumulates length type information for given definition's range. + /// + /// Source array of definitions to read values from. + /// Starting index of the range. + /// Number of definitions included in the range. + /// Length type for given range. + private LayoutTimeSizeType GetLengthTypeForRange( + DefinitionBase[] definitions, + int start, + int count) + { + Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); + + LayoutTimeSizeType lengthType = LayoutTimeSizeType.None; + int i = start + count - 1; + + do + { + lengthType |= definitions[i].SizeType; + } while (--i >= start); + + return (lengthType); + } + + /// + /// Distributes min size back to definition array's range. + /// + /// Start of the range. + /// Number of items in the range. + /// Minimum size that should "fit" into the definitions range. + /// Definition array receiving distribution. + /// Size used to resolve percentages. + private void EnsureMinSizeInDefinitionRange( + DefinitionBase[] definitions, + int start, + int count, + double requestedSize, + double percentReferenceSize) + { + Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length); + + // avoid processing when asked to distribute "0" + if (!_IsZero(requestedSize)) + { + DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting + int end = start + count; + int autoDefinitionsCount = 0; + double rangeMinSize = 0; + double rangePreferredSize = 0; + double rangeMaxSize = 0; + double maxMaxSize = 0; // maximum of maximum sizes + + // first accumulate the necessary information: + // a) sum up the sizes in the range; + // b) count the number of auto definitions in the range; + // c) initialize temp array + // d) cache the maximum size into SizeCache + // e) accumulate max of max sizes + for (int i = start; i < end; ++i) + { + double minSize = definitions[i].MinSize; + double preferredSize = definitions[i].PreferredSize; + double maxSize = Math.Max(definitions[i].UserMaxSize, minSize); + + rangeMinSize += minSize; + rangePreferredSize += preferredSize; + rangeMaxSize += maxSize; + + definitions[i].SizeCache = maxSize; + + // sanity check: no matter what, but min size must always be the smaller; + // max size must be the biggest; and preferred should be in between + Debug.Assert( minSize <= preferredSize + && preferredSize <= maxSize + && rangeMinSize <= rangePreferredSize + && rangePreferredSize <= rangeMaxSize ); + + if (maxMaxSize < maxSize) maxMaxSize = maxSize; + if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++; + tempDefinitions[i - start] = definitions[i]; + } + + // avoid processing if the range already big enough + if (requestedSize > rangeMinSize) + { + if (requestedSize <= rangePreferredSize) + { + // + // requestedSize fits into preferred size of the range. + // distribute according to the following logic: + // * do not distribute into auto definitions - they should continue to stay "tight"; + // * for all non-auto definitions distribute to equi-size min sizes, without exceeding preferred size. + // + // in order to achieve that, definitions are sorted in a way that all auto definitions + // are first, then definitions follow ascending order with PreferredSize as the key of sorting. + // + double sizeToDistribute; + int i; + + Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer); + for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) + { + // sanity check: only auto definitions allowed in this loop + Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + + // adjust sizeToDistribute value by subtracting auto definition min size + sizeToDistribute -= (tempDefinitions[i].MinSize); + } + + for (; i < count; ++i) + { + // sanity check: no auto definitions allowed in this loop + Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + + double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize); + if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); } + sizeToDistribute -= newMinSize; + } + + // sanity check: requested size must all be distributed + Debug.Assert(_IsZero(sizeToDistribute)); + } + else if (requestedSize <= rangeMaxSize) + { + // + // requestedSize bigger than preferred size, but fit into max size of the range. + // distribute according to the following logic: + // * do not distribute into auto definitions, if possible - they should continue to stay "tight"; + // * for all non-auto definitions distribute to euqi-size min sizes, without exceeding max size. + // + // in order to achieve that, definitions are sorted in a way that all non-auto definitions + // are last, then definitions follow ascending order with MaxSize as the key of sorting. + // + double sizeToDistribute; + int i; + + Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer); + for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) + { + // sanity check: no auto definitions allowed in this loop + Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + + double preferredSize = tempDefinitions[i].PreferredSize; + double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i); + tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); + sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + } + + for (; i < count; ++i) + { + // sanity check: only auto definitions allowed in this loop + Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + + double preferredSize = tempDefinitions[i].MinSize; + double newMinSize = preferredSize + sizeToDistribute / (count - i); + tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); + sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + } + + // sanity check: requested size must all be distributed + Debug.Assert(_IsZero(sizeToDistribute)); + } + else + { + // + // requestedSize bigger than max size of the range. + // distribute according to the following logic: + // * for all definitions distribute to equi-size min sizes. + // + double equalSize = requestedSize / count; + + if ( equalSize < maxMaxSize + && !_AreClose(equalSize, maxMaxSize) ) + { + // equi-size is less than maximum of maxSizes. + // in this case distribute so that smaller definitions grow faster than + // bigger ones. + double totalRemainingSize = maxMaxSize * count - rangeMaxSize; + double sizeToDistribute = requestedSize - rangeMaxSize; + + // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers + Debug.Assert( !double.IsInfinity(totalRemainingSize) + && !DoubleUtil.IsNaN(totalRemainingSize) + && totalRemainingSize > 0 + && !double.IsInfinity(sizeToDistribute) + && !DoubleUtil.IsNaN(sizeToDistribute) + && sizeToDistribute > 0 ); + + for (int i = 0; i < count; ++i) + { + double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize; + tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize); + } + } + else + { + // + // equi-size is greater or equal to maximum of max sizes. + // all definitions receive equalSize as their mim sizes. + // + for (int i = 0; i < count; ++i) + { + tempDefinitions[i].UpdateMinSize(equalSize); + } + } + } + } + } + } + + /// + /// Resolves Star's for given array of definitions. + /// + /// Array of definitions to resolve stars. + /// All available size. + /// + /// Must initialize LayoutSize for all Star entries in given array of definitions. + /// + private void ResolveStar( + DefinitionBase[] definitions, + double availableSize) + { + if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) + { + ResolveStarLegacy(definitions, availableSize); + } + else + { + ResolveStarMaxDiscrepancy(definitions, availableSize); + } + } + + // original implementation, used from 3.0 through 4.6.2 + private void ResolveStarLegacy( + DefinitionBase[] definitions, + double availableSize) + { + DefinitionBase[] tempDefinitions = TempDefinitions; + int starDefinitionsCount = 0; + double takenSize = 0; + + for (int i = 0; i < definitions.Length; ++i) + { + switch (definitions[i].SizeType) + { + case (LayoutTimeSizeType.Auto): + takenSize += definitions[i].MinSize; + break; + case (LayoutTimeSizeType.Pixel): + takenSize += definitions[i].MeasureSize; + break; + case (LayoutTimeSizeType.Star): + { + tempDefinitions[starDefinitionsCount++] = definitions[i]; + + double starValue = definitions[i].UserSize.Value; + + if (_IsZero(starValue)) + { + definitions[i].MeasureSize = 0; + definitions[i].SizeCache = 0; + } + else + { + // clipping by c_starClip guarantees that sum of even a very big number of max'ed out star values + // can be summed up without overflow + starValue = Math.Min(starValue, c_starClip); + + // Note: normalized star value is temporary cached into MeasureSize + definitions[i].MeasureSize = starValue; + double maxSize = Math.Max(definitions[i].MinSize, definitions[i].UserMaxSize); + maxSize = Math.Min(maxSize, c_starClip); + definitions[i].SizeCache = maxSize / starValue; + } + } + break; + } + } + + if (starDefinitionsCount > 0) + { + Array.Sort(tempDefinitions, 0, starDefinitionsCount, s_starDistributionOrderComparer); + + // the 'do {} while' loop below calculates sum of star weights in order to avoid fp overflow... + // partial sum value is stored in each definition's SizeCache member. + // this way the algorithm guarantees (starValue <= definition.SizeCache) and thus + // (starValue / definition.SizeCache) will never overflow due to sum of star weights becoming zero. + // this is an important change from previous implementation where the following was possible: + // ((BigValueStar + SmallValueStar) - BigValueStar) resulting in 0... + double allStarWeights = 0; + int i = starDefinitionsCount - 1; + do + { + allStarWeights += tempDefinitions[i].MeasureSize; + tempDefinitions[i].SizeCache = allStarWeights; + } while (--i >= 0); + + i = 0; + do + { + double resolvedSize; + double starValue = tempDefinitions[i].MeasureSize; + + if (_IsZero(starValue)) + { + resolvedSize = tempDefinitions[i].MinSize; + } + else + { + double userSize = Math.Max(availableSize - takenSize, 0.0) * (starValue / tempDefinitions[i].SizeCache); + resolvedSize = Math.Min(userSize, tempDefinitions[i].UserMaxSize); + resolvedSize = Math.Max(tempDefinitions[i].MinSize, resolvedSize); + } + + tempDefinitions[i].MeasureSize = resolvedSize; + takenSize += resolvedSize; + } while (++i < starDefinitionsCount); + } + } + + // new implementation as of 4.7. Several improvements: + // 1. Allocate to *-defs hitting their min or max constraints, before allocating + // to other *-defs. A def that hits its min uses more space than its + // proportional share, reducing the space available to everyone else. + // The legacy algorithm deducted this space only from defs processed + // after the min; the new algorithm deducts it proportionally from all + // defs. This avoids the "*-defs exceed available space" problem, + // and other related problems where *-defs don't receive proportional + // allocations even though no constraints are preventing it. + // 2. When multiple defs hit min or max, resolve the one with maximum + // discrepancy (defined below). This avoids discontinuities - small + // change in available space resulting in large change to one def's allocation. + // 3. Correct handling of large *-values, including Infinity. + private void ResolveStarMaxDiscrepancy( + DefinitionBase[] definitions, + double availableSize) + { + int defCount = definitions.Length; + DefinitionBase[] tempDefinitions = TempDefinitions; + int minCount = 0, maxCount = 0; + double takenSize = 0; + double totalStarWeight = 0.0; + int starCount = 0; // number of unresolved *-definitions + double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" + + // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights + double maxStar = 0.0; + for (int i=0; i maxStar) + { + maxStar = def.UserSize.Value; + } + } + } + + if (Double.IsPositiveInfinity(maxStar)) + { + // negative scale means one or more of the weights was Infinity + scale = -1.0; + } + else if (starCount > 0) + { + // if maxStar * starCount > Double.Max, summing all the weights could cause + // floating-point overflow. To avoid that, scale the weights by a factor to keep + // the sum within limits. Choose a power of 2, to preserve precision. + double power = Math.Floor(Math.Log(Double.MaxValue / maxStar / starCount, 2.0)); + if (power < 0.0) + { + scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia + } + } + + // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights + // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. + // More explanation below... + for (bool runPhase2and3=true; runPhase2and3; ) + { + // Phase 2. Compute total *-weight W and available space S. + // For *-items that have Min or Max constraints, compute the ratios used to decide + // whether proportional space is too big or too small and add the item to the + // corresponding list. (The "min" list is in the first half of tempDefinitions, + // the "max" list in the second half. TempDefinitions has capacity at least + // 2*defCount, so there's room for both lists.) + totalStarWeight = 0.0; + takenSize = 0.0; + minCount = maxCount = 0; + + for (int i=0; i 0.0) + { + // store ratio w/min in MeasureSize (for now) + tempDefinitions[minCount++] = def; + def.MeasureSize = starWeight / def.MinSize; + } + + double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); + if (!Double.IsPositiveInfinity(effectiveMaxSize)) + { + // store ratio w/max in SizeCache (for now) + tempDefinitions[defCount + maxCount++] = def; + def.SizeCache = starWeight / effectiveMaxSize; + } + } + break; + } + } + + // Phase 3. Resolve *-items whose proportional sizes are too big or too small. + int minCountPhase2 = minCount, maxCountPhase2 = maxCount; + double takenStarWeight = 0.0; + double remainingAvailableSize = availableSize - takenSize; + double remainingStarWeight = totalStarWeight - takenStarWeight; + Array.Sort(tempDefinitions, 0, minCount, s_minRatioComparer); + Array.Sort(tempDefinitions, defCount, maxCount, s_maxRatioComparer); + + while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) + { + // the calculation + // remainingStarWeight = totalStarWeight - takenStarWeight + // is subject to catastrophic cancellation if the two terms are nearly equal, + // which leads to meaningless results. Check for that, and recompute from + // the remaining definitions. [This leads to quadratic behavior in really + // pathological cases - but they'd never arise in practice.] + const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate + if (remainingStarWeight < totalStarWeight * starFactor) + { + takenStarWeight = 0.0; + totalStarWeight = 0.0; + + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + if (def.SizeType == LayoutTimeSizeType.Star && def.MeasureSize > 0.0) + { + totalStarWeight += StarWeight(def, scale); + } + } + + remainingStarWeight = totalStarWeight - takenStarWeight; + } + + double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity; + double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; + + // choose the def with larger ratio to the current proportion ("max discrepancy") + double proportion = remainingStarWeight / remainingAvailableSize; + bool? chooseMin = Choose(minRatio, maxRatio, proportion); + + // if no def was chosen, advance to phase 4; the current proportion doesn't + // conflict with any min or max values. + if (!(chooseMin.HasValue)) + { + break; + } + + // get the chosen definition and its resolved size + DefinitionBase resolvedDef; + double resolvedSize; + if (chooseMin == true) + { + resolvedDef = tempDefinitions[minCount - 1]; + resolvedSize = resolvedDef.MinSize; + --minCount; + } + else + { + resolvedDef = tempDefinitions[defCount + maxCount - 1]; + resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); + --maxCount; + } + + // resolve the chosen def, deduct its contributions from W and S. + // Defs resolved in phase 3 are marked by storing the negative of their resolved + // size in MeasureSize, to distinguish them from a pending def. + takenSize += resolvedSize; + resolvedDef.MeasureSize = -resolvedSize; + takenStarWeight += StarWeight(resolvedDef, scale); + --starCount; + + remainingAvailableSize = availableSize - takenSize; + remainingStarWeight = totalStarWeight - takenStarWeight; + + // advance to the next candidate defs, removing ones that have been resolved. + // Both counts are advanced, as a def might appear in both lists. + while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0) + { + --minCount; + tempDefinitions[minCount] = null; + } + while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0) + { + --maxCount; + tempDefinitions[defCount + maxCount] = null; + } + } + + // decide whether to run Phase2 and Phase3 again. There are 3 cases: + // 1. There is space available, and *-defs remaining. This is the + // normal case - move on to Phase 4 to allocate the remaining + // space proportionally to the remaining *-defs. + // 2. There is space available, but no *-defs. This implies at least one + // def was resolved as 'max', taking less space than its proportion. + // If there are also 'min' defs, reconsider them - we can give + // them more space. If not, all the *-defs are 'max', so there's + // no way to use all the available space. + // 3. We allocated too much space. This implies at least one def was + // resolved as 'min'. If there are also 'max' defs, reconsider + // them, otherwise the over-allocation is an inevitable consequence + // of the given min constraints. + // Note that if we return to Phase2, at least one *-def will have been + // resolved. This guarantees we don't run Phase2+3 infinitely often. + runPhase2and3 = false; + if (starCount == 0 && takenSize < availableSize) + { + // if no *-defs remain and we haven't allocated all the space, reconsider the defs + // resolved as 'min'. Their allocation can be increased to make up the gap. + for (int i = minCount; i < minCountPhase2; ++i) + { + DefinitionBase def = tempDefinitions[i]; + if (def != null) + { + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + + if (takenSize > availableSize) + { + // if we've allocated too much space, reconsider the defs + // resolved as 'max'. Their allocation can be decreased to make up the gap. + for (int i = maxCount; i < maxCountPhase2; ++i) + { + DefinitionBase def = tempDefinitions[defCount + i]; + if (def != null) + { + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + } + + // Phase 4. Resolve the remaining defs proportionally. + starCount = 0; + for (int i=0; i 0) + { + Array.Sort(tempDefinitions, 0, starCount, s_starWeightComparer); + + // compute the partial sums of *-weight, in increasing order of weight + // for minimal loss of precision. + totalStarWeight = 0.0; + for (int i = 0; i < starCount; ++i) + { + DefinitionBase def = tempDefinitions[i]; + totalStarWeight += def.MeasureSize; + def.SizeCache = totalStarWeight; + } + + // resolve the defs, in decreasing order of weight + for (int i = starCount - 1; i >= 0; --i) + { + DefinitionBase def = tempDefinitions[i]; + double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; + + // min and max should have no effect by now, but just in case... + resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); + resolvedSize = Math.Max(def.MinSize, resolvedSize); + + def.MeasureSize = resolvedSize; + takenSize += resolvedSize; + } + } + } + + /// + /// Calculates desired size for given array of definitions. + /// + /// Array of definitions to use for calculations. + /// Desired size. + private double CalculateDesiredSize( + DefinitionBase[] definitions) + { + double desiredSize = 0; + + for (int i = 0; i < definitions.Length; ++i) + { + desiredSize += definitions[i].MinSize; + } + + return (desiredSize); + } + + /// + /// Calculates and sets final size for all definitions in the given array. + /// + /// Array of definitions to process. + /// Final size to lay out to. + /// True if sizing row definitions, false for columns + private void SetFinalSize( + DefinitionBase[] definitions, + double finalSize, + bool columns) + { + if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) + { + SetFinalSizeLegacy(definitions, finalSize, columns); + } + else + { + SetFinalSizeMaxDiscrepancy(definitions, finalSize, columns); + } + } + + // original implementation, used from 3.0 through 4.6.2 + private void SetFinalSizeLegacy( + DefinitionBase[] definitions, + double finalSize, + bool columns) + { + int starDefinitionsCount = 0; // traverses form the first entry up + int nonStarIndex = definitions.Length; // traverses from the last entry down + double allPreferredArrangeSize = 0; + bool useLayoutRounding = this.UseLayoutRounding; + int[] definitionIndices = DefinitionIndices; + double[] roundingErrors = null; + + // If using layout rounding, check whether rounding needs to compensate for high DPI + double dpi = 1.0; + + if (useLayoutRounding) + { + DpiScale dpiScale = GetDpi(); + dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; + roundingErrors = RoundingErrors; + } + + for (int i = 0; i < definitions.Length; ++i) + { + // if definition is shared then is cannot be star + Debug.Assert(!definitions[i].IsShared || !definitions[i].UserSize.IsStar); + + if (definitions[i].UserSize.IsStar) + { + double starValue = definitions[i].UserSize.Value; + + if (_IsZero(starValue)) + { + // cach normilized star value temporary into MeasureSize + definitions[i].MeasureSize = 0; + definitions[i].SizeCache = 0; + } + else + { + // clipping by c_starClip guarantees that sum of even a very big number of max'ed out star values + // can be summed up without overflow + starValue = Math.Min(starValue, c_starClip); + + // Note: normalized star value is temporary cached into MeasureSize + definitions[i].MeasureSize = starValue; + double maxSize = Math.Max(definitions[i].MinSizeForArrange, definitions[i].UserMaxSize); + maxSize = Math.Min(maxSize, c_starClip); + definitions[i].SizeCache = maxSize / starValue; + if (useLayoutRounding) + { + roundingErrors[i] = definitions[i].SizeCache; + definitions[i].SizeCache = UIElement.RoundLayoutValue(definitions[i].SizeCache, dpi); + } + } + definitionIndices[starDefinitionsCount++] = i; + } + else + { + double userSize = 0; + + switch (definitions[i].UserSize.GridUnitType) + { + case (GridUnitType.Pixel): + userSize = definitions[i].UserSize.Value; + break; + + case (GridUnitType.Auto): + userSize = definitions[i].MinSizeForArrange; + break; + } + + double userMaxSize; + + if (definitions[i].IsShared) + { + // overriding userMaxSize effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + userMaxSize = userSize; + } + else + { + userMaxSize = definitions[i].UserMaxSize; + } + + definitions[i].SizeCache = Math.Max(definitions[i].MinSizeForArrange, Math.Min(userSize, userMaxSize)); + if (useLayoutRounding) + { + roundingErrors[i] = definitions[i].SizeCache; + definitions[i].SizeCache = UIElement.RoundLayoutValue(definitions[i].SizeCache, dpi); + } + + allPreferredArrangeSize += definitions[i].SizeCache; + definitionIndices[--nonStarIndex] = i; + } + } + + // indices should meet + Debug.Assert(nonStarIndex == starDefinitionsCount); + + if (starDefinitionsCount > 0) + { + StarDistributionOrderIndexComparer starDistributionOrderIndexComparer = new StarDistributionOrderIndexComparer(definitions); + Array.Sort(definitionIndices, 0, starDefinitionsCount, starDistributionOrderIndexComparer); + + // the 'do {} while' loop below calculates sum of star weights in order to avoid fp overflow... + // partial sum value is stored in each definition's SizeCache member. + // this way the algorithm guarantees (starValue <= definition.SizeCache) and thus + // (starValue / definition.SizeCache) will never overflow due to sum of star weights becoming zero. + // this is an important change from previous implementation where the following was possible: + // ((BigValueStar + SmallValueStar) - BigValueStar) resulting in 0... + double allStarWeights = 0; + int i = starDefinitionsCount - 1; + do + { + allStarWeights += definitions[definitionIndices[i]].MeasureSize; + definitions[definitionIndices[i]].SizeCache = allStarWeights; + } while (--i >= 0); + + i = 0; + do + { + double resolvedSize; + double starValue = definitions[definitionIndices[i]].MeasureSize; + + if (_IsZero(starValue)) + { + resolvedSize = definitions[definitionIndices[i]].MinSizeForArrange; + } + else + { + double userSize = Math.Max(finalSize - allPreferredArrangeSize, 0.0) * (starValue / definitions[definitionIndices[i]].SizeCache); + resolvedSize = Math.Min(userSize, definitions[definitionIndices[i]].UserMaxSize); + resolvedSize = Math.Max(definitions[definitionIndices[i]].MinSizeForArrange, resolvedSize); + } + + definitions[definitionIndices[i]].SizeCache = resolvedSize; + if (useLayoutRounding) + { + roundingErrors[definitionIndices[i]] = definitions[definitionIndices[i]].SizeCache; + definitions[definitionIndices[i]].SizeCache = UIElement.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi); + } + + allPreferredArrangeSize += definitions[definitionIndices[i]].SizeCache; + } while (++i < starDefinitionsCount); + } + + if ( allPreferredArrangeSize > finalSize + && !_AreClose(allPreferredArrangeSize, finalSize) ) + { + DistributionOrderIndexComparer distributionOrderIndexComparer = new DistributionOrderIndexComparer(definitions); + Array.Sort(definitionIndices, 0, definitions.Length, distributionOrderIndexComparer); + double sizeToDistribute = finalSize - allPreferredArrangeSize; + + for (int i = 0; i < definitions.Length; ++i) + { + int definitionIndex = definitionIndices[i]; + double final = definitions[definitionIndex].SizeCache + (sizeToDistribute / (definitions.Length - i)); + double finalOld = final; + final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); + final = Math.Min(final, definitions[definitionIndex].SizeCache); + + if (useLayoutRounding) + { + roundingErrors[definitionIndex] = final; + final = UIElement.RoundLayoutValue(finalOld, dpi); + final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); + final = Math.Min(final, definitions[definitionIndex].SizeCache); + } + + sizeToDistribute -= (final - definitions[definitionIndex].SizeCache); + definitions[definitionIndex].SizeCache = final; + } + + allPreferredArrangeSize = finalSize - sizeToDistribute; + } + + if (useLayoutRounding) + { + if (!_AreClose(allPreferredArrangeSize, finalSize)) + { + // Compute deltas + for (int i = 0; i < definitions.Length; ++i) + { + roundingErrors[i] = roundingErrors[i] - definitions[i].SizeCache; + definitionIndices[i] = i; + } + + // Sort rounding errors + RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); + Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); + double adjustedSize = allPreferredArrangeSize; + double dpiIncrement = UIElement.RoundLayoutValue(1.0, dpi); + + if (allPreferredArrangeSize > finalSize) + { + int i = definitions.Length - 1; + while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache - dpiIncrement; + final = Math.Max(final, definition.MinSizeForArrange); + if (final < definition.SizeCache) + { + adjustedSize -= dpiIncrement; + } + definition.SizeCache = final; + i--; + } + } + else if (allPreferredArrangeSize < finalSize) + { + int i = 0; + while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache + dpiIncrement; + final = Math.Max(final, definition.MinSizeForArrange); + if (final > definition.SizeCache) + { + adjustedSize += dpiIncrement; + } + definition.SizeCache = final; + i++; + } + } + } + } + + definitions[0].FinalOffset = 0.0; + for (int i = 0; i < definitions.Length; ++i) + { + definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; + } + } + + // new implementation, as of 4.7. This incorporates the same algorithm + // as in ResolveStarMaxDiscrepancy. It differs in the same way that SetFinalSizeLegacy + // differs from ResolveStarLegacy, namely (a) leaves results in def.SizeCache + // instead of def.MeasureSize, (b) implements LayoutRounding if requested, + // (c) stores intermediate results differently. + // The LayoutRounding logic is improved: + // 1. Use pre-rounded values during proportional allocation. This avoids the + // same kind of problems arising from interaction with min/max that + // motivated the new algorithm in the first place. + // 2. Use correct "nudge" amount when distributing roundoff space. This + // comes into play at high DPI - greater than 134. + // 3. Applies rounding only to real pixel values (not to ratios) + private void SetFinalSizeMaxDiscrepancy( + DefinitionBase[] definitions, + double finalSize, + bool columns) + { + int defCount = definitions.Length; + int[] definitionIndices = DefinitionIndices; + int minCount = 0, maxCount = 0; + double takenSize = 0.0; + double totalStarWeight = 0.0; + int starCount = 0; // number of unresolved *-definitions + double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" + + // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights + double maxStar = 0.0; + for (int i=0; i maxStar) + { + maxStar = def.UserSize.Value; + } + } + } + + if (Double.IsPositiveInfinity(maxStar)) + { + // negative scale means one or more of the weights was Infinity + scale = -1.0; + } + else if (starCount > 0) + { + // if maxStar * starCount > Double.Max, summing all the weights could cause + // floating-point overflow. To avoid that, scale the weights by a factor to keep + // the sum within limits. Choose a power of 2, to preserve precision. + double power = Math.Floor(Math.Log(Double.MaxValue / maxStar / starCount, 2.0)); + if (power < 0.0) + { + scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia + } + } + + + // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights + // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. + // More explanation below... + for (bool runPhase2and3=true; runPhase2and3; ) + { + // Phase 2. Compute total *-weight W and available space S. + // For *-items that have Min or Max constraints, compute the ratios used to decide + // whether proportional space is too big or too small and add the item to the + // corresponding list. (The "min" list is in the first half of definitionIndices, + // the "max" list in the second half. DefinitionIndices has capacity at least + // 2*defCount, so there's room for both lists.) + totalStarWeight = 0.0; + takenSize = 0.0; + minCount = maxCount = 0; + + for (int i=0; i 0.0) + { + // store ratio w/min in MeasureSize (for now) + definitionIndices[minCount++] = i; + def.MeasureSize = starWeight / def.MinSizeForArrange; + } + + double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize); + if (!Double.IsPositiveInfinity(effectiveMaxSize)) + { + // store ratio w/max in SizeCache (for now) + definitionIndices[defCount + maxCount++] = i; + def.SizeCache = starWeight / effectiveMaxSize; + } + } + } + else + { + double userSize = 0; + + switch (def.UserSize.GridUnitType) + { + case (GridUnitType.Pixel): + userSize = def.UserSize.Value; + break; + + case (GridUnitType.Auto): + userSize = def.MinSizeForArrange; + break; + } + + double userMaxSize; + + if (def.IsShared) + { + // overriding userMaxSize effectively prevents squishy-ness. + // this is a "solution" to avoid shared definitions from been sized to + // different final size at arrange time, if / when different grids receive + // different final sizes. + userMaxSize = userSize; + } + else + { + userMaxSize = def.UserMaxSize; + } + + def.SizeCache = Math.Max(def.MinSizeForArrange, Math.Min(userSize, userMaxSize)); + takenSize += def.SizeCache; + } + } + + // Phase 3. Resolve *-items whose proportional sizes are too big or too small. + int minCountPhase2 = minCount, maxCountPhase2 = maxCount; + double takenStarWeight = 0.0; + double remainingAvailableSize = finalSize - takenSize; + double remainingStarWeight = totalStarWeight - takenStarWeight; + + MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions); + Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer); + MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions); + Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer); + + while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) + { + // the calculation + // remainingStarWeight = totalStarWeight - takenStarWeight + // is subject to catastrophic cancellation if the two terms are nearly equal, + // which leads to meaningless results. Check for that, and recompute from + // the remaining definitions. [This leads to quadratic behavior in really + // pathological cases - but they'd never arise in practice.] + const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate + if (remainingStarWeight < totalStarWeight * starFactor) + { + takenStarWeight = 0.0; + totalStarWeight = 0.0; + + for (int i = 0; i < defCount; ++i) + { + DefinitionBase def = definitions[i]; + if (def.UserSize.IsStar && def.MeasureSize > 0.0) + { + totalStarWeight += StarWeight(def, scale); + } + } + + remainingStarWeight = totalStarWeight - takenStarWeight; + } + + double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : Double.PositiveInfinity; + double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0; + + // choose the def with larger ratio to the current proportion ("max discrepancy") + double proportion = remainingStarWeight / remainingAvailableSize; + bool? chooseMin = Choose(minRatio, maxRatio, proportion); + + // if no def was chosen, advance to phase 4; the current proportion doesn't + // conflict with any min or max values. + if (!chooseMin.HasValue) + { + break; + } + + // get the chosen definition and its resolved size + int resolvedIndex; + DefinitionBase resolvedDef; + double resolvedSize; + if (chooseMin == true) + { + resolvedIndex = definitionIndices[minCount - 1]; + resolvedDef = definitions[resolvedIndex]; + resolvedSize = resolvedDef.MinSizeForArrange; + --minCount; + } + else + { + resolvedIndex = definitionIndices[defCount + maxCount - 1]; + resolvedDef = definitions[resolvedIndex]; + resolvedSize = Math.Max(resolvedDef.MinSizeForArrange, resolvedDef.UserMaxSize); + --maxCount; + } + + // resolve the chosen def, deduct its contributions from W and S. + // Defs resolved in phase 3 are marked by storing the negative of their resolved + // size in MeasureSize, to distinguish them from a pending def. + takenSize += resolvedSize; + resolvedDef.MeasureSize = -resolvedSize; + takenStarWeight += StarWeight(resolvedDef, scale); + --starCount; + + remainingAvailableSize = finalSize - takenSize; + remainingStarWeight = totalStarWeight - takenStarWeight; + + // advance to the next candidate defs, removing ones that have been resolved. + // Both counts are advanced, as a def might appear in both lists. + while (minCount > 0 && definitions[definitionIndices[minCount - 1]].MeasureSize < 0.0) + { + --minCount; + definitionIndices[minCount] = -1; + } + while (maxCount > 0 && definitions[definitionIndices[defCount + maxCount - 1]].MeasureSize < 0.0) + { + --maxCount; + definitionIndices[defCount + maxCount] = -1; + } + } + + // decide whether to run Phase2 and Phase3 again. There are 3 cases: + // 1. There is space available, and *-defs remaining. This is the + // normal case - move on to Phase 4 to allocate the remaining + // space proportionally to the remaining *-defs. + // 2. There is space available, but no *-defs. This implies at least one + // def was resolved as 'max', taking less space than its proportion. + // If there are also 'min' defs, reconsider them - we can give + // them more space. If not, all the *-defs are 'max', so there's + // no way to use all the available space. + // 3. We allocated too much space. This implies at least one def was + // resolved as 'min'. If there are also 'max' defs, reconsider + // them, otherwise the over-allocation is an inevitable consequence + // of the given min constraints. + // Note that if we return to Phase2, at least one *-def will have been + // resolved. This guarantees we don't run Phase2+3 infinitely often. + runPhase2and3 = false; + if (starCount == 0 && takenSize < finalSize) + { + // if no *-defs remain and we haven't allocated all the space, reconsider the defs + // resolved as 'min'. Their allocation can be increased to make up the gap. + for (int i = minCount; i < minCountPhase2; ++i) + { + if (definitionIndices[i] >= 0) + { + DefinitionBase def = definitions[definitionIndices[i]]; + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + + if (takenSize > finalSize) + { + // if we've allocated too much space, reconsider the defs + // resolved as 'max'. Their allocation can be decreased to make up the gap. + for (int i = maxCount; i < maxCountPhase2; ++i) + { + if (definitionIndices[defCount + i] >= 0) + { + DefinitionBase def = definitions[definitionIndices[defCount + i]]; + def.MeasureSize = 1.0; // mark as 'not yet resolved' + ++starCount; + runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 + } + } + } + } + + // Phase 4. Resolve the remaining defs proportionally. + starCount = 0; + for (int i=0; i 0) + { + StarWeightIndexComparer starWeightIndexComparer = new StarWeightIndexComparer(definitions); + Array.Sort(definitionIndices, 0, starCount, starWeightIndexComparer); + + // compute the partial sums of *-weight, in increasing order of weight + // for minimal loss of precision. + totalStarWeight = 0.0; + for (int i = 0; i < starCount; ++i) + { + DefinitionBase def = definitions[definitionIndices[i]]; + totalStarWeight += def.MeasureSize; + def.SizeCache = totalStarWeight; + } + + // resolve the defs, in decreasing order of weight. + for (int i = starCount - 1; i >= 0; --i) + { + DefinitionBase def = definitions[definitionIndices[i]]; + double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(finalSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; + + // min and max should have no effect by now, but just in case... + resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); + resolvedSize = Math.Max(def.MinSizeForArrange, resolvedSize); + + // Use the raw (unrounded) sizes to update takenSize, so that + // proportions are computed in the same terms as in phase 3; + // this avoids errors arising from min/max constraints. + takenSize += resolvedSize; + def.SizeCache = resolvedSize; + } + } + + // Phase 5. Apply layout rounding. We do this after fully allocating + // unrounded sizes, to avoid breaking assumptions in the previous phases + if (UseLayoutRounding) + { + DpiScale dpiScale = GetDpi(); + double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; + double[] roundingErrors = RoundingErrors; + double roundedTakenSize = 0.0; + + // round each of the allocated sizes, keeping track of the deltas + for (int i = 0; i < definitions.Length; ++i) + { + DefinitionBase def = definitions[i]; + double roundedSize = UIElement.RoundLayoutValue(def.SizeCache, dpi); + roundingErrors[i] = (roundedSize - def.SizeCache); + def.SizeCache = roundedSize; + roundedTakenSize += roundedSize; + } + + // The total allocation might differ from finalSize due to rounding + // effects. Tweak the allocations accordingly. + + // Theoretical and historical note. The problem at hand - allocating + // space to columns (or rows) with *-weights, min and max constraints, + // and layout rounding - has a long history. Especially the special + // case of 50 columns with min=1 and available space=435 - allocating + // seats in the U.S. House of Representatives to the 50 states in + // proportion to their population. There are numerous algorithms + // and papers dating back to the 1700's, including the book: + // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982. + // + // One surprising result of all this research is that *any* algorithm + // will suffer from one or more undesirable features such as the + // "population paradox" or the "Alabama paradox", where (to use our terminology) + // increasing the available space by one pixel might actually decrease + // the space allocated to a given column, or increasing the weight of + // a column might decrease its allocation. This is worth knowing + // in case someone complains about this behavior; it's not a bug so + // much as something inherent to the problem. Cite the book mentioned + // above or one of the 100s of references, and resolve as WontFix. + // + // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer) + // each being allocated a large number of pixels (~50 or greater), and + // people don't even notice the kind of 1-pixel anomolies that are + // theoretically inevitable, or don't care if they do. At least they shouldn't + // care - no one should be using the results WPF's grid layout to make + // quantitative decisions; its job is to produce a reasonable display, not + // to allocate seats in Congress. + // + // Our algorithm is more susceptible to paradox than the one currently + // used for Congressional allocation ("Huntington-Hill" algorithm), but + // it is faster to run: O(N log N) vs. O(S * N), where N=number of + // definitions, S = number of available pixels. And it produces + // adequate results in practice, as mentioned above. + // + // To reiterate one point: all this only applies when layout rounding + // is in effect. When fractional sizes are allowed, the algorithm + // behaves as well as possible, subject to the min/max constraints + // and precision of floating-point computation. (However, the resulting + // display is subject to anti-aliasing problems. TANSTAAFL.) + + if (!_AreClose(roundedTakenSize, finalSize)) + { + // Compute deltas + for (int i = 0; i < definitions.Length; ++i) + { + definitionIndices[i] = i; + } + + // Sort rounding errors + RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); + Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); + double adjustedSize = roundedTakenSize; + double dpiIncrement = 1.0/dpi; + + if (roundedTakenSize > finalSize) + { + int i = definitions.Length - 1; + while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache - dpiIncrement; + final = Math.Max(final, definition.MinSizeForArrange); + if (final < definition.SizeCache) + { + adjustedSize -= dpiIncrement; + } + definition.SizeCache = final; + i--; + } + } + else if (roundedTakenSize < finalSize) + { + int i = 0; + while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length) + { + DefinitionBase definition = definitions[definitionIndices[i]]; + double final = definition.SizeCache + dpiIncrement; + final = Math.Max(final, definition.MinSizeForArrange); + if (final > definition.SizeCache) + { + adjustedSize += dpiIncrement; + } + definition.SizeCache = final; + i++; + } + } + } + } + + // Phase 6. Compute final offsets + definitions[0].FinalOffset = 0.0; + for (int i = 0; i < definitions.Length; ++i) + { + definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; + } + } + + /// + /// Choose the ratio with maximum discrepancy from the current proportion. + /// Returns: + /// true if proportion fails a min constraint but not a max, or + /// if the min constraint has higher discrepancy + /// false if proportion fails a max constraint but not a min, or + /// if the max constraint has higher discrepancy + /// null if proportion doesn't fail a min or max constraint + /// The discrepancy is the ratio of the proportion to the max- or min-ratio. + /// When both ratios hit the constraint, minRatio < proportion < maxRatio, + /// and the minRatio has higher discrepancy if + /// (proportion / minRatio) > (maxRatio / proportion) + /// + private static bool? Choose(double minRatio, double maxRatio, double proportion) + { + if (minRatio < proportion) + { + if (maxRatio > proportion) + { + // compare proportion/minRatio : maxRatio/proportion, but + // do it carefully to avoid floating-point overflow or underflow + // and divide-by-0. + double minPower = Math.Floor(Math.Log(minRatio, 2.0)); + double maxPower = Math.Floor(Math.Log(maxRatio, 2.0)); + double f = Math.Pow(2.0, Math.Floor((minPower + maxPower) / 2.0)); + if ((proportion / f) * (proportion / f) > (minRatio / f) * (maxRatio / f)) + { + return true; + } + else + { + return false; + } + } + else + { + return true; + } + } + else if (maxRatio > proportion) + { + return false; + } + + return null; + } + + /// + /// Sorts row/column indices by rounding error if layout rounding is applied. + /// + /// Index, rounding error pair + /// Index, rounding error pair + /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise + private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y) + { + if (x.Value < y.Value) + { + return -1; + } + else if (x.Value > y.Value) + { + return 1; + } + return 0; + } + + /// + /// Calculates final (aka arrange) size for given range. + /// + /// Array of definitions to process. + /// Start of the range. + /// Number of items in the range. + /// Final size. + private double GetFinalSizeForRange( + DefinitionBase[] definitions, + int start, + int count) + { + double size = 0; + int i = start + count - 1; + + do + { + size += definitions[i].SizeCache; + } while (--i >= start); + + return (size); + } + + /// + /// Clears dirty state for the grid and its columns / rows + /// + private void SetValid() + { + ExtendedData extData = ExtData; + if (extData != null) + { +// for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid (); +// for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid (); + + if (extData.TempDefinitions != null) + { + // TempDefinitions has to be cleared to avoid "memory leaks" + Array.Clear(extData.TempDefinitions, 0, Math.Max(DefinitionsU.Length, DefinitionsV.Length)); + extData.TempDefinitions = null; + } + } + } + + /// + /// Returns true if ColumnDefinitions collection is not empty + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeColumnDefinitions() + { + ExtendedData extData = ExtData; + return ( extData != null + && extData.ColumnDefinitions != null + && extData.ColumnDefinitions.Count > 0 ); + } + + /// + /// Returns true if RowDefinitions collection is not empty + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ShouldSerializeRowDefinitions() + { + ExtendedData extData = ExtData; + return ( extData != null + && extData.RowDefinitions != null + && extData.RowDefinitions.Count > 0 ); + } + + /// + /// Synchronized ShowGridLines property with the state of the grid's visual collection + /// by adding / removing GridLinesRenderer visual. + /// Returns a reference to GridLinesRenderer visual or null. + /// + private GridLinesRenderer EnsureGridLinesRenderer() + { + // + // synchronize the state + // + if (ShowGridLines && (_gridLinesRenderer == null)) + { + _gridLinesRenderer = new GridLinesRenderer(); + this.AddVisualChild(_gridLinesRenderer); + } + + if ((!ShowGridLines) && (_gridLinesRenderer != null)) + { + this.RemoveVisualChild(_gridLinesRenderer); + _gridLinesRenderer = null; + } + + return (_gridLinesRenderer); + } + + /// + /// SetFlags is used to set or unset one or multiple + /// flags on the object. + /// + private void SetFlags(bool value, Flags flags) + { + _flags = value ? (_flags | flags) : (_flags & (~flags)); + } + + /// + /// CheckFlagsAnd returns true if all the flags in the + /// given bitmask are set on the object. + /// + private bool CheckFlagsAnd(Flags flags) + { + return ((_flags & flags) == flags); + } + + /// + /// CheckFlagsOr returns true if at least one flag in the + /// given bitmask is set. + /// + /// + /// If no bits are set in the given bitmask, the method returns + /// true. + /// + private bool CheckFlagsOr(Flags flags) + { + return (flags == 0 || (_flags & flags) != 0); + } + + /// + /// + /// + private static void OnShowGridLinesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Grid grid = (Grid)d; + + if ( grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway + && grid.ListenToNotifications) + { + grid.InvalidateVisual(); + } + + grid.SetFlags((bool) e.NewValue, Flags.ShowGridLinesPropertyValue); + } + + /// + /// + /// + private static void OnCellAttachedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Visual child = d as Visual; + + if (child != null) + { + Grid grid = VisualTreeHelper.GetParent(child) as Grid; + if ( grid != null + && grid.ExtData != null + && grid.ListenToNotifications ) + { + grid.CellsStructureDirty = true; + grid.InvalidateMeasure(); + } + } + } + + /// + /// + /// + private static bool IsIntValueNotNegative(object value) + { + return ((int)value >= 0); + } + + /// + /// + /// + private static bool IsIntValueGreaterThanZero(object value) + { + return ((int)value > 0); + } + + /// + /// Helper for Comparer methods. + /// + /// + /// true iff one or both of x and y are null, in which case result holds + /// the relative sort order. + /// + private static bool CompareNullRefs(object x, object y, out int result) + { + result = 2; + + if (x == null) + { + if (y == null) + { + result = 0; + } + else + { + result = -1; + } + } + else + { + if (y == null) + { + result = 1; + } + } + + return (result != 2); + } + + #endregion Private Methods + + //------------------------------------------------------ + // + // Private Properties + // + //------------------------------------------------------ + + #region Private Properties + + /// + /// Private version returning array of column definitions. + /// + private DefinitionBase[] DefinitionsU + { + get { return (ExtData.DefinitionsU); } + } + + /// + /// Private version returning array of row definitions. + /// + private DefinitionBase[] DefinitionsV + { + get { return (ExtData.DefinitionsV); } + } + + /// + /// Helper accessor to layout time array of definitions. + /// + private DefinitionBase[] TempDefinitions + { + get + { + ExtendedData extData = ExtData; + int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length) * 2; + + if ( extData.TempDefinitions == null + || extData.TempDefinitions.Length < requiredLength ) + { + WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(s_tempDefinitionsDataSlot); + if (tempDefinitionsWeakRef == null) + { + extData.TempDefinitions = new DefinitionBase[requiredLength]; + Thread.SetData(s_tempDefinitionsDataSlot, new WeakReference(extData.TempDefinitions)); + } + else + { + extData.TempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; + if ( extData.TempDefinitions == null + || extData.TempDefinitions.Length < requiredLength ) + { + extData.TempDefinitions = new DefinitionBase[requiredLength]; + tempDefinitionsWeakRef.Target = extData.TempDefinitions; + } + } + } + return (extData.TempDefinitions); + } + } + + /// + /// Helper accessor to definition indices. + /// + private int[] DefinitionIndices + { + get + { + int requiredLength = Math.Max(Math.Max(DefinitionsU.Length, DefinitionsV.Length), 1) * 2; + + if (_definitionIndices == null || _definitionIndices.Length < requiredLength) + { + _definitionIndices = new int[requiredLength]; + } + + return _definitionIndices; + } + } + + /// + /// Helper accessor to rounding errors. + /// + private double[] RoundingErrors + { + get + { + int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length); + + if (_roundingErrors == null && requiredLength == 0) + { + _roundingErrors = new double[1]; + } + else if (_roundingErrors == null || _roundingErrors.Length < requiredLength) + { + _roundingErrors = new double[requiredLength]; + } + return _roundingErrors; + } + } + + /// + /// Private version returning array of cells. + /// + private CellCache[] PrivateCells + { + get { return (ExtData.CellCachesCollection); } + } + + /// + /// Convenience accessor to ValidCellsStructure bit flag. + /// + private bool CellsStructureDirty + { + get { return (!CheckFlagsAnd(Flags.ValidCellsStructure)); } + set { SetFlags(!value, Flags.ValidCellsStructure); } + } + + /// + /// Convenience accessor to ListenToNotifications bit flag. + /// + private bool ListenToNotifications + { + get { return (CheckFlagsAnd(Flags.ListenToNotifications)); } + set { SetFlags(value, Flags.ListenToNotifications); } + } + + /// + /// Convenience accessor to SizeToContentU bit flag. + /// + private bool SizeToContentU + { + get { return (CheckFlagsAnd(Flags.SizeToContentU)); } + set { SetFlags(value, Flags.SizeToContentU); } + } + + /// + /// Convenience accessor to SizeToContentV bit flag. + /// + private bool SizeToContentV + { + get { return (CheckFlagsAnd(Flags.SizeToContentV)); } + set { SetFlags(value, Flags.SizeToContentV); } + } + + /// + /// Convenience accessor to HasStarCellsU bit flag. + /// + private bool HasStarCellsU + { + get { return (CheckFlagsAnd(Flags.HasStarCellsU)); } + set { SetFlags(value, Flags.HasStarCellsU); } + } + + /// + /// Convenience accessor to HasStarCellsV bit flag. + /// + private bool HasStarCellsV + { + get { return (CheckFlagsAnd(Flags.HasStarCellsV)); } + set { SetFlags(value, Flags.HasStarCellsV); } + } + + /// + /// Convenience accessor to HasGroup3CellsInAutoRows bit flag. + /// + private bool HasGroup3CellsInAutoRows + { + get { return (CheckFlagsAnd(Flags.HasGroup3CellsInAutoRows)); } + set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } + } + + /// + /// fp version of d == 0. + /// + /// Value to check. + /// true if d == 0. + private static bool _IsZero(double d) + { + return (Math.Abs(d) < c_epsilon); + } + + /// + /// fp version of d1 == d2 + /// + /// First value to compare + /// Second value to compare + /// true if d1 == d2 + private static bool _AreClose(double d1, double d2) + { + return (Math.Abs(d1 - d2) < c_epsilon); + } + + /// + /// Returns reference to extended data bag. + /// + private ExtendedData ExtData + { + get { return (_data); } + } + + /// + /// Returns *-weight, adjusted for scale computed during Phase 1 + /// + static double StarWeight(DefinitionBase def, double scale) + { + if (scale < 0.0) + { + // if one of the *-weights is Infinity, adjust the weights by mapping + // Infinty to 1.0 and everything else to 0.0: the infinite items share the + // available space equally, everyone else gets nothing. + return (Double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0; + } + else + { + return def.UserSize.Value * scale; + } + } + + #endregion Private Properties + + //------------------------------------------------------ + // + // Private Fields + // + //------------------------------------------------------ + + #region Private Fields + private ExtendedData _data; // extended data instantiated on demand, for non-trivial case handling only + private Flags _flags; // grid validity / property caches dirtiness flags + private GridLinesRenderer _gridLinesRenderer; + + // Keeps track of definition indices. + int[] _definitionIndices; + + // Stores unrounded values and rounding errors during layout rounding. + double[] _roundingErrors; + + #endregion Private Fields + + //------------------------------------------------------ + // + // Static Fields + // + //------------------------------------------------------ + + #region Static Fields + private const double c_epsilon = 1e-5; // used in fp calculations + private const double c_starClip = 1e298; // used as maximum for clipping star values during normalization + private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop + private static readonly LocalDataStoreSlot s_tempDefinitionsDataSlot = Thread.AllocateDataSlot(); + private static readonly IComparer s_spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); + private static readonly IComparer s_spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); + private static readonly IComparer s_starDistributionOrderComparer = new StarDistributionOrderComparer(); + private static readonly IComparer s_distributionOrderComparer = new DistributionOrderComparer(); + private static readonly IComparer s_minRatioComparer = new MinRatioComparer(); + private static readonly IComparer s_maxRatioComparer = new MaxRatioComparer(); + private static readonly IComparer s_starWeightComparer = new StarWeightComparer(); + + #endregion Static Fields + + //------------------------------------------------------ + // + // Private Structures / Classes + // + //------------------------------------------------------ + + #region Private Structures Classes + + /// + /// Extended data instantiated on demand, when grid handles non-trivial case. + /// + private class ExtendedData + { + internal ColumnDefinitionCollection ColumnDefinitions; // collection of column definitions (logical tree support) + internal RowDefinitionCollection RowDefinitions; // collection of row definitions (logical tree support) + internal DefinitionBase[] DefinitionsU; // collection of column definitions used during calc + internal DefinitionBase[] DefinitionsV; // collection of row definitions used during calc + internal CellCache[] CellCachesCollection; // backing store for logical children + internal int CellGroup1; // index of the first cell in first cell group + internal int CellGroup2; // index of the first cell in second cell group + internal int CellGroup3; // index of the first cell in third cell group + internal int CellGroup4; // index of the first cell in forth cell group + internal DefinitionBase[] TempDefinitions; // temporary array used during layout for various purposes + // TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length) + } + + /// + /// Grid validity / property caches dirtiness flags + /// + [System.Flags] + private enum Flags + { + // + // the foolowing flags let grid tracking dirtiness in more granular manner: + // * Valid???Structure flags indicate that elements were added or removed. + // * Valid???Layout flags indicate that layout time portion of the information + // stored on the objects should be updated. + // + ValidDefinitionsUStructure = 0x00000001, + ValidDefinitionsVStructure = 0x00000002, + ValidCellsStructure = 0x00000004, + + // + // boolean properties state + // + ShowGridLinesPropertyValue = 0x00000100, // show grid lines ? + + // + // boolean flags + // + ListenToNotifications = 0x00001000, // "0" when all notifications are ignored + SizeToContentU = 0x00002000, // "1" if calculating to content in U direction + SizeToContentV = 0x00004000, // "1" if calculating to content in V direction + HasStarCellsU = 0x00008000, // "1" if at least one cell belongs to a Star column + HasStarCellsV = 0x00010000, // "1" if at least one cell belongs to a Star row + HasGroup3CellsInAutoRows = 0x00020000, // "1" if at least one cell of group 3 belongs to an Auto row + MeasureOverrideInProgress = 0x00040000, // "1" while in the context of Grid.MeasureOverride + ArrangeOverrideInProgress = 0x00080000, // "1" while in the context of Grid.ArrangeOverride + } + + #endregion Private Structures Classes + + //------------------------------------------------------ + // + // Properties + // + //------------------------------------------------------ + + #region Properties + + /// + /// ShowGridLines property. This property is used mostly + /// for simplification of visual debuggig. When it is set + /// to true grid lines are drawn to visualize location + /// of grid lines. + /// + public static readonly DependencyProperty ShowGridLinesProperty = + DependencyProperty.Register( + "ShowGridLines", + typeof(bool), + typeof(Grid), + new FrameworkPropertyMetadata( + false, + new PropertyChangedCallback(OnShowGridLinesPropertyChanged))); + + /// + /// Column property. This is an attached property. + /// Grid defines Column property, so that it can be set + /// on any element treated as a cell. Column property + /// specifies child's position with respect to columns. + /// + /// + /// Columns are 0 - based. In order to appear in first column, element + /// should have Column property set to 0. + /// Default value for the property is 0. + /// + [CommonDependencyProperty] + public static readonly DependencyProperty ColumnProperty = + DependencyProperty.RegisterAttached( + "Column", + typeof(int), + typeof(Grid), + new FrameworkPropertyMetadata( + 0, + new PropertyChangedCallback(OnCellAttachedPropertyChanged)), + new ValidateValueCallback(IsIntValueNotNegative)); + + /// + /// Row property. This is an attached property. + /// Grid defines Row, so that it can be set + /// on any element treated as a cell. Row property + /// specifies child's position with respect to rows. + /// + /// Rows are 0 - based. In order to appear in first row, element + /// should have Row property set to 0. + /// Default value for the property is 0. + /// + /// + [CommonDependencyProperty] + public static readonly DependencyProperty RowProperty = + DependencyProperty.RegisterAttached( + "Row", + typeof(int), + typeof(Grid), + new FrameworkPropertyMetadata( + 0, + new PropertyChangedCallback(OnCellAttachedPropertyChanged)), + new ValidateValueCallback(IsIntValueNotNegative)); + + /// + /// ColumnSpan property. This is an attached property. + /// Grid defines ColumnSpan, so that it can be set + /// on any element treated as a cell. ColumnSpan property + /// specifies child's width with respect to columns. + /// Example, ColumnSpan == 2 means that child will span across two columns. + /// + /// + /// Default value for the property is 1. + /// + [CommonDependencyProperty] + public static readonly DependencyProperty ColumnSpanProperty = + DependencyProperty.RegisterAttached( + "ColumnSpan", + typeof(int), + typeof(Grid), + new FrameworkPropertyMetadata( + 1, + new PropertyChangedCallback(OnCellAttachedPropertyChanged)), + new ValidateValueCallback(IsIntValueGreaterThanZero)); + + /// + /// RowSpan property. This is an attached property. + /// Grid defines RowSpan, so that it can be set + /// on any element treated as a cell. RowSpan property + /// specifies child's height with respect to row grid lines. + /// Example, RowSpan == 3 means that child will span across three rows. + /// + /// + /// Default value for the property is 1. + /// + [CommonDependencyProperty] + public static readonly DependencyProperty RowSpanProperty = + DependencyProperty.RegisterAttached( + "RowSpan", + typeof(int), + typeof(Grid), + new FrameworkPropertyMetadata( + 1, + new PropertyChangedCallback(OnCellAttachedPropertyChanged)), + new ValidateValueCallback(IsIntValueGreaterThanZero)); + + + /// + /// IsSharedSizeScope property marks scoping element for shared size. + /// + public static readonly DependencyProperty IsSharedSizeScopeProperty = + DependencyProperty.RegisterAttached( + "IsSharedSizeScope", + typeof(bool), + typeof(Grid), + new FrameworkPropertyMetadata( + false, + new PropertyChangedCallback(DefinitionBase.OnIsSharedSizeScopePropertyChanged))); + + #endregion Properties + + //------------------------------------------------------ + // + // Internal Structures / Classes + // + //------------------------------------------------------ + + #region Internal Structures Classes + + /// + /// LayoutTimeSizeType is used internally and reflects layout-time size type. + /// + [System.Flags] + internal enum LayoutTimeSizeType : byte + { + None = 0x00, + Pixel = 0x01, + Auto = 0x02, + Star = 0x04, + } + + #endregion Internal Structures Classes + + //------------------------------------------------------ + // + // Private Structures / Classes + // + //------------------------------------------------------ + + #region Private Structures Classes + + /// + /// CellCache stored calculated values of + /// 1. attached cell positioning properties; + /// 2. size type; + /// 3. index of a next cell in the group; + /// + private struct CellCache + { + internal int ColumnIndex; + internal int RowIndex; + internal int ColumnSpan; + internal int RowSpan; + internal LayoutTimeSizeType SizeTypeU; + internal LayoutTimeSizeType SizeTypeV; + internal int Next; + internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } } + internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } } + internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } } + internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } } + } + + /// + /// Helper class for representing a key for a span in hashtable. + /// + private class SpanKey + { + /// + /// Constructor. + /// + /// Starting index of the span. + /// Span count. + /// true for columns; false for rows. + internal SpanKey(int start, int count, bool u) + { + _start = start; + _count = count; + _u = u; + } + + /// + /// + /// + public override int GetHashCode() + { + int hash = (_start ^ (_count << 2)); + + if (_u) hash &= 0x7ffffff; + else hash |= 0x8000000; + + return (hash); + } + + /// + /// + /// + public override bool Equals(object obj) + { + SpanKey sk = obj as SpanKey; + return ( sk != null + && sk._start == _start + && sk._count == _count + && sk._u == _u ); + } + + /// + /// Returns start index of the span. + /// + internal int Start { get { return (_start); } } + + /// + /// Returns span count. + /// + internal int Count { get { return (_count); } } + + /// + /// Returns true if this is a column span. + /// false if this is a row span. + /// + internal bool U { get { return (_u); } } + + private int _start; + private int _count; + private bool _u; + } + + /// + /// SpanPreferredDistributionOrderComparer. + /// + private class SpanPreferredDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + if (definitionX.UserSize.IsAuto) + { + if (definitionY.UserSize.IsAuto) + { + result = definitionX.MinSize.CompareTo(definitionY.MinSize); + } + else + { + result = -1; + } + } + else + { + if (definitionY.UserSize.IsAuto) + { + result = +1; + } + else + { + result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize); + } + } + } + + return result; + } + } + + /// + /// SpanMaxDistributionOrderComparer. + /// + private class SpanMaxDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + if (definitionX.UserSize.IsAuto) + { + if (definitionY.UserSize.IsAuto) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + else + { + result = +1; + } + } + else + { + if (definitionY.UserSize.IsAuto) + { + result = -1; + } + else + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + } + } + + return result; + } + } + + /// + /// StarDistributionOrderComparer. + /// + private class StarDistributionOrderComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// DistributionOrderComparer. + /// + private class DistributionOrderComparer: IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + double xprime = definitionX.SizeCache - definitionX.MinSizeForArrange; + double yprime = definitionY.SizeCache - definitionY.MinSizeForArrange; + result = xprime.CompareTo(yprime); + } + + return result; + } + } + + + /// + /// StarDistributionOrderIndexComparer. + /// + private class StarDistributionOrderIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal StarDistributionOrderIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// DistributionOrderComparer. + /// + private class DistributionOrderIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal DistributionOrderIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + double xprime = definitionX.SizeCache - definitionX.MinSizeForArrange; + double yprime = definitionY.SizeCache - definitionY.MinSizeForArrange; + result = xprime.CompareTo(yprime); + } + + return result; + } + } + + /// + /// RoundingErrorIndexComparer. + /// + private class RoundingErrorIndexComparer : IComparer + { + private readonly double[] errors; + + internal RoundingErrorIndexComparer(double[] errors) + { + Invariant.Assert(errors != null); + this.errors = errors; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + int result; + + if (!CompareNullRefs(indexX, indexY, out result)) + { + double errorX = errors[indexX.Value]; + double errorY = errors[indexY.Value]; + result = errorX.CompareTo(errorY); + } + + return result; + } + } + + /// + /// MinRatioComparer. + /// Sort by w/min (stored in MeasureSize), descending. + /// We query the list from the back, i.e. in ascending order of w/min. + /// + private class MinRatioComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionY, definitionX, out result)) + { + result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); + } + + return result; + } + } + + /// + /// MaxRatioComparer. + /// Sort by w/max (stored in SizeCache), ascending. + /// We query the list from the back, i.e. in descending order of w/max. + /// + private class MaxRatioComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// StarWeightComparer. + /// Sort by *-weight (stored in MeasureSize), ascending. + /// + private class StarWeightComparer : IComparer + { + public int Compare(object x, object y) + { + DefinitionBase definitionX = x as DefinitionBase; + DefinitionBase definitionY = y as DefinitionBase; + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); + } + + return result; + } + } + + /// + /// MinRatioIndexComparer. + /// + private class MinRatioIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal MinRatioIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionY, definitionX, out result)) + { + result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); + } + + return result; + } + } + + /// + /// MaxRatioIndexComparer. + /// + private class MaxRatioIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal MaxRatioIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); + } + + return result; + } + } + + /// + /// MaxRatioIndexComparer. + /// + private class StarWeightIndexComparer : IComparer + { + private readonly DefinitionBase[] definitions; + + internal StarWeightIndexComparer(DefinitionBase[] definitions) + { + Invariant.Assert(definitions != null); + this.definitions = definitions; + } + + public int Compare(object x, object y) + { + int? indexX = x as int?; + int? indexY = y as int?; + + DefinitionBase definitionX = null; + DefinitionBase definitionY = null; + + if (indexX != null) + { + definitionX = definitions[indexX.Value]; + } + if (indexY != null) + { + definitionY = definitions[indexY.Value]; + } + + int result; + + if (!CompareNullRefs(definitionX, definitionY, out result)) + { + result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); + } + + return result; + } + } + + /// + /// Implementation of a simple enumerator of grid's logical children + /// + private class GridChildrenCollectionEnumeratorSimple : IEnumerator + { + internal GridChildrenCollectionEnumeratorSimple(Grid grid, bool includeChildren) + { + Debug.Assert(grid != null); + _currentEnumerator = -1; + _enumerator0 = new ColumnDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.ColumnDefinitions : null); + _enumerator1 = new RowDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.RowDefinitions : null); + // GridLineRenderer is NOT included into this enumerator. + _enumerator2Index = 0; + if (includeChildren) + { + _enumerator2Collection = grid.Children; + _enumerator2Count = _enumerator2Collection.Count; + } + else + { + _enumerator2Collection = null; + _enumerator2Count = 0; + } + } + + public bool MoveNext() + { + while (_currentEnumerator < 3) + { + if (_currentEnumerator >= 0) + { + switch (_currentEnumerator) + { + case (0): if (_enumerator0.MoveNext()) { _currentChild = _enumerator0.Current; return (true); } break; + case (1): if (_enumerator1.MoveNext()) { _currentChild = _enumerator1.Current; return (true); } break; + case (2): if (_enumerator2Index < _enumerator2Count) + { + _currentChild = _enumerator2Collection[_enumerator2Index]; + _enumerator2Index++; + return (true); + } + break; + } + } + _currentEnumerator++; + } + return (false); + } + + public Object Current + { + get + { + if (_currentEnumerator == -1) + { + #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception + throw new InvalidOperationException(SR.Get(SRID.EnumeratorNotStarted)); + } + if (_currentEnumerator >= 3) + { + #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception + throw new InvalidOperationException(SR.Get(SRID.EnumeratorReachedEnd)); + } + + // assert below is not true anymore since UIElementCollection allowes for null children + //Debug.Assert(_currentChild != null); + return (_currentChild); + } + } + + public void Reset() + { + _currentEnumerator = -1; + _currentChild = null; + _enumerator0.Reset(); + _enumerator1.Reset(); + _enumerator2Index = 0; + } + + private int _currentEnumerator; + private Object _currentChild; + private ColumnDefinitionCollection.Enumerator _enumerator0; + private RowDefinitionCollection.Enumerator _enumerator1; + private UIElementCollection _enumerator2Collection; + private int _enumerator2Index; + private int _enumerator2Count; + } + + /// + /// Helper to render grid lines. + /// + internal class GridLinesRenderer : DrawingVisual + { + /// + /// Static initialization + /// + static GridLinesRenderer() + { + s_oddDashPen = new Pen(Brushes.Blue, c_penWidth); + DoubleCollection oddDashArray = new DoubleCollection(); + oddDashArray.Add(c_dashLength); + oddDashArray.Add(c_dashLength); + s_oddDashPen.DashStyle = new DashStyle(oddDashArray, 0); + s_oddDashPen.DashCap = PenLineCap.Flat; + s_oddDashPen.Freeze(); + + s_evenDashPen = new Pen(Brushes.Yellow, c_penWidth); + DoubleCollection evenDashArray = new DoubleCollection(); + evenDashArray.Add(c_dashLength); + evenDashArray.Add(c_dashLength); + s_evenDashPen.DashStyle = new DashStyle(evenDashArray, c_dashLength); + s_evenDashPen.DashCap = PenLineCap.Flat; + s_evenDashPen.Freeze(); + } + + /// + /// UpdateRenderBounds. + /// + /// Size of render bounds + internal void UpdateRenderBounds(Size boundsSize) + { + using (DrawingContext drawingContext = RenderOpen()) + { + Grid grid = VisualTreeHelper.GetParent(this) as Grid; + if ( grid == null + || grid.ShowGridLines == false ) + { + return; + } + + for (int i = 1; i < grid.DefinitionsU.Length; ++i) + { + DrawGridLine( + drawingContext, + grid.DefinitionsU[i].FinalOffset, 0.0, + grid.DefinitionsU[i].FinalOffset, boundsSize.Height); + } + + for (int i = 1; i < grid.DefinitionsV.Length; ++i) + { + DrawGridLine( + drawingContext, + 0.0, grid.DefinitionsV[i].FinalOffset, + boundsSize.Width, grid.DefinitionsV[i].FinalOffset); + } + } + } + + /// + /// Draw single hi-contrast line. + /// + private static void DrawGridLine( + DrawingContext drawingContext, + double startX, + double startY, + double endX, + double endY) + { + Point start = new Point(startX, startY); + Point end = new Point(endX, endY); + drawingContext.DrawLine(s_oddDashPen, start, end); + drawingContext.DrawLine(s_evenDashPen, start, end); + } + + private const double c_dashLength = 4.0; // + private const double c_penWidth = 1.0; // + private static readonly Pen s_oddDashPen; // first pen to draw dash + private static readonly Pen s_evenDashPen; // second pen to draw dash + private static readonly Point c_zeroPoint = new Point(0, 0); + } + + #endregion Private Structures Classes + + //------------------------------------------------------ + // + // Extended debugging for grid + // + //------------------------------------------------------ + +#if GRIDPARANOIA + private static double _performanceFrequency; + private static readonly bool _performanceFrequencyInitialized = InitializePerformanceFrequency(); + + //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")] + private static extern bool QueryPerformanceCounter(out long lpPerformanceCount); + + //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")] + private static extern bool QueryPerformanceFrequency(out long lpFrequency); + + private static double CostInMilliseconds(long count) + { + return ((double)count / _performanceFrequency); + } + + private static long Cost(long startCount, long endCount) + { + long l = endCount - startCount; + if (l < 0) { l += long.MaxValue; } + return (l); + } + + private static bool InitializePerformanceFrequency() + { + long l; + QueryPerformanceFrequency(out l); + _performanceFrequency = (double)l * 0.001; + return (true); + } + + private struct Counter + { + internal long Start; + internal long Total; + internal int Calls; + } + + private Counter[] _counters; + private bool _hasNewCounterInfo; +#endif // GRIDPARANOIA + + // + // This property + // 1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject + // 2. This is a performance optimization + // + internal override int EffectiveValuesInitialSize + { + get { return 9; } + } + + [Conditional("GRIDPARANOIA")] + internal void EnterCounterScope(Counters scopeCounter) + { + #if GRIDPARANOIA + if (ID == "CountThis") + { + if (_counters == null) + { + _counters = new Counter[(int)Counters.Count]; + } + ExitCounterScope(Counters.Default); + EnterCounter(scopeCounter); + } + else + { + _counters = null; + } + #endif // GRIDPARANOIA + } + + [Conditional("GRIDPARANOIA")] + internal void ExitCounterScope(Counters scopeCounter) + { + #if GRIDPARANOIA + if (_counters != null) + { + if (scopeCounter != Counters.Default) + { + ExitCounter(scopeCounter); + } + + if (_hasNewCounterInfo) + { + string NFormat = "F6"; + Console.WriteLine( + "\ncounter name | total t (ms) | # of calls | per call t (ms)" + + "\n----------------------+---------------+---------------+----------------------" ); + + for (int i = 0; i < _counters.Length; ++i) + { + if (_counters[i].Calls > 0) + { + Counters counter = (Counters)i; + double total = CostInMilliseconds(_counters[i].Total); + double single = total / _counters[i].Calls; + string counterName = counter.ToString(); + string separator; + + if (counterName.Length < 8) { separator = "\t\t\t"; } + else if (counterName.Length < 16) { separator = "\t\t"; } + else { separator = "\t"; } + + Console.WriteLine( + counter.ToString() + separator + + total.ToString(NFormat) + "\t" + + _counters[i].Calls + "\t\t" + + single.ToString(NFormat)); + + _counters[i] = new Counter(); + } + } + } + _hasNewCounterInfo = false; + } + #endif // GRIDPARANOIA + } + + [Conditional("GRIDPARANOIA")] + internal void EnterCounter(Counters counter) + { + #if GRIDPARANOIA + if (_counters != null) + { + Debug.Assert((int)counter < _counters.Length); + + int i = (int)counter; + QueryPerformanceCounter(out _counters[i].Start); + } + #endif // GRIDPARANOIA + } + + [Conditional("GRIDPARANOIA")] + internal void ExitCounter(Counters counter) + { + #if GRIDPARANOIA + if (_counters != null) + { + Debug.Assert((int)counter < _counters.Length); + + int i = (int)counter; + long l; + QueryPerformanceCounter(out l); + l = Cost(_counters[i].Start, l); + _counters[i].Total += l; + _counters[i].Calls++; + _hasNewCounterInfo = true; + } + #endif // GRIDPARANOIA + } + + internal enum Counters : int + { + Default = -1, + + MeasureOverride, + _ValidateColsStructure, + _ValidateRowsStructure, + _ValidateCells, + _MeasureCell, + __MeasureChild, + _CalculateDesiredSize, + + ArrangeOverride, + _SetFinalSize, + _ArrangeChildHelper2, + _PositionCell, + + Count, + } + } +} + + + + diff --git a/src/Avalonia.Controls/Grid/DefinitionBase.cs b/src/Avalonia.Controls/Grid/DefinitionBase.cs deleted file mode 100644 index 051ed49289..0000000000 --- a/src/Avalonia.Controls/Grid/DefinitionBase.cs +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Collections; -using System.Diagnostics; - -namespace Avalonia.Controls -{ - /// - /// Base class for and . - /// - public abstract class DefinitionBase : AvaloniaObject - { - /// - /// Static ctor. Used for static registration of properties. - /// - static DefinitionBase() - { - SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - } - - internal bool UseSharedMinimum { get; set; } - internal bool LayoutWasUpdated { get; set; } - - private int _parentIndex = -1; // this instance's index in parent's children collection - private LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" - private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's - private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure - private double _sizeCache; // cache used for various purposes (sorting, caching, etc) during calculations - private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case) - internal SharedSizeScope _privateSharedSizeScope; - private SharedSizeState _sharedState; // reference to shared state object this instance is registered with - - /// - /// Defines the property. - /// - public static readonly StyledProperty SharedSizeGroupProperty = - AvaloniaProperty.Register(nameof(SharedSizeGroup), inherits: true); - - /// - /// Gets or sets the name of the shared size group of the column or row. - /// - public string SharedSizeGroup - { - get { return GetValue(SharedSizeGroupProperty); } - set { SetValue(SharedSizeGroupProperty, value); } - } - /// - /// Callback to notify about entering model tree. - /// - internal void OnEnterParentTree(Grid grid, int index) - { - Parent = grid; - _parentIndex = index; - } - - internal void UpdateSharedScope() - { - if (_sharedState == null & - SharedSizeGroup != null & - Parent?.PrivateSharedSizeScope != null ) - { - _privateSharedSizeScope = Parent.PrivateSharedSizeScope; - _sharedState = _privateSharedSizeScope.EnsureSharedState(SharedSizeGroup); - _sharedState.AddMember(this); - } - } - - internal Grid Parent { get; set; } - - /// - /// Callback to notify about exitting model tree. - /// - internal void OnExitParentTree() - { - _offset = 0; - if (_sharedState != null) - { - _sharedState.RemoveMember(this); - _sharedState = null; - } - } - - /// - /// Performs action preparing definition to enter layout calculation mode. - /// - internal void OnBeforeLayout(Grid grid) - { - if (SharedSizeGroup != null) - UpdateSharedScope(); - - // reset layout state. - _minSize = 0; - LayoutWasUpdated = true; - - // defer verification for shared definitions - if (_sharedState != null) - { - _sharedState.EnsureDeferredValidation(grid); - } - } - - /// - /// Updates min size. - /// - /// New size. - internal void UpdateMinSize(double minSize) - { - _minSize = Math.Max(_minSize, minSize); - } - - /// - /// Sets min size. - /// - /// New size. - internal void SetMinSize(double minSize) - { - _minSize = minSize; - } - - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal void OnUserSizePropertyChanged(AvaloniaPropertyChangedEventArgs e) - { - _sharedState?.Invalidate(); - } - - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal static bool IsUserMinSizePropertyValueValid(object value) - { - double v = (double)value; - return (!double.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); - } - - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal static void OnUserMaxSizePropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) - { - Grid parentGrid = (Grid)definition.Parent; - parentGrid.InvalidateMeasure(); - - } - - /// - /// This method needs to be internal to be accessable from derived classes. - /// - internal static bool IsUserMaxSizePropertyValueValid(object value) - { - double v = (double)value; - return (!double.IsNaN(v) && v >= 0.0d); - } - - /// - /// Returns true if this definition is a part of shared group. - /// - internal bool IsShared - { - get { return (_sharedState != null); } - } - - /// - /// Internal accessor to user size field. - /// - internal GridLength UserSize - { - get { return (_sharedState != null ? _sharedState.UserSize : UserSizeValueCache); } - } - - /// - /// Internal accessor to user min size field. - /// - internal double UserMinSize - { - get { return (UserMinSizeValueCache); } - } - - /// - /// Internal accessor to user max size field. - /// - internal double UserMaxSize - { - get { return (UserMaxSizeValueCache); } - } - - /// - /// DefinitionBase's index in the parents collection. - /// - internal int Index - { - get - { - return (_parentIndex); - } - set - { - Debug.Assert(value >= -1 && _parentIndex != value); - _parentIndex = value; - } - } - - /// - /// Layout-time user size type. - /// - internal LayoutTimeSizeType SizeType - { - get { return (_sizeType); } - set { _sizeType = value; } - } - - /// - /// Returns or sets measure size for the definition. - /// - internal double MeasureSize - { - get { return (_measureSize); } - set { _measureSize = value; } - } - - /// - /// Returns definition's layout time type sensitive preferred size. - /// - /// - /// Returned value is guaranteed to be true preferred size. - /// - internal double PreferredSize - { - get - { - double preferredSize = MinSize; - if (_sizeType != LayoutTimeSizeType.Auto - && preferredSize < _measureSize) - { - preferredSize = _measureSize; - } - return (preferredSize); - } - } - - /// - /// Returns or sets size cache for the definition. - /// - internal double SizeCache - { - get { return (_sizeCache); } - set { _sizeCache = value; } - } - - /// - /// Returns min size. - /// - internal double MinSize - { - get - { - double minSize = _minSize; - if (UseSharedMinimum - && _sharedState != null - && minSize < _sharedState.MinSize) - { - minSize = _sharedState.MinSize; - } - return (minSize); - } - } - - /// - /// Returns min size, always taking into account shared state. - /// - internal double MinSizeForArrange - { - get - { - double minSize = _minSize; - if (_sharedState != null - && (UseSharedMinimum || !LayoutWasUpdated) - && minSize < _sharedState.MinSize) - { - minSize = _sharedState.MinSize; - } - return (minSize); - } - } - - /// - /// Offset. - /// - internal double FinalOffset - { - get { return _offset; } - set { _offset = value; } - } - - /// - /// Internal helper to access up-to-date UserSize property value. - /// - internal abstract GridLength UserSizeValueCache { get; } - - /// - /// Internal helper to access up-to-date UserMinSize property value. - /// - internal abstract double UserMinSizeValueCache { get; } - - /// - /// Internal helper to access up-to-date UserMaxSize property value. - /// - internal abstract double UserMaxSizeValueCache { get; } - - private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e) - { - string sharedSizeGroupId = (string)e.NewValue; - - if (definition._sharedState != null) - { - // if definition is already registered AND shared size group id is changing, - // then un-register the definition from the current shared size state object. - definition._sharedState.RemoveMember(definition); - definition._sharedState = null; - } - - if ((definition._sharedState == null) && (sharedSizeGroupId != null)) - { - var privateSharedSizeScope = definition._privateSharedSizeScope; - if (privateSharedSizeScope != null) - { - // if definition is not registered and both: shared size group id AND private shared scope - // are available, then register definition. - definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - definition._sharedState.AddMember(definition); - - } - } - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/Grid.cs b/src/Avalonia.Controls/Grid/Grid.cs deleted file mode 100644 index 511853a982..0000000000 --- a/src/Avalonia.Controls/Grid/Grid.cs +++ /dev/null @@ -1,2378 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using Avalonia.Collections; -using Avalonia.Controls.Utils; -using System.Threading; -using JetBrains.Annotations; -using Avalonia.Controls; -using Avalonia; -using System.Collections; -using Avalonia.Utilities; -using Avalonia.Layout; - -namespace Avalonia.Controls -{ - public class Grid : Panel - { - internal bool CellsStructureDirty = true; - internal bool SizeToContentU; - internal bool SizeToContentV; - internal bool HasStarCellsU; - internal bool HasStarCellsV; - internal bool HasGroup3CellsInAutoRows; - internal bool DefinitionsDirty; - internal bool IsTrivialGrid => (_definitionsU?.Length <= 1) && - (_definitionsV?.Length <= 1); - internal int CellGroup1; - internal int CellGroup2; - internal int CellGroup3; - internal int CellGroup4; - - internal bool HasSharedSizeScope() - { - return this.GetValue(Grid.PrivateSharedSizeScopeProperty) != null; - } - - /// - /// Helper for Comparer methods. - /// - /// - /// true if one or both of x and y are null, in which case result holds - /// the relative sort order. - /// - internal static bool CompareNullRefs(object x, object y, out int result) - { - result = 2; - - if (x == null) - { - if (y == null) - { - result = 0; - } - else - { - result = -1; - } - } - else - { - if (y == null) - { - result = 1; - } - } - - return (result != 2); - } - - // temporary array used during layout for various purposes - // TempDefinitions.Length == Max(DefinitionsU.Length, DefinitionsV.Length) - private DefinitionBase[] _tempDefinitions; - - - private GridLinesRenderer _gridLinesRenderer; - - // Keeps track of definition indices. - private int[] _definitionIndices; - - private GridCellCache[] _cellCache; - - // Stores unrounded values and rounding errors during layout rounding. - private double[] _roundingErrors; - private ColumnDefinitions _columnDefinitions; - private RowDefinitions _rowDefinitions; - private DefinitionBase[] _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - private DefinitionBase[] _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - - internal SharedSizeScope PrivateSharedSizeScope - { - get { return GetPrivateSharedSizeScope(this); } - set { SetPrivateSharedSizeScope(this, value); } - } - - // 5 is an arbitrary constant chosen to end the measure loop - private const int _layoutLoopMaxCount = 5; - private static readonly LocalDataStoreSlot _tempDefinitionsDataSlot; - private static readonly IComparer _spanPreferredDistributionOrderComparer; - private static readonly IComparer _spanMaxDistributionOrderComparer; - private static readonly IComparer _minRatioComparer; - private static readonly IComparer _maxRatioComparer; - private static readonly IComparer _starWeightComparer; - - /// - /// Helper accessor to layout time array of definitions. - /// - private DefinitionBase[] TempDefinitions - { - get - { - int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length) * 2; - - if (_tempDefinitions == null - || _tempDefinitions.Length < requiredLength) - { - WeakReference tempDefinitionsWeakRef = (WeakReference)Thread.GetData(_tempDefinitionsDataSlot); - if (tempDefinitionsWeakRef == null) - { - _tempDefinitions = new DefinitionBase[requiredLength]; - Thread.SetData(_tempDefinitionsDataSlot, new WeakReference(_tempDefinitions)); - } - else - { - _tempDefinitions = (DefinitionBase[])tempDefinitionsWeakRef.Target; - if (_tempDefinitions == null - || _tempDefinitions.Length < requiredLength) - { - _tempDefinitions = new DefinitionBase[requiredLength]; - tempDefinitionsWeakRef.Target = _tempDefinitions; - } - } - } - return (_tempDefinitions); - } - } - - /// - /// Helper accessor to definition indices. - /// - private int[] DefinitionIndices - { - get - { - int requiredLength = Math.Max(Math.Max(_definitionsU.Length, _definitionsV.Length), 1) * 2; - - if (_definitionIndices == null || _definitionIndices.Length < requiredLength) - { - _definitionIndices = new int[requiredLength]; - } - - return _definitionIndices; - } - } - - /// - /// Helper accessor to rounding errors. - /// - private double[] RoundingErrors - { - get - { - int requiredLength = Math.Max(_definitionsU.Length, _definitionsV.Length); - - if (_roundingErrors == null && requiredLength == 0) - { - _roundingErrors = new double[1]; - } - else if (_roundingErrors == null || _roundingErrors.Length < requiredLength) - { - _roundingErrors = new double[requiredLength]; - } - return _roundingErrors; - } - } - - static Grid() - { - ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); - IsSharedSizeScopeProperty.Changed.AddClassHandler(IsSharedSizeScopePropertyChanged); - BoundsProperty.Changed.AddClassHandler(BoundsPropertyChanged); - - AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); - - _tempDefinitionsDataSlot = Thread.AllocateDataSlot(); - _spanPreferredDistributionOrderComparer = new SpanPreferredDistributionOrderComparer(); - _spanMaxDistributionOrderComparer = new SpanMaxDistributionOrderComparer(); - _minRatioComparer = new MinRatioComparer(); - _maxRatioComparer = new MaxRatioComparer(); - _starWeightComparer = new StarWeightComparer(); - } - - private static void BoundsPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs arg2) - { - for (int i = 0; i < grid._definitionsU.Length; i++) - grid._definitionsU[i].OnUserSizePropertyChanged(arg2); - for (int i = 0; i < grid._definitionsV.Length; i++) - grid._definitionsV[i].OnUserSizePropertyChanged(arg2); - - UpdateSharedSizeScopes(grid); - } - - private static void IsSharedSizeScopePropertyChanged(Control control, AvaloniaPropertyChangedEventArgs e) - { - if ((bool)e.NewValue) - { - control.SetValue(Grid.PrivateSharedSizeScopeProperty, new SharedSizeScope()); - } - else - { - control.SetValue(Grid.PrivateSharedSizeScopeProperty, null); - } - } - - static void UpdateSharedSizeScopes(Grid grid) - { - for (int i = 0; i < grid._definitionsU.Length; i++) - if (grid._definitionsU[i].SharedSizeGroup != null) - grid._definitionsU[i].UpdateSharedScope(); - for (int i = 0; i < grid._definitionsV.Length; i++) - if (grid._definitionsV[i].SharedSizeGroup != null) - grid._definitionsV[i].UpdateSharedScope(); - } - - /// - /// Defines the Column attached property. - /// - public static readonly AttachedProperty ColumnProperty = - AvaloniaProperty.RegisterAttached( - "Column", - validate: ValidateColumn); - - /// - /// Defines the ColumnSpan attached property. - /// - public static readonly AttachedProperty ColumnSpanProperty = - AvaloniaProperty.RegisterAttached("ColumnSpan", 1); - - /// - /// Defines the Row attached property. - /// - public static readonly AttachedProperty RowProperty = - AvaloniaProperty.RegisterAttached( - "Row", - validate: ValidateRow); - - /// - /// Defines the RowSpan attached property. - /// - public static readonly AttachedProperty RowSpanProperty = - AvaloniaProperty.RegisterAttached("RowSpan", 1); - - public static readonly AttachedProperty IsSharedSizeScopeProperty = - AvaloniaProperty.RegisterAttached("IsSharedSizeScope", false); - - internal static readonly AttachedProperty PrivateSharedSizeScopeProperty = - AvaloniaProperty.RegisterAttached("&&PrivateSharedSizeScope", null, inherits: true); - - /// - /// Defines the property. - /// - public static readonly StyledProperty ShowGridLinesProperty = - AvaloniaProperty.Register( - nameof(ShowGridLines), - defaultValue: false); - - /// - /// ShowGridLines property. - /// - public bool ShowGridLines - { - get { return GetValue(ShowGridLinesProperty); } - set { SetValue(ShowGridLinesProperty, value); } - } - - /// - /// Gets or sets the columns definitions for the grid. - /// - public ColumnDefinitions ColumnDefinitions - { - get - { - if (_columnDefinitions == null) - { - ColumnDefinitions = new ColumnDefinitions(); - } - - return _columnDefinitions; - } - set - { - _columnDefinitions = value; - _columnDefinitions.TrackItemPropertyChanged(_ => Invalidate()); - DefinitionsDirty = true; - - if (_columnDefinitions.Count > 0) - _definitionsU = _columnDefinitions.Cast().ToArray(); - - CallEnterParentTree(_definitionsU); - - _columnDefinitions.CollectionChanged += delegate - { - CallExitParentTree(_definitionsU); - - if (_columnDefinitions.Count == 0) - { - _definitionsU = new DefinitionBase[1] { new ColumnDefinition() }; - } - else - { - _definitionsU = _columnDefinitions.Cast().ToArray(); - DefinitionsDirty = true; - } - - CallEnterParentTree(_definitionsU); - - Invalidate(); - }; - } - } - - private void CallEnterParentTree(DefinitionBase[] definitionsU) - { - for (int i = 0; i < definitionsU.Length; i++) - definitionsU[i].OnEnterParentTree(this, i); - } - - private void CallExitParentTree(DefinitionBase[] definitionsU) - { - for (int i = 0; i < definitionsU.Length; i++) - definitionsU[i].OnExitParentTree(); - } - - /// - /// Gets or sets the row definitions for the grid. - /// - public RowDefinitions RowDefinitions - { - get - { - if (_rowDefinitions == null) - { - RowDefinitions = new RowDefinitions(); - } - - return _rowDefinitions; - } - set - { - _rowDefinitions = value; - _rowDefinitions.TrackItemPropertyChanged(_ => Invalidate()); - - DefinitionsDirty = true; - - if (_rowDefinitions.Count > 0) - _definitionsV = _rowDefinitions.Cast().ToArray(); - - _rowDefinitions.CollectionChanged += delegate - { - CallExitParentTree(_definitionsU); - - if (_rowDefinitions.Count == 0) - { - _definitionsV = new DefinitionBase[1] { new RowDefinition() }; - } - else - { - _definitionsV = _rowDefinitions.Cast().ToArray(); - DefinitionsDirty = true; - } - CallEnterParentTree(_definitionsU); - - Invalidate(); - }; - } - } - - - /// - /// Gets the value of the Column attached property for a control. - /// - /// The control. - /// The control's column. - public static int GetColumn(AvaloniaObject element) - { - return element.GetValue(ColumnProperty); - } - - /// - /// Gets the value of the ColumnSpan attached property for a control. - /// - /// The control. - /// The control's column span. - public static int GetColumnSpan(AvaloniaObject element) - { - return element.GetValue(ColumnSpanProperty); - } - - /// - /// Gets the value of the Row attached property for a control. - /// - /// The control. - /// The control's row. - public static int GetRow(AvaloniaObject element) - { - return element.GetValue(RowProperty); - } - - /// - /// Gets the value of the RowSpan attached property for a control. - /// - /// The control. - /// The control's row span. - public static int GetRowSpan(AvaloniaObject element) - { - return element.GetValue(RowSpanProperty); - } - - /// - /// Gets the value of the IsSharedSizeScope attached property for a control. - /// - /// The control. - /// The control's IsSharedSizeScope value. - public static bool GetIsSharedSizeScope(AvaloniaObject element) - { - return element.GetValue(IsSharedSizeScopeProperty); - } - - /// - /// Sets the value of the IsSharedSizeScope attached property for a control. - /// - public static void SetIsSharedSizeScope(AvaloniaObject element, bool value) - { - element.SetValue(IsSharedSizeScopeProperty, value); - } - - internal static SharedSizeScope GetPrivateSharedSizeScope(AvaloniaObject element) - { - return element.GetValue(PrivateSharedSizeScopeProperty); - } - - internal static void SetPrivateSharedSizeScope(AvaloniaObject element, SharedSizeScope value) - { - element.SetValue(PrivateSharedSizeScopeProperty, value); - } - - /// - /// Sets the value of the Column attached property for a control. - /// - /// The control. - /// The column value. - public static void SetColumn(AvaloniaObject element, int value) - { - element.SetValue(ColumnProperty, value); - } - - /// - /// Sets the value of the ColumnSpan attached property for a control. - /// - /// The control. - /// The column span value. - public static void SetColumnSpan(AvaloniaObject element, int value) - { - element.SetValue(ColumnSpanProperty, value); - } - - /// - /// Sets the value of the Row attached property for a control. - /// - /// The control. - /// The row value. - public static void SetRow(AvaloniaObject element, int value) - { - element.SetValue(RowProperty, value); - } - - /// - /// Sets the value of the RowSpan attached property for a control. - /// - /// The control. - /// The row span value. - public static void SetRowSpan(AvaloniaObject element, int value) - { - element.SetValue(RowSpanProperty, value); - } - - /// - /// Content measurement. - /// - /// Constraint - /// Desired size - protected override Size MeasureOverride(Size constraint) - { - Size gridDesiredSize; - - try - { - if (IsTrivialGrid) - { - gridDesiredSize = new Size(); - - for (int i = 0, count = Children.Count; i < count; ++i) - { - var child = Children[i]; - if (child != null) - { - child.Measure(constraint); - gridDesiredSize = new Size( - Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), - Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); - } - } - } - else - { - { - bool sizeToContentU = double.IsPositiveInfinity(constraint.Width); - bool sizeToContentV = double.IsPositiveInfinity(constraint.Height); - - // Clear index information and rounding errors - if (DefinitionsDirty) - { - if (_definitionIndices != null) - { - Array.Clear(_definitionIndices, 0, _definitionIndices.Length); - _definitionIndices = null; - } - - if (UseLayoutRounding) - { - if (_roundingErrors != null) - { - Array.Clear(_roundingErrors, 0, _roundingErrors.Length); - _roundingErrors = null; - } - } - - DefinitionsDirty = false; - } - - ValidateDefinitionsLayout(_definitionsU, sizeToContentU); - ValidateDefinitionsLayout(_definitionsV, sizeToContentV); - - CellsStructureDirty |= (SizeToContentU != sizeToContentU) - || (SizeToContentV != sizeToContentV); - - SizeToContentU = sizeToContentU; - SizeToContentV = sizeToContentV; - } - - ValidateCells(); - - Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0); - - MeasureCellsGroup(CellGroup1, constraint, false, false); - - { - // after Group1 is measured, only Group3 may have cells belonging to Auto rows. - bool canResolveStarsV = !HasGroup3CellsInAutoRows; - - if (canResolveStarsV) - { - if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } - MeasureCellsGroup(CellGroup2, constraint, false, false); - if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } - MeasureCellsGroup(CellGroup3, constraint, false, false); - } - else - { - // if at least one cell exists in Group2, it must be measured before - // StarsU can be resolved. - bool canResolveStarsU = CellGroup2 > _cellCache.Length; - if (canResolveStarsU) - { - if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } - MeasureCellsGroup(CellGroup3, constraint, false, false); - if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } - } - else - { - // This is a revision to the algorithm employed for the cyclic - // dependency case described above. We now repeatedly - // measure Group3 and Group2 until their sizes settle. We - // also use a count heuristic to break a loop in case of one. - - bool hasDesiredSizeUChanged = false; - int cnt = 0; - - // Cache Group2MinWidths & Group3MinHeights - double[] group2MinSizes = CacheMinSizes(CellGroup2, false); - double[] group3MinSizes = CacheMinSizes(CellGroup3, true); - - MeasureCellsGroup(CellGroup2, constraint, false, true); - - do - { - if (hasDesiredSizeUChanged) - { - // Reset cached Group3Heights - ApplyCachedMinSizes(group3MinSizes, true); - } - - if (HasStarCellsU) { ResolveStar(_definitionsU, constraint.Width); } - MeasureCellsGroup(CellGroup3, constraint, false, false); - - // Reset cached Group2Widths - ApplyCachedMinSizes(group2MinSizes, false); - - if (HasStarCellsV) { ResolveStar(_definitionsV, constraint.Height); } - MeasureCellsGroup(CellGroup2, constraint, - cnt == _layoutLoopMaxCount, false, out hasDesiredSizeUChanged); - } - while (hasDesiredSizeUChanged && ++cnt <= _layoutLoopMaxCount); - } - } - } - - MeasureCellsGroup(CellGroup4, constraint, false, false); - - gridDesiredSize = new Size( - CalculateDesiredSize(_definitionsU), - CalculateDesiredSize(_definitionsV)); - } - } - finally - { - } - - UpdateSharedSizeScopes(this); - return (gridDesiredSize); - } - - /// - /// Content arrangement. - /// - /// Arrange size - protected override Size ArrangeOverride(Size arrangeSize) - { - try - { - if (IsTrivialGrid) - { - for (int i = 0, count = Children.Count; i < count; ++i) - { - var child = Children[i]; - if (child != null) - { - child.Arrange(new Rect(arrangeSize)); - } - } - } - else - { - Debug.Assert(_definitionsU.Length > 0 && _definitionsV.Length > 0); - - SetFinalSize(_definitionsU, arrangeSize.Width, true); - SetFinalSize(_definitionsV, arrangeSize.Height, false); - - for (int currentCell = 0; currentCell < _cellCache.Length; ++currentCell) - { - IControl cell = Children[currentCell]; - if (cell == null) - { - continue; - } - - int columnIndex = _cellCache[currentCell].ColumnIndex; - int rowIndex = _cellCache[currentCell].RowIndex; - int columnSpan = _cellCache[currentCell].ColumnSpan; - int rowSpan = _cellCache[currentCell].RowSpan; - - Rect cellRect = new Rect( - columnIndex == 0 ? 0.0 : _definitionsU[columnIndex].FinalOffset, - rowIndex == 0 ? 0.0 : _definitionsV[rowIndex].FinalOffset, - GetFinalSizeForRange(_definitionsU, columnIndex, columnSpan), - GetFinalSizeForRange(_definitionsV, rowIndex, rowSpan)); - - cell.Arrange(cellRect); - } - - // update render bound on grid lines renderer visual - var gridLinesRenderer = EnsureGridLinesRenderer(); - if (gridLinesRenderer != null) - { - gridLinesRenderer.UpdateRenderBounds(arrangeSize); - } - } - } - finally - { - SetValid(); - } - - for (var i = 0; i < ColumnDefinitions.Count; i++) - { - ColumnDefinitions[i].ActualWidth = GetFinalColumnDefinitionWidth(i); - } - - for (var i = 0; i < RowDefinitions.Count; i++) - { - RowDefinitions[i].ActualHeight = GetFinalRowDefinitionHeight(i); - } - - UpdateSharedSizeScopes(this); - return (arrangeSize); - } - - /// - /// Returns final width for a column. - /// - /// - /// Used from public ColumnDefinition ActualWidth. Calculates final width using offset data. - /// - private double GetFinalColumnDefinitionWidth(int columnIndex) - { - double value = 0.0; - - // actual value calculations require structure to be up-to-date - if (!DefinitionsDirty) - { - value = _definitionsU[(columnIndex + 1) % _definitionsU.Length].FinalOffset; - if (columnIndex != 0) { value -= _definitionsU[columnIndex].FinalOffset; } - } - return (value); - } - - /// - /// Returns final height for a row. - /// - /// - /// Used from public RowDefinition ActualHeight. Calculates final height using offset data. - /// - private double GetFinalRowDefinitionHeight(int rowIndex) - { - double value = 0.0; - - // actual value calculations require structure to be up-to-date - if (!DefinitionsDirty) - { - value = _definitionsV[(rowIndex + 1) % _definitionsV.Length].FinalOffset; - if (rowIndex != 0) { value -= _definitionsV[rowIndex].FinalOffset; } - } - return (value); - } - - /// - /// Invalidates grid caches and makes the grid dirty for measure. - /// - internal void Invalidate() - { - CellsStructureDirty = true; - InvalidateMeasure(); - } - - /// - /// Lays out cells according to rows and columns, and creates lookup grids. - /// - private void ValidateCells() - { - if (!CellsStructureDirty) return; - - _cellCache = new GridCellCache[Children.Count]; - CellGroup1 = int.MaxValue; - CellGroup2 = int.MaxValue; - CellGroup3 = int.MaxValue; - CellGroup4 = int.MaxValue; - - bool hasStarCellsU = false; - bool hasStarCellsV = false; - bool hasGroup3CellsInAutoRows = false; - - for (int i = _cellCache.Length - 1; i >= 0; --i) - { - var child = Children[i] as Control; - - if (child == null) - { - continue; - } - - var cell = new GridCellCache(); - - // read indices from the corresponding properties - // clamp to value < number_of_columns - // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(GetColumn(child), _definitionsU.Length - 1); - - // clamp to value < number_of_rows - // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow(child), _definitionsV.Length - 1); - - // read span properties - // clamp to not exceed beyond right side of the grid - // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan(child), _definitionsU.Length - cell.ColumnIndex); - - // clamp to not exceed beyond bottom side of the grid - // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan(child), _definitionsV.Length - cell.RowIndex); - - Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < _definitionsU.Length); - Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < _definitionsV.Length); - - // - // calculate and cache length types for the child - // - cell.SizeTypeU = GetLengthTypeForRange(_definitionsU, cell.ColumnIndex, cell.ColumnSpan); - cell.SizeTypeV = GetLengthTypeForRange(_definitionsV, cell.RowIndex, cell.RowSpan); - - hasStarCellsU |= cell.IsStarU; - hasStarCellsV |= cell.IsStarV; - - // - // distribute cells into four groups. - // - if (!cell.IsStarV) - { - if (!cell.IsStarU) - { - cell.Next = CellGroup1; - CellGroup1 = i; - } - else - { - cell.Next = CellGroup3; - CellGroup3 = i; - - // remember if this cell belongs to auto row - hasGroup3CellsInAutoRows |= cell.IsAutoV; - } - } - else - { - if (cell.IsAutoU - // note below: if spans through Star column it is NOT Auto - && !cell.IsStarU) - { - cell.Next = CellGroup2; - CellGroup2 = i; - } - else - { - cell.Next = CellGroup4; - CellGroup4 = i; - } - } - - _cellCache[i] = cell; - } - - HasStarCellsU = hasStarCellsU; - HasStarCellsV = hasStarCellsV; - HasGroup3CellsInAutoRows = hasGroup3CellsInAutoRows; - - CellsStructureDirty = false; - } - - /// - /// Validates layout time size type information on given array of definitions. - /// Sets MinSize and MeasureSizes. - /// - /// Array of definitions to update. - /// if "true" then star definitions are treated as Auto. - private void ValidateDefinitionsLayout( - DefinitionBase[] definitions, - bool treatStarAsAuto) - { - for (int i = 0; i < definitions.Length; ++i) - { - definitions[i].OnBeforeLayout(this); - - double userMinSize = definitions[i].UserMinSize; - double userMaxSize = definitions[i].UserMaxSize; - double userSize = 0; - - switch (definitions[i].UserSize.GridUnitType) - { - case (GridUnitType.Pixel): - definitions[i].SizeType = LayoutTimeSizeType.Pixel; - userSize = definitions[i].UserSize.Value; - - // this was brought with NewLayout and defeats squishy behavior - userMinSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); - break; - case (GridUnitType.Auto): - definitions[i].SizeType = LayoutTimeSizeType.Auto; - userSize = double.PositiveInfinity; - break; - case (GridUnitType.Star): - if (treatStarAsAuto) - { - definitions[i].SizeType = LayoutTimeSizeType.Auto; - userSize = double.PositiveInfinity; - } - else - { - definitions[i].SizeType = LayoutTimeSizeType.Star; - userSize = double.PositiveInfinity; - } - break; - default: - Debug.Assert(false); - break; - } - - definitions[i].UpdateMinSize(userMinSize); - definitions[i].MeasureSize = Math.Max(userMinSize, Math.Min(userSize, userMaxSize)); - } - } - - private double[] CacheMinSizes(int cellsHead, bool isRows) - { - double[] minSizes = isRows ? new double[_definitionsV.Length] - : new double[_definitionsU.Length]; - - for (int j = 0; j < minSizes.Length; j++) - { - minSizes[j] = -1; - } - - int i = cellsHead; - do - { - if (isRows) - { - minSizes[_cellCache[i].RowIndex] = _definitionsV[_cellCache[i].RowIndex].MinSize; - } - else - { - minSizes[_cellCache[i].ColumnIndex] = _definitionsU[_cellCache[i].ColumnIndex].MinSize; - } - - i = _cellCache[i].Next; - } while (i < _cellCache.Length); - - return minSizes; - } - - private void ApplyCachedMinSizes(double[] minSizes, bool isRows) - { - for (int i = 0; i < minSizes.Length; i++) - { - if (MathUtilities.GreaterThanOrClose(minSizes[i], 0)) - { - if (isRows) - { - _definitionsV[i].SetMinSize(minSizes[i]); - } - else - { - _definitionsU[i].SetMinSize(minSizes[i]); - } - } - } - } - - private void MeasureCellsGroup( - int cellsHead, - Size referenceSize, - bool ignoreDesiredSizeU, - bool forceInfinityV) - { - bool unusedHasDesiredSizeUChanged; - MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, - forceInfinityV, out unusedHasDesiredSizeUChanged); - } - - /// - /// Measures one group of cells. - /// - /// Head index of the cells chain. - /// Reference size for spanned cells - /// calculations. - /// When "true" cells' desired - /// width is not registered in columns. - /// Passed through to MeasureCell. - /// When "true" cells' desired height is not registered in rows. - private void MeasureCellsGroup( - int cellsHead, - Size referenceSize, - bool ignoreDesiredSizeU, - bool forceInfinityV, - out bool hasDesiredSizeUChanged) - { - hasDesiredSizeUChanged = false; - - if (cellsHead >= _cellCache.Length) - { - return; - } - - Hashtable spanStore = null; - bool ignoreDesiredSizeV = forceInfinityV; - - int i = cellsHead; - do - { - double oldWidth = Children[i].DesiredSize.Width; - - MeasureCell(i, forceInfinityV); - - hasDesiredSizeUChanged |= !MathUtilities.AreClose(oldWidth, Children[i].DesiredSize.Width); - - if (!ignoreDesiredSizeU) - { - if (_cellCache[i].ColumnSpan == 1) - { - _definitionsU[_cellCache[i].ColumnIndex] - .UpdateMinSize(Math.Min(Children[i].DesiredSize.Width, - _definitionsU[_cellCache[i].ColumnIndex].UserMaxSize)); - } - else - { - RegisterSpan( - ref spanStore, - _cellCache[i].ColumnIndex, - _cellCache[i].ColumnSpan, - true, - Children[i].DesiredSize.Width); - } - } - - if (!ignoreDesiredSizeV) - { - if (_cellCache[i].RowSpan == 1) - { - _definitionsV[_cellCache[i].RowIndex] - .UpdateMinSize(Math.Min(Children[i].DesiredSize.Height, - _definitionsV[_cellCache[i].RowIndex].UserMaxSize)); - } - else - { - RegisterSpan( - ref spanStore, - _cellCache[i].RowIndex, - _cellCache[i].RowSpan, - false, - Children[i].DesiredSize.Height); - } - } - - i = _cellCache[i].Next; - } while (i < _cellCache.Length); - - if (spanStore != null) - { - foreach (DictionaryEntry e in spanStore) - { - GridSpanKey key = (GridSpanKey)e.Key; - double requestedSize = (double)e.Value; - - EnsureMinSizeInDefinitionRange( - key.U ? _definitionsU : _definitionsV, - key.Start, - key.Count, - requestedSize, - key.U ? referenceSize.Width : referenceSize.Height); - } - } - } - - /// - /// Helper method to register a span information for delayed processing. - /// - /// Reference to a hashtable object used as storage. - /// Span starting index. - /// Span count. - /// true if this is a column span. false if this is a row span. - /// Value to store. If an entry already exists the biggest value is stored. - private static void RegisterSpan( - ref Hashtable store, - int start, - int count, - bool u, - double value) - { - if (store == null) - { - store = new Hashtable(); - } - - GridSpanKey key = new GridSpanKey(start, count, u); - object o = store[key]; - - if (o == null - || value > (double)o) - { - store[key] = value; - } - } - - /// - /// Takes care of measuring a single cell. - /// - /// Index of the cell to measure. - /// If "true" then cell is always - /// calculated to infinite height. - private void MeasureCell( - int cell, - bool forceInfinityV) - { - double cellMeasureWidth; - double cellMeasureHeight; - - if (_cellCache[cell].IsAutoU - && !_cellCache[cell].IsStarU) - { - // if cell belongs to at least one Auto column and not a single Star column - // then it should be calculated "to content", thus it is possible to "shortcut" - // calculations and simply assign PositiveInfinity here. - cellMeasureWidth = double.PositiveInfinity; - } - else - { - // otherwise... - cellMeasureWidth = GetMeasureSizeForRange( - _definitionsU, - _cellCache[cell].ColumnIndex, - _cellCache[cell].ColumnSpan); - } - - if (forceInfinityV) - { - cellMeasureHeight = double.PositiveInfinity; - } - else if (_cellCache[cell].IsAutoV - && !_cellCache[cell].IsStarV) - { - // if cell belongs to at least one Auto row and not a single Star row - // then it should be calculated "to content", thus it is possible to "shortcut" - // calculations and simply assign PositiveInfinity here. - cellMeasureHeight = double.PositiveInfinity; - } - else - { - cellMeasureHeight = GetMeasureSizeForRange( - _definitionsV, - _cellCache[cell].RowIndex, - _cellCache[cell].RowSpan); - } - - var child = Children[cell]; - - if (child != null) - { - Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); - child.Measure(childConstraint); - } - } - - /// - /// Calculates one dimensional measure size for given definitions' range. - /// - /// Source array of definitions to read values from. - /// Starting index of the range. - /// Number of definitions included in the range. - /// Calculated measure size. - /// - /// For "Auto" definitions MinWidth is used in place of PreferredSize. - /// - private double GetMeasureSizeForRange( - DefinitionBase[] definitions, - int start, - int count) - { - Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); - - double measureSize = 0; - int i = start + count - 1; - - do - { - measureSize += (definitions[i].SizeType == LayoutTimeSizeType.Auto) - ? definitions[i].MinSize - : definitions[i].MeasureSize; - } while (--i >= start); - - return (measureSize); - } - - /// - /// Accumulates length type information for given definition's range. - /// - /// Source array of definitions to read values from. - /// Starting index of the range. - /// Number of definitions included in the range. - /// Length type for given range. - private LayoutTimeSizeType GetLengthTypeForRange( - DefinitionBase[] definitions, - int start, - int count) - { - Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); - - LayoutTimeSizeType lengthType = LayoutTimeSizeType.None; - int i = start + count - 1; - - do - { - lengthType |= definitions[i].SizeType; - } while (--i >= start); - - return (lengthType); - } - - /// - /// Distributes min size back to definition array's range. - /// - /// Start of the range. - /// Number of items in the range. - /// Minimum size that should "fit" into the definitions range. - /// Definition array receiving distribution. - /// Size used to resolve percentages. - private void EnsureMinSizeInDefinitionRange( - DefinitionBase[] definitions, - int start, - int count, - double requestedSize, - double percentReferenceSize) - { - Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length); - - // avoid processing when asked to distribute "0" - if (!MathUtilities.IsZero(requestedSize)) - { - DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting - int end = start + count; - int autoDefinitionsCount = 0; - double rangeMinSize = 0; - double rangePreferredSize = 0; - double rangeMaxSize = 0; - double maxMaxSize = 0; // maximum of maximum sizes - - // first accumulate the necessary information: - // a) sum up the sizes in the range; - // b) count the number of auto definitions in the range; - // c) initialize temp array - // d) cache the maximum size into SizeCache - // e) accumulate max of max sizes - for (int i = start; i < end; ++i) - { - double minSize = definitions[i].MinSize; - double preferredSize = definitions[i].PreferredSize; - double maxSize = Math.Max(definitions[i].UserMaxSize, minSize); - - rangeMinSize += minSize; - rangePreferredSize += preferredSize; - rangeMaxSize += maxSize; - - definitions[i].SizeCache = maxSize; - - // sanity check: no matter what, but min size must always be the smaller; - // max size must be the biggest; and preferred should be in between - Debug.Assert(minSize <= preferredSize - && preferredSize <= maxSize - && rangeMinSize <= rangePreferredSize - && rangePreferredSize <= rangeMaxSize); - - if (maxMaxSize < maxSize) maxMaxSize = maxSize; - if (definitions[i].UserSize.IsAuto) autoDefinitionsCount++; - tempDefinitions[i - start] = definitions[i]; - } - - // avoid processing if the range already big enough - if (requestedSize > rangeMinSize) - { - if (requestedSize <= rangePreferredSize) - { - // - // requestedSize fits into preferred size of the range. - // distribute according to the following logic: - // * do not distribute into auto definitions - they should continue to stay "tight"; - // * for all non-auto definitions distribute to equi-size min sizes, without exceeding preferred size. - // - // in order to achieve that, definitions are sorted in a way that all auto definitions - // are first, then definitions follow ascending order with PreferredSize as the key of sorting. - // - double sizeToDistribute; - int i; - - Array.Sort(tempDefinitions, 0, count, _spanPreferredDistributionOrderComparer); - for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) - { - // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); - - // adjust sizeToDistribute value by subtracting auto definition min size - sizeToDistribute -= (tempDefinitions[i].MinSize); - } - - for (; i < count; ++i) - { - // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); - - double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize); - if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); } - sizeToDistribute -= newMinSize; - } - - // sanity check: requested size must all be distributed - Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); - } - else if (requestedSize <= rangeMaxSize) - { - // - // requestedSize bigger than preferred size, but fit into max size of the range. - // distribute according to the following logic: - // * do not distribute into auto definitions, if possible - they should continue to stay "tight"; - // * for all non-auto definitions distribute to euqi-size min sizes, without exceeding max size. - // - // in order to achieve that, definitions are sorted in a way that all non-auto definitions - // are last, then definitions follow ascending order with MaxSize as the key of sorting. - // - double sizeToDistribute; - int i; - - Array.Sort(tempDefinitions, 0, count, _spanMaxDistributionOrderComparer); - for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) - { - // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); - - double preferredSize = tempDefinitions[i].PreferredSize; - double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); - } - - for (; i < count; ++i) - { - // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); - - double preferredSize = tempDefinitions[i].MinSize; - double newMinSize = preferredSize + sizeToDistribute / (count - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); - } - - // sanity check: requested size must all be distributed - Debug.Assert(MathUtilities.IsZero(sizeToDistribute)); - } - else - { - // - // requestedSize bigger than max size of the range. - // distribute according to the following logic: - // * for all definitions distribute to equi-size min sizes. - // - double equalSize = requestedSize / count; - - if (equalSize < maxMaxSize - && !MathUtilities.AreClose(equalSize, maxMaxSize)) - { - // equi-size is less than maximum of maxSizes. - // in this case distribute so that smaller definitions grow faster than - // bigger ones. - double totalRemainingSize = maxMaxSize * count - rangeMaxSize; - double sizeToDistribute = requestedSize - rangeMaxSize; - - // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers - Debug.Assert(!double.IsInfinity(totalRemainingSize) - && !double.IsNaN(totalRemainingSize) - && totalRemainingSize > 0 - && !double.IsInfinity(sizeToDistribute) - && !double.IsNaN(sizeToDistribute) - && sizeToDistribute > 0); - - for (int i = 0; i < count; ++i) - { - double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize; - tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize); - } - } - else - { - // - // equi-size is greater or equal to maximum of max sizes. - // all definitions receive equalSize as their mim sizes. - // - for (int i = 0; i < count; ++i) - { - tempDefinitions[i].UpdateMinSize(equalSize); - } - } - } - } - } - } - - // new implementation as of 4.7. Several improvements: - // 1. Allocate to *-defs hitting their min or max constraints, before allocating - // to other *-defs. A def that hits its min uses more space than its - // proportional share, reducing the space available to everyone else. - // The legacy algorithm deducted this space only from defs processed - // after the min; the new algorithm deducts it proportionally from all - // defs. This avoids the "*-defs exceed available space" problem, - // and other related problems where *-defs don't receive proportional - // allocations even though no constraints are preventing it. - // 2. When multiple defs hit min or max, resolve the one with maximum - // discrepancy (defined below). This avoids discontinuities - small - // change in available space resulting in large change to one def's allocation. - // 3. Correct handling of large *-values, including Infinity. - - /// - /// Resolves Star's for given array of definitions. - /// - /// Array of definitions to resolve stars. - /// All available size. - /// - /// Must initialize LayoutSize for all Star entries in given array of definitions. - /// - private void ResolveStar( - DefinitionBase[] definitions, - double availableSize) - { - int defCount = definitions.Length; - DefinitionBase[] tempDefinitions = TempDefinitions; - int minCount = 0, maxCount = 0; - double takenSize = 0; - double totalStarWeight = 0.0; - int starCount = 0; // number of unresolved *-definitions - double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" - - // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights - double maxStar = 0.0; - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.SizeType == LayoutTimeSizeType.Star) - { - ++starCount; - def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3" - if (def.UserSize.Value > maxStar) - { - maxStar = def.UserSize.Value; - } - } - } - - if (double.IsPositiveInfinity(maxStar)) - { - // negative scale means one or more of the weights was Infinity - scale = -1.0; - } - else if (starCount > 0) - { - // if maxStar * starCount > double.Max, summing all the weights could cause - // floating-point overflow. To avoid that, scale the weights by a factor to keep - // the sum within limits. Choose a power of 2, to preserve precision. - double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0)); - if (power < 0.0) - { - scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia - } - } - - // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights - // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. - // More explanation below... - for (bool runPhase2and3 = true; runPhase2and3;) - { - // Phase 2. Compute total *-weight W and available space S. - // For *-items that have Min or Max constraints, compute the ratios used to decide - // whether proportional space is too big or too small and add the item to the - // corresponding list. (The "min" list is in the first half of tempDefinitions, - // the "max" list in the second half. TempDefinitions has capacity at least - // 2*defCount, so there's room for both lists.) - totalStarWeight = 0.0; - takenSize = 0.0; - minCount = maxCount = 0; - - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - switch (def.SizeType) - { - case (LayoutTimeSizeType.Auto): - takenSize += definitions[i].MinSize; - break; - case (LayoutTimeSizeType.Pixel): - takenSize += def.MeasureSize; - break; - case (LayoutTimeSizeType.Star): - if (def.MeasureSize < 0.0) - { - takenSize += -def.MeasureSize; // already resolved - } - else - { - double starWeight = StarWeight(def, scale); - totalStarWeight += starWeight; - - if (def.MinSize > 0.0) - { - // store ratio w/min in MeasureSize (for now) - tempDefinitions[minCount++] = def; - def.MeasureSize = starWeight / def.MinSize; - } - - double effectiveMaxSize = Math.Max(def.MinSize, def.UserMaxSize); - if (!double.IsPositiveInfinity(effectiveMaxSize)) - { - // store ratio w/max in SizeCache (for now) - tempDefinitions[defCount + maxCount++] = def; - def.SizeCache = starWeight / effectiveMaxSize; - } - } - break; - } - } - - // Phase 3. Resolve *-items whose proportional sizes are too big or too small. - int minCountPhase2 = minCount, maxCountPhase2 = maxCount; - double takenStarWeight = 0.0; - double remainingAvailableSize = availableSize - takenSize; - double remainingStarWeight = totalStarWeight - takenStarWeight; - Array.Sort(tempDefinitions, 0, minCount, _minRatioComparer); - Array.Sort(tempDefinitions, defCount, maxCount, _maxRatioComparer); - - while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) - { - // the calculation - // remainingStarWeight = totalStarWeight - takenStarWeight - // is subject to catastrophic cancellation if the two terms are nearly equal, - // which leads to meaningless results. Check for that, and recompute from - // the remaining definitions. [This leads to quadratic behavior in really - // pathological cases - but they'd never arise in practice.] - const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate - if (remainingStarWeight < totalStarWeight * starFactor) - { - takenStarWeight = 0.0; - totalStarWeight = 0.0; - - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - if (def.SizeType == LayoutTimeSizeType.Star && def.MeasureSize > 0.0) - { - totalStarWeight += StarWeight(def, scale); - } - } - - remainingStarWeight = totalStarWeight - takenStarWeight; - } - - double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : double.PositiveInfinity; - double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; - - // choose the def with larger ratio to the current proportion ("max discrepancy") - double proportion = remainingStarWeight / remainingAvailableSize; - bool? chooseMin = Choose(minRatio, maxRatio, proportion); - - // if no def was chosen, advance to phase 4; the current proportion doesn't - // conflict with any min or max values. - if (!(chooseMin.HasValue)) - { - break; - } - - // get the chosen definition and its resolved size - DefinitionBase resolvedDef; - double resolvedSize; - if (chooseMin == true) - { - resolvedDef = tempDefinitions[minCount - 1]; - resolvedSize = resolvedDef.MinSize; - --minCount; - } - else - { - resolvedDef = tempDefinitions[defCount + maxCount - 1]; - resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); - --maxCount; - } - - // resolve the chosen def, deduct its contributions from W and S. - // Defs resolved in phase 3 are marked by storing the negative of their resolved - // size in MeasureSize, to distinguish them from a pending def. - takenSize += resolvedSize; - resolvedDef.MeasureSize = -resolvedSize; - takenStarWeight += StarWeight(resolvedDef, scale); - --starCount; - - remainingAvailableSize = availableSize - takenSize; - remainingStarWeight = totalStarWeight - takenStarWeight; - - // advance to the next candidate defs, removing ones that have been resolved. - // Both counts are advanced, as a def might appear in both lists. - while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0) - { - --minCount; - tempDefinitions[minCount] = null; - } - while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0) - { - --maxCount; - tempDefinitions[defCount + maxCount] = null; - } - } - - // decide whether to run Phase2 and Phase3 again. There are 3 cases: - // 1. There is space available, and *-defs remaining. This is the - // normal case - move on to Phase 4 to allocate the remaining - // space proportionally to the remaining *-defs. - // 2. There is space available, but no *-defs. This implies at least one - // def was resolved as 'max', taking less space than its proportion. - // If there are also 'min' defs, reconsider them - we can give - // them more space. If not, all the *-defs are 'max', so there's - // no way to use all the available space. - // 3. We allocated too much space. This implies at least one def was - // resolved as 'min'. If there are also 'max' defs, reconsider - // them, otherwise the over-allocation is an inevitable consequence - // of the given min constraints. - // Note that if we return to Phase2, at least one *-def will have been - // resolved. This guarantees we don't run Phase2+3 infinitely often. - runPhase2and3 = false; - if (starCount == 0 && takenSize < availableSize) - { - // if no *-defs remain and we haven't allocated all the space, reconsider the defs - // resolved as 'min'. Their allocation can be increased to make up the gap. - for (int i = minCount; i < minCountPhase2; ++i) - { - DefinitionBase def = tempDefinitions[i]; - if (def != null) - { - def.MeasureSize = 1.0; // mark as 'not yet resolved' - ++starCount; - runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 - } - } - } - - if (takenSize > availableSize) - { - // if we've allocated too much space, reconsider the defs - // resolved as 'max'. Their allocation can be decreased to make up the gap. - for (int i = maxCount; i < maxCountPhase2; ++i) - { - DefinitionBase def = tempDefinitions[defCount + i]; - if (def != null) - { - def.MeasureSize = 1.0; // mark as 'not yet resolved' - ++starCount; - runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 - } - } - } - } - - // Phase 4. Resolve the remaining defs proportionally. - starCount = 0; - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.SizeType == LayoutTimeSizeType.Star) - { - if (def.MeasureSize < 0.0) - { - // this def was resolved in phase 3 - fix up its measure size - def.MeasureSize = -def.MeasureSize; - } - else - { - // this def needs resolution, add it to the list, sorted by *-weight - tempDefinitions[starCount++] = def; - def.MeasureSize = StarWeight(def, scale); - } - } - } - - if (starCount > 0) - { - Array.Sort(tempDefinitions, 0, starCount, _starWeightComparer); - - // compute the partial sums of *-weight, in increasing order of weight - // for minimal loss of precision. - totalStarWeight = 0.0; - for (int i = 0; i < starCount; ++i) - { - DefinitionBase def = tempDefinitions[i]; - totalStarWeight += def.MeasureSize; - def.SizeCache = totalStarWeight; - } - - // resolve the defs, in decreasing order of weight - for (int i = starCount - 1; i >= 0; --i) - { - DefinitionBase def = tempDefinitions[i]; - double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; - - // min and max should have no effect by now, but just in case... - resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); - resolvedSize = Math.Max(def.MinSize, resolvedSize); - - def.MeasureSize = resolvedSize; - takenSize += resolvedSize; - } - } - } - - /// - /// Calculates desired size for given array of definitions. - /// - /// Array of definitions to use for calculations. - /// Desired size. - private double CalculateDesiredSize( - DefinitionBase[] definitions) - { - double desiredSize = 0; - - for (int i = 0; i < definitions.Length; ++i) - { - desiredSize += definitions[i].MinSize; - } - - return (desiredSize); - } - - /// - /// Calculates and sets final size for all definitions in the given array. - /// - /// Array of definitions to process. - /// Final size to lay out to. - /// True if sizing row definitions, false for columns - private void SetFinalSize( - DefinitionBase[] definitions, - double finalSize, - bool columns) - { - int defCount = definitions.Length; - int[] definitionIndices = DefinitionIndices; - int minCount = 0, maxCount = 0; - double takenSize = 0.0; - double totalStarWeight = 0.0; - int starCount = 0; // number of unresolved *-definitions - double scale = 1.0; // scale factor applied to each *-weight; negative means "Infinity is present" - - // Phase 1. Determine the maximum *-weight and prepare to adjust *-weights - double maxStar = 0.0; - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.UserSize.IsStar) - { - ++starCount; - def.MeasureSize = 1.0; // meaning "not yet resolved in phase 3" - if (def.UserSize.Value > maxStar) - { - maxStar = def.UserSize.Value; - } - } - } - - if (double.IsPositiveInfinity(maxStar)) - { - // negative scale means one or more of the weights was Infinity - scale = -1.0; - } - else if (starCount > 0) - { - // if maxStar * starCount > double.Max, summing all the weights could cause - // floating-point overflow. To avoid that, scale the weights by a factor to keep - // the sum within limits. Choose a power of 2, to preserve precision. - double power = Math.Floor(Math.Log(double.MaxValue / maxStar / starCount, 2.0)); - if (power < 0.0) - { - scale = Math.Pow(2.0, power - 4.0); // -4 is just for paranoia - } - } - - - // normally Phases 2 and 3 execute only once. But certain unusual combinations of weights - // and constraints can defeat the algorithm, in which case we repeat Phases 2 and 3. - // More explanation below... - for (bool runPhase2and3 = true; runPhase2and3;) - { - // Phase 2. Compute total *-weight W and available space S. - // For *-items that have Min or Max constraints, compute the ratios used to decide - // whether proportional space is too big or too small and add the item to the - // corresponding list. (The "min" list is in the first half of definitionIndices, - // the "max" list in the second half. DefinitionIndices has capacity at least - // 2*defCount, so there's room for both lists.) - totalStarWeight = 0.0; - takenSize = 0.0; - minCount = maxCount = 0; - - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.UserSize.IsStar) - { - Debug.Assert(!def.IsShared, "*-defs cannot be shared"); - - if (def.MeasureSize < 0.0) - { - takenSize += -def.MeasureSize; // already resolved - } - else - { - double starWeight = StarWeight(def, scale); - totalStarWeight += starWeight; - - if (def.MinSizeForArrange > 0.0) - { - // store ratio w/min in MeasureSize (for now) - definitionIndices[minCount++] = i; - def.MeasureSize = starWeight / def.MinSizeForArrange; - } - - double effectiveMaxSize = Math.Max(def.MinSizeForArrange, def.UserMaxSize); - if (!double.IsPositiveInfinity(effectiveMaxSize)) - { - // store ratio w/max in SizeCache (for now) - definitionIndices[defCount + maxCount++] = i; - def.SizeCache = starWeight / effectiveMaxSize; - } - } - } - else - { - double userSize = 0; - - switch (def.UserSize.GridUnitType) - { - case (GridUnitType.Pixel): - userSize = def.UserSize.Value; - break; - - case (GridUnitType.Auto): - userSize = def.MinSizeForArrange; - break; - } - - double userMaxSize; - - if (def.IsShared) - { - // overriding userMaxSize effectively prevents squishy-ness. - // this is a "solution" to avoid shared definitions from been sized to - // different final size at arrange time, if / when different grids receive - // different final sizes. - userMaxSize = userSize; - } - else - { - userMaxSize = def.UserMaxSize; - } - - def.SizeCache = Math.Max(def.MinSizeForArrange, Math.Min(userSize, userMaxSize)); - takenSize += def.SizeCache; - } - } - - // Phase 3. Resolve *-items whose proportional sizes are too big or too small. - int minCountPhase2 = minCount, maxCountPhase2 = maxCount; - double takenStarWeight = 0.0; - double remainingAvailableSize = finalSize - takenSize; - double remainingStarWeight = totalStarWeight - takenStarWeight; - - MinRatioIndexComparer minRatioIndexComparer = new MinRatioIndexComparer(definitions); - Array.Sort(definitionIndices, 0, minCount, minRatioIndexComparer); - MaxRatioIndexComparer maxRatioIndexComparer = new MaxRatioIndexComparer(definitions); - Array.Sort(definitionIndices, defCount, maxCount, maxRatioIndexComparer); - - while (minCount + maxCount > 0 && remainingAvailableSize > 0.0) - { - // the calculation - // remainingStarWeight = totalStarWeight - takenStarWeight - // is subject to catastrophic cancellation if the two terms are nearly equal, - // which leads to meaningless results. Check for that, and recompute from - // the remaining definitions. [This leads to quadratic behavior in really - // pathological cases - but they'd never arise in practice.] - const double starFactor = 1.0 / 256.0; // lose more than 8 bits of precision -> recalculate - if (remainingStarWeight < totalStarWeight * starFactor) - { - takenStarWeight = 0.0; - totalStarWeight = 0.0; - - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - if (def.UserSize.IsStar && def.MeasureSize > 0.0) - { - totalStarWeight += StarWeight(def, scale); - } - } - - remainingStarWeight = totalStarWeight - takenStarWeight; - } - - double minRatio = (minCount > 0) ? definitions[definitionIndices[minCount - 1]].MeasureSize : double.PositiveInfinity; - double maxRatio = (maxCount > 0) ? definitions[definitionIndices[defCount + maxCount - 1]].SizeCache : -1.0; - - // choose the def with larger ratio to the current proportion ("max discrepancy") - double proportion = remainingStarWeight / remainingAvailableSize; - bool? chooseMin = Choose(minRatio, maxRatio, proportion); - - // if no def was chosen, advance to phase 4; the current proportion doesn't - // conflict with any min or max values. - if (!(chooseMin.HasValue)) - { - break; - } - - // get the chosen definition and its resolved size - int resolvedIndex; - DefinitionBase resolvedDef; - double resolvedSize; - if (chooseMin == true) - { - resolvedIndex = definitionIndices[minCount - 1]; - resolvedDef = definitions[resolvedIndex]; - resolvedSize = resolvedDef.MinSizeForArrange; - --minCount; - } - else - { - resolvedIndex = definitionIndices[defCount + maxCount - 1]; - resolvedDef = definitions[resolvedIndex]; - resolvedSize = Math.Max(resolvedDef.MinSizeForArrange, resolvedDef.UserMaxSize); - --maxCount; - } - - // resolve the chosen def, deduct its contributions from W and S. - // Defs resolved in phase 3 are marked by storing the negative of their resolved - // size in MeasureSize, to distinguish them from a pending def. - takenSize += resolvedSize; - resolvedDef.MeasureSize = -resolvedSize; - takenStarWeight += StarWeight(resolvedDef, scale); - --starCount; - - remainingAvailableSize = finalSize - takenSize; - remainingStarWeight = totalStarWeight - takenStarWeight; - - // advance to the next candidate defs, removing ones that have been resolved. - // Both counts are advanced, as a def might appear in both lists. - while (minCount > 0 && definitions[definitionIndices[minCount - 1]].MeasureSize < 0.0) - { - --minCount; - definitionIndices[minCount] = -1; - } - while (maxCount > 0 && definitions[definitionIndices[defCount + maxCount - 1]].MeasureSize < 0.0) - { - --maxCount; - definitionIndices[defCount + maxCount] = -1; - } - } - - // decide whether to run Phase2 and Phase3 again. There are 3 cases: - // 1. There is space available, and *-defs remaining. This is the - // normal case - move on to Phase 4 to allocate the remaining - // space proportionally to the remaining *-defs. - // 2. There is space available, but no *-defs. This implies at least one - // def was resolved as 'max', taking less space than its proportion. - // If there are also 'min' defs, reconsider them - we can give - // them more space. If not, all the *-defs are 'max', so there's - // no way to use all the available space. - // 3. We allocated too much space. This implies at least one def was - // resolved as 'min'. If there are also 'max' defs, reconsider - // them, otherwise the over-allocation is an inevitable consequence - // of the given min constraints. - // Note that if we return to Phase2, at least one *-def will have been - // resolved. This guarantees we don't run Phase2+3 infinitely often. - runPhase2and3 = false; - if (starCount == 0 && takenSize < finalSize) - { - // if no *-defs remain and we haven't allocated all the space, reconsider the defs - // resolved as 'min'. Their allocation can be increased to make up the gap. - for (int i = minCount; i < minCountPhase2; ++i) - { - if (definitionIndices[i] >= 0) - { - DefinitionBase def = definitions[definitionIndices[i]]; - def.MeasureSize = 1.0; // mark as 'not yet resolved' - ++starCount; - runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 - } - } - } - - if (takenSize > finalSize) - { - // if we've allocated too much space, reconsider the defs - // resolved as 'max'. Their allocation can be decreased to make up the gap. - for (int i = maxCount; i < maxCountPhase2; ++i) - { - if (definitionIndices[defCount + i] >= 0) - { - DefinitionBase def = definitions[definitionIndices[defCount + i]]; - def.MeasureSize = 1.0; // mark as 'not yet resolved' - ++starCount; - runPhase2and3 = true; // found a candidate, so re-run Phases 2 and 3 - } - } - } - } - - // Phase 4. Resolve the remaining defs proportionally. - starCount = 0; - for (int i = 0; i < defCount; ++i) - { - DefinitionBase def = definitions[i]; - - if (def.UserSize.IsStar) - { - if (def.MeasureSize < 0.0) - { - // this def was resolved in phase 3 - fix up its size - def.SizeCache = -def.MeasureSize; - } - else - { - // this def needs resolution, add it to the list, sorted by *-weight - definitionIndices[starCount++] = i; - def.MeasureSize = StarWeight(def, scale); - } - } - } - - if (starCount > 0) - { - StarWeightIndexComparer starWeightIndexComparer = new StarWeightIndexComparer(definitions); - Array.Sort(definitionIndices, 0, starCount, starWeightIndexComparer); - - // compute the partial sums of *-weight, in increasing order of weight - // for minimal loss of precision. - totalStarWeight = 0.0; - for (int i = 0; i < starCount; ++i) - { - DefinitionBase def = definitions[definitionIndices[i]]; - totalStarWeight += def.MeasureSize; - def.SizeCache = totalStarWeight; - } - - // resolve the defs, in decreasing order of weight. - for (int i = starCount - 1; i >= 0; --i) - { - DefinitionBase def = definitions[definitionIndices[i]]; - double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(finalSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; - - // min and max should have no effect by now, but just in case... - resolvedSize = Math.Min(resolvedSize, def.UserMaxSize); - resolvedSize = Math.Max(def.MinSizeForArrange, resolvedSize); - - // Use the raw (unrounded) sizes to update takenSize, so that - // proportions are computed in the same terms as in phase 3; - // this avoids errors arising from min/max constraints. - takenSize += resolvedSize; - def.SizeCache = resolvedSize; - } - } - - // Phase 5. Apply layout rounding. We do this after fully allocating - // unrounded sizes, to avoid breaking assumptions in the previous phases - if (UseLayoutRounding) - { - var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; - - double[] roundingErrors = RoundingErrors; - double roundedTakenSize = 0.0; - - // round each of the allocated sizes, keeping track of the deltas - for (int i = 0; i < definitions.Length; ++i) - { - DefinitionBase def = definitions[i]; - double roundedSize = RoundLayoutValue(def.SizeCache, dpi); - roundingErrors[i] = (roundedSize - def.SizeCache); - def.SizeCache = roundedSize; - roundedTakenSize += roundedSize; - } - - // The total allocation might differ from finalSize due to rounding - // effects. Tweak the allocations accordingly. - - // Theoretical and historical note. The problem at hand - allocating - // space to columns (or rows) with *-weights, min and max constraints, - // and layout rounding - has a long history. Especially the special - // case of 50 columns with min=1 and available space=435 - allocating - // seats in the U.S. House of Representatives to the 50 states in - // proportion to their population. There are numerous algorithms - // and papers dating back to the 1700's, including the book: - // Balinski, M. and H. Young, Fair Representation, Yale University Press, New Haven, 1982. - // - // One surprising result of all this research is that *any* algorithm - // will suffer from one or more undesirable features such as the - // "population paradox" or the "Alabama paradox", where (to use our terminology) - // increasing the available space by one pixel might actually decrease - // the space allocated to a given column, or increasing the weight of - // a column might decrease its allocation. This is worth knowing - // in case someone complains about this behavior; it's not a bug so - // much as something inherent to the problem. Cite the book mentioned - // above or one of the 100s of references, and resolve as WontFix. - // - // Fortunately, our scenarios tend to have a small number of columns (~10 or fewer) - // each being allocated a large number of pixels (~50 or greater), and - // people don't even notice the kind of 1-pixel anomolies that are - // theoretically inevitable, or don't care if they do. At least they shouldn't - // care - no one should be using the results WPF's grid layout to make - // quantitative decisions; its job is to produce a reasonable display, not - // to allocate seats in Congress. - // - // Our algorithm is more susceptible to paradox than the one currently - // used for Congressional allocation ("Huntington-Hill" algorithm), but - // it is faster to run: O(N log N) vs. O(S * N), where N=number of - // definitions, S = number of available pixels. And it produces - // adequate results in practice, as mentioned above. - // - // To reiterate one point: all this only applies when layout rounding - // is in effect. When fractional sizes are allowed, the algorithm - // behaves as well as possible, subject to the min/max constraints - // and precision of floating-point computation. (However, the resulting - // display is subject to anti-aliasing problems. TANSTAAFL.) - - if (!MathUtilities.AreClose(roundedTakenSize, finalSize)) - { - // Compute deltas - for (int i = 0; i < definitions.Length; ++i) - { - definitionIndices[i] = i; - } - - // Sort rounding errors - RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); - Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); - double adjustedSize = roundedTakenSize; - double dpiIncrement = 1.0 / dpi; - - if (roundedTakenSize > finalSize) - { - int i = definitions.Length - 1; - while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0) - { - DefinitionBase definition = definitions[definitionIndices[i]]; - double final = definition.SizeCache - dpiIncrement; - final = Math.Max(final, definition.MinSizeForArrange); - if (final < definition.SizeCache) - { - adjustedSize -= dpiIncrement; - } - definition.SizeCache = final; - i--; - } - } - else if (roundedTakenSize < finalSize) - { - int i = 0; - while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Length) - { - DefinitionBase definition = definitions[definitionIndices[i]]; - double final = definition.SizeCache + dpiIncrement; - final = Math.Max(final, definition.MinSizeForArrange); - if (final > definition.SizeCache) - { - adjustedSize += dpiIncrement; - } - definition.SizeCache = final; - i++; - } - } - } - } - - // Phase 6. Compute final offsets - definitions[0].FinalOffset = 0.0; - for (int i = 0; i < definitions.Length; ++i) - { - definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; - } - } - - // Choose the ratio with maximum discrepancy from the current proportion. - // Returns: - // true if proportion fails a min constraint but not a max, or - // if the min constraint has higher discrepancy - // false if proportion fails a max constraint but not a min, or - // if the max constraint has higher discrepancy - // null if proportion doesn't fail a min or max constraint - // The discrepancy is the ratio of the proportion to the max- or min-ratio. - // When both ratios hit the constraint, minRatio < proportion < maxRatio, - // and the minRatio has higher discrepancy if - // (proportion / minRatio) > (maxRatio / proportion) - private static bool? Choose(double minRatio, double maxRatio, double proportion) - { - if (minRatio < proportion) - { - if (maxRatio > proportion) - { - // compare proportion/minRatio : maxRatio/proportion, but - // do it carefully to avoid floating-point overflow or underflow - // and divide-by-0. - double minPower = Math.Floor(Math.Log(minRatio, 2.0)); - double maxPower = Math.Floor(Math.Log(maxRatio, 2.0)); - double f = Math.Pow(2.0, Math.Floor((minPower + maxPower) / 2.0)); - if ((proportion / f) * (proportion / f) > (minRatio / f) * (maxRatio / f)) - { - return true; - } - else - { - return false; - } - } - else - { - return true; - } - } - else if (maxRatio > proportion) - { - return false; - } - - return null; - } - - /// - /// Sorts row/column indices by rounding error if layout rounding is applied. - /// - /// Index, rounding error pair - /// Index, rounding error pair - /// 1 if x.Value > y.Value, 0 if equal, -1 otherwise - private static int CompareRoundingErrors(KeyValuePair x, KeyValuePair y) - { - if (x.Value < y.Value) - { - return -1; - } - else if (x.Value > y.Value) - { - return 1; - } - return 0; - } - - /// - /// Calculates final (aka arrange) size for given range. - /// - /// Array of definitions to process. - /// Start of the range. - /// Number of items in the range. - /// Final size. - private double GetFinalSizeForRange( - DefinitionBase[] definitions, - int start, - int count) - { - double size = 0; - int i = start + count - 1; - - do - { - size += definitions[i].SizeCache; - } while (--i >= start); - - return (size); - } - - /// - /// Clears dirty state for the grid and its columns / rows - /// - private void SetValid() - { - if (IsTrivialGrid && _tempDefinitions != null) - { - // TempDefinitions has to be cleared to avoid "memory leaks" - Array.Clear(_tempDefinitions, 0, Math.Max(_definitionsU.Length, _definitionsV.Length)); - _tempDefinitions = null; - } - } - - /// - /// Synchronized ShowGridLines property with the state of the grid's visual collection - /// by adding / removing GridLinesRenderer visual. - /// Returns a reference to GridLinesRenderer visual or null. - /// - private GridLinesRenderer EnsureGridLinesRenderer() - { - // - // synchronize the state - // - if (ShowGridLines && (_gridLinesRenderer == null)) - { - _gridLinesRenderer = new GridLinesRenderer(); - this.VisualChildren.Add(_gridLinesRenderer); - } - - if ((!ShowGridLines) && (_gridLinesRenderer != null)) - { - this.VisualChildren.Remove(_gridLinesRenderer); - _gridLinesRenderer = null; - } - - return (_gridLinesRenderer); - } - - private double RoundLayoutValue(double value, double dpiScale) - { - double newValue; - - // If DPI == 1, don't use DPI-aware rounding. - if (!MathUtilities.AreClose(dpiScale, 1.0)) - { - newValue = Math.Round(value * dpiScale) / dpiScale; - // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. - if (double.IsNaN(newValue) || - double.IsInfinity(newValue) || - MathUtilities.AreClose(newValue, double.MaxValue)) - { - newValue = value; - } - } - else - { - newValue = Math.Round(value); - } - - return newValue; - } - - private static int ValidateColumn(AvaloniaObject o, int value) - { - if (value < 0) - { - throw new ArgumentException("Invalid Grid.Column value."); - } - - return value; - } - - private static int ValidateRow(AvaloniaObject o, int value) - { - if (value < 0) - { - throw new ArgumentException("Invalid Grid.Row value."); - } - - return value; - } - - private static void OnShowGridLinesPropertyChanged(Grid grid, AvaloniaPropertyChangedEventArgs e) - { - if (!grid.IsTrivialGrid) // trivial grid is 1 by 1. there is no grid lines anyway - { - grid.Invalidate(); - } - } - - /// - /// Returns *-weight, adjusted for scale computed during Phase 1 - /// - private static double StarWeight(DefinitionBase def, double scale) - { - if (scale < 0.0) - { - // if one of the *-weights is Infinity, adjust the weights by mapping - // Infinty to 1.0 and everything else to 0.0: the infinite items share the - // available space equally, everyone else gets nothing. - return (double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0; - } - else - { - return def.UserSize.Value * scale; - } - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/GridCellCache.cs b/src/Avalonia.Controls/Grid/GridCellCache.cs deleted file mode 100644 index 81edf72ca5..0000000000 --- a/src/Avalonia.Controls/Grid/GridCellCache.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Avalonia.Controls -{ - /// - /// CellCache stored calculated values of - /// 1. attached cell positioning properties; - /// 2. size type; - /// 3. index of a next cell in the group; - /// - internal struct GridCellCache - { - internal int ColumnIndex; - internal int RowIndex; - internal int ColumnSpan; - internal int RowSpan; - internal LayoutTimeSizeType SizeTypeU; - internal LayoutTimeSizeType SizeTypeV; - internal int Next; - internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } } - internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } } - internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } } - internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/GridLength.cs b/src/Avalonia.Controls/Grid/GridLength.cs deleted file mode 100644 index 02be95b647..0000000000 --- a/src/Avalonia.Controls/Grid/GridLength.cs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Globalization; -using Avalonia.Utilities; - -namespace Avalonia.Controls -{ - /// - /// Defines the valid units for a . - /// - public enum GridUnitType - { - /// - /// The row or column is auto-sized to fit its content. - /// - Auto = 0, - - /// - /// The row or column is sized in device independent pixels. - /// - Pixel = 1, - - /// - /// The row or column is sized as a weighted proportion of available space. - /// - Star = 2, - } - - /// - /// Holds the width or height of a 's column and row definitions. - /// - public struct GridLength : IEquatable - { - private readonly GridUnitType _type; - - private readonly double _value; - - /// - /// Initializes a new instance of the struct. - /// - /// The size of the GridLength in device independent pixels. - public GridLength(double value) - : this(value, GridUnitType.Pixel) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The size of the GridLength. - /// The unit of the GridLength. - public GridLength(double value, GridUnitType type) - { - if (value < 0 || double.IsNaN(value) || double.IsInfinity(value)) - { - throw new ArgumentException("Invalid value", nameof(value)); - } - - if (type < GridUnitType.Auto || type > GridUnitType.Star) - { - throw new ArgumentException("Invalid value", nameof(type)); - } - - _type = type; - _value = value; - } - - /// - /// Gets an instance of that indicates that a row or column should - /// auto-size to fit its content. - /// - public static GridLength Auto => new GridLength(0, GridUnitType.Auto); - - /// - /// Gets the unit of the . - /// - public GridUnitType GridUnitType => _type; - - /// - /// Gets a value that indicates whether the has a of Pixel. - /// - public bool IsAbsolute => _type == GridUnitType.Pixel; - - /// - /// Gets a value that indicates whether the has a of Auto. - /// - public bool IsAuto => _type == GridUnitType.Auto; - - /// - /// Gets a value that indicates whether the has a of Star. - /// - public bool IsStar => _type == GridUnitType.Star; - - /// - /// Gets the length. - /// - public double Value => _value; - - /// - /// Compares two GridLength structures for equality. - /// - /// The first GridLength. - /// The second GridLength. - /// True if the structures are equal, otherwise false. - public static bool operator ==(GridLength a, GridLength b) - { - return (a.IsAuto && b.IsAuto) || (a._value == b._value && a._type == b._type); - } - - /// - /// Compares two GridLength structures for inequality. - /// - /// The first GridLength. - /// The first GridLength. - /// True if the structures are unequal, otherwise false. - public static bool operator !=(GridLength gl1, GridLength gl2) - { - return !(gl1 == gl2); - } - - /// - /// Determines whether the is equal to the specified object. - /// - /// The object with which to test equality. - /// True if the objects are equal, otherwise false. - public override bool Equals(object o) - { - if (o == null) - { - return false; - } - - if (!(o is GridLength)) - { - return false; - } - - return this == (GridLength)o; - } - - /// - /// Compares two GridLength structures for equality. - /// - /// The structure with which to test equality. - /// True if the structures are equal, otherwise false. - public bool Equals(GridLength gridLength) - { - return this == gridLength; - } - - /// - /// Gets a hash code for the GridLength. - /// - /// The hash code. - public override int GetHashCode() - { - return _value.GetHashCode() ^ _type.GetHashCode(); - } - - /// - /// Gets a string representation of the . - /// - /// The string representation. - public override string ToString() - { - if (IsAuto) - { - return "Auto"; - } - - string s = _value.ToString(); - return IsStar ? s + "*" : s; - } - - /// - /// Parses a string to return a . - /// - /// The string. - /// The . - public static GridLength Parse(string s) - { - s = s.ToUpperInvariant(); - - if (s == "AUTO") - { - return Auto; - } - else if (s.EndsWith("*")) - { - var valueString = s.Substring(0, s.Length - 1).Trim(); - var value = valueString.Length > 0 ? double.Parse(valueString, CultureInfo.InvariantCulture) : 1; - return new GridLength(value, GridUnitType.Star); - } - else - { - var value = double.Parse(s, CultureInfo.InvariantCulture); - return new GridLength(value, GridUnitType.Pixel); - } - } - - /// - /// Parses a string to return a collection of s. - /// - /// The string. - /// The . - public static IEnumerable ParseLengths(string s) - { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture)) - { - while (tokenizer.TryReadString(out var item)) - { - yield return Parse(item); - } - } - } - } -} diff --git a/src/Avalonia.Controls/Grid/GridLinesRenderer.cs b/src/Avalonia.Controls/Grid/GridLinesRenderer.cs deleted file mode 100644 index 0f7f5963d2..0000000000 --- a/src/Avalonia.Controls/Grid/GridLinesRenderer.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Avalonia.VisualTree; -using Avalonia.Media; - -namespace Avalonia.Controls -{ - internal class GridLinesRenderer : Control - { - /// - /// Static initialization - /// - static GridLinesRenderer() - { - var dashArray = new List() { _dashLength, _dashLength }; - - var ds1 = new DashStyle(dashArray, 0); - _oddDashPen = new Pen(Brushes.Blue, - _penWidth, - lineCap: PenLineCap.Flat, - dashStyle: ds1); - - var ds2 = new DashStyle(dashArray, _dashLength); - _evenDashPen = new Pen(Brushes.Yellow, - _penWidth, - lineCap: PenLineCap.Flat, - dashStyle: ds2); - } - - /// - /// UpdateRenderBounds. - /// - public override void Render(DrawingContext drawingContext) - { - var grid = this.GetVisualParent(); - - if (grid == null - || !grid.ShowGridLines - || grid.IsTrivialGrid) - { - return; - } - - for (int i = 1; i < grid.ColumnDefinitions.Count; ++i) - { - DrawGridLine( - drawingContext, - grid.ColumnDefinitions[i].FinalOffset, 0.0, - grid.ColumnDefinitions[i].FinalOffset, _lastArrangeSize.Height); - } - - for (int i = 1; i < grid.RowDefinitions.Count; ++i) - { - DrawGridLine( - drawingContext, - 0.0, grid.RowDefinitions[i].FinalOffset, - _lastArrangeSize.Width, grid.RowDefinitions[i].FinalOffset); - } - } - - /// - /// Draw single hi-contrast line. - /// - private static void DrawGridLine( - DrawingContext drawingContext, - double startX, - double startY, - double endX, - double endY) - { - var start = new Point(startX, startY); - var end = new Point(endX, endY); - drawingContext.DrawLine(_oddDashPen, start, end); - drawingContext.DrawLine(_evenDashPen, start, end); - } - - internal void UpdateRenderBounds(Size arrangeSize) - { - _lastArrangeSize = arrangeSize; - this.InvalidateVisual(); - } - - private static Size _lastArrangeSize; - private const double _dashLength = 4.0; // - private const double _penWidth = 1.0; // - private static readonly Pen _oddDashPen; // first pen to draw dash - private static readonly Pen _evenDashPen; // second pen to draw dash - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/GridSpanKey.cs b/src/Avalonia.Controls/Grid/GridSpanKey.cs deleted file mode 100644 index cd48bc1265..0000000000 --- a/src/Avalonia.Controls/Grid/GridSpanKey.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Avalonia.Controls -{ - /// - /// Helper class for representing a key for a span in hashtable. - /// - internal class GridSpanKey - { - /// - /// Constructor. - /// - /// Starting index of the span. - /// Span count. - /// true for columns; false for rows. - internal GridSpanKey(int start, int count, bool u) - { - _start = start; - _count = count; - _u = u; - } - - /// - /// - /// - public override int GetHashCode() - { - int hash = (_start ^ (_count << 2)); - - if (_u) hash &= 0x7ffffff; - else hash |= 0x8000000; - - return (hash); - } - - /// - /// - /// - public override bool Equals(object obj) - { - GridSpanKey sk = obj as GridSpanKey; - return (sk != null - && sk._start == _start - && sk._count == _count - && sk._u == _u); - } - - /// - /// Returns start index of the span. - /// - internal int Start { get { return (_start); } } - - /// - /// Returns span count. - /// - internal int Count { get { return (_count); } } - - /// - /// Returns true if this is a column span. - /// false if this is a row span. - /// - internal bool U { get { return (_u); } } - - private int _start; - private int _count; - private bool _u; - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/GridSplitter.cs b/src/Avalonia.Controls/Grid/GridSplitter.cs deleted file mode 100644 index 304a760216..0000000000 --- a/src/Avalonia.Controls/Grid/GridSplitter.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Avalonia.Controls.Primitives; -using Avalonia.Input; -using Avalonia.VisualTree; - -namespace Avalonia.Controls -{ - /// - /// Represents the control that redistributes space between columns or rows of a Grid control. - /// - /// - /// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext. - /// - public class GridSplitter : Thumb - { - private List _definitions; - - private Grid _grid; - - private DefinitionBase _nextDefinition; - - private Orientation _orientation; - - private DefinitionBase _prevDefinition; - - private void GetDeltaConstraints(out double min, out double max) - { - var prevDefinitionLen = GetActualLength(_prevDefinition); - var prevDefinitionMin = GetMinLength(_prevDefinition); - var prevDefinitionMax = GetMaxLength(_prevDefinition); - - var nextDefinitionLen = GetActualLength(_nextDefinition); - var nextDefinitionMin = GetMinLength(_nextDefinition); - var nextDefinitionMax = GetMaxLength(_nextDefinition); - // Determine the minimum and maximum the columns can be resized - min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen); - max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin); - } - - protected override void OnDragDelta(VectorEventArgs e) - { - // WPF doesn't change anything when spliter is in the last row/column - // but resizes the splitter row/column when it's the first one. - // this is different, but more internally consistent. - if (_prevDefinition == null || _nextDefinition == null) - return; - - var delta = _orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y; - double max; - double min; - GetDeltaConstraints(out min, out max); - delta = Math.Min(Math.Max(delta, min), max); - - var prevIsStar = IsStar(_prevDefinition); - var nextIsStar = IsStar(_nextDefinition); - - if (prevIsStar && nextIsStar) - { - foreach (var definition in _definitions) - { - if (definition == _prevDefinition) - { - SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta); - } - else if (definition == _nextDefinition) - { - SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta); - } - else if (IsStar(definition)) - { - SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars. - } - } - } - else if (prevIsStar) - { - SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta); - } - else if (nextIsStar) - { - SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta); - } - else - { - SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta); - SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta); - } - } - - private double GetActualLength(DefinitionBase definition) - { - if (definition == null) - return 0; - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; - } - - private double GetMinLength(DefinitionBase definition) - { - if (definition == null) - return 0; - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight; - } - - private double GetMaxLength(DefinitionBase definition) - { - if (definition == null) - return 0; - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight; - } - - private bool IsStar(DefinitionBase definition) - { - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar; - } - - private void SetLengthInStars(DefinitionBase definition, double value) - { - var columnDefinition = definition as ColumnDefinition; - if (columnDefinition != null) - { - columnDefinition.Width = new GridLength(value, GridUnitType.Star); - } - else - { - ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star); - } - } - - private void SetLength(DefinitionBase definition, double value) - { - var columnDefinition = definition as ColumnDefinition; - if (columnDefinition != null) - { - columnDefinition.Width = new GridLength(value); - } - else - { - ((RowDefinition)definition).Height = new GridLength(value); - } - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - _grid = this.GetVisualParent(); - - _orientation = DetectOrientation(); - - int definitionIndex; //row or col - if (_orientation == Orientation.Vertical) - { - Cursor = new Cursor(StandardCursorType.SizeWestEast); - _definitions = _grid.ColumnDefinitions.Cast().ToList(); - definitionIndex = GetValue(Grid.ColumnProperty); - PseudoClasses.Add(":vertical"); - } - else - { - Cursor = new Cursor(StandardCursorType.SizeNorthSouth); - definitionIndex = GetValue(Grid.RowProperty); - _definitions = _grid.RowDefinitions.Cast().ToList(); - PseudoClasses.Add(":horizontal"); - } - - if (definitionIndex > 0) - _prevDefinition = _definitions[definitionIndex - 1]; - - if (definitionIndex < _definitions.Count - 1) - _nextDefinition = _definitions[definitionIndex + 1]; - } - - private Orientation DetectOrientation() - { - if (!_grid.ColumnDefinitions.Any()) - return Orientation.Horizontal; - if (!_grid.RowDefinitions.Any()) - return Orientation.Vertical; - - var col = GetValue(Grid.ColumnProperty); - var row = GetValue(Grid.RowProperty); - var width = _grid.ColumnDefinitions[col].Width; - var height = _grid.RowDefinitions[row].Height; - if (width.IsAuto && !height.IsAuto) - { - return Orientation.Vertical; - } - if (!width.IsAuto && height.IsAuto) - { - return Orientation.Horizontal; - } - if (_grid.Children.OfType() // Decision based on other controls in the same column - .Where(c => Grid.GetColumn(c) == col) - .Any(c => c.GetType() != typeof(GridSplitter))) - { - return Orientation.Horizontal; - } - return Orientation.Vertical; - } - } -} diff --git a/src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs b/src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs deleted file mode 100644 index 1432c29ae5..0000000000 --- a/src/Avalonia.Controls/Grid/LayoutTimeSizeType.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Avalonia.Controls -{ - /// - /// LayoutTimeSizeType is used internally and reflects layout-time size type. - /// - [System.Flags] - internal enum LayoutTimeSizeType : byte - { - None = 0x00, - Pixel = 0x01, - Auto = 0x02, - Star = 0x04, - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/MaxRatioComparer.cs b/src/Avalonia.Controls/Grid/MaxRatioComparer.cs deleted file mode 100644 index fe7eb356ec..0000000000 --- a/src/Avalonia.Controls/Grid/MaxRatioComparer.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; - -namespace Avalonia.Controls -{ - - /// - /// MaxRatioComparer. - /// Sort by w/max (stored in SizeCache), ascending. - /// We query the list from the back, i.e. in descending order of w/max. - /// - internal class MaxRatioComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs b/src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs deleted file mode 100644 index 01bcf85b27..0000000000 --- a/src/Avalonia.Controls/Grid/MaxRatioIndexComparer.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections; - -namespace Avalonia.Controls -{ - internal class MaxRatioIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal MaxRatioIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/MinRatioComparer.cs b/src/Avalonia.Controls/Grid/MinRatioComparer.cs deleted file mode 100644 index 8e0fa0a282..0000000000 --- a/src/Avalonia.Controls/Grid/MinRatioComparer.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; - -namespace Avalonia.Controls -{ - /// - /// MinRatioComparer. - /// Sort by w/min (stored in MeasureSize), descending. - /// We query the list from the back, i.e. in ascending order of w/min. - /// - internal class MinRatioComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!Grid.CompareNullRefs(definitionY, definitionX, out result)) - { - result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs b/src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs deleted file mode 100644 index 01add324c1..0000000000 --- a/src/Avalonia.Controls/Grid/MinRatioIndexComparer.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections; - -namespace Avalonia.Controls -{ - internal class MinRatioIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal MinRatioIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!Grid.CompareNullRefs(definitionY, definitionX, out result)) - { - result = definitionY.MeasureSize.CompareTo(definitionX.MeasureSize); - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs b/src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs deleted file mode 100644 index a0a9035384..0000000000 --- a/src/Avalonia.Controls/Grid/RoundingErrorIndexComparer.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections; - -namespace Avalonia.Controls -{ - internal class RoundingErrorIndexComparer : IComparer - { - private readonly double[] errors; - - internal RoundingErrorIndexComparer(double[] errors) - { - Contract.Requires(errors != null); - this.errors = errors; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - int result; - - if (!Grid.CompareNullRefs(indexX, indexY, out result)) - { - double errorX = errors[indexX.Value]; - double errorY = errors[indexY.Value]; - result = errorX.CompareTo(errorY); - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SharedSizeScope.cs b/src/Avalonia.Controls/Grid/SharedSizeScope.cs deleted file mode 100644 index 6835d13132..0000000000 --- a/src/Avalonia.Controls/Grid/SharedSizeScope.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; -using System.Diagnostics; - -namespace Avalonia.Controls -{ - /// - /// Collection of shared states objects for a single scope - /// - internal class SharedSizeScope - { - /// - /// Returns SharedSizeState object for a given group. - /// Creates a new StatedState object if necessary. - /// - internal SharedSizeState EnsureSharedState(string sharedSizeGroup) - { - // check that sharedSizeGroup is not default - Debug.Assert(sharedSizeGroup != null); - - SharedSizeState sharedState = _registry[sharedSizeGroup] as SharedSizeState; - if (sharedState == null) - { - sharedState = new SharedSizeState(this, sharedSizeGroup); - _registry[sharedSizeGroup] = sharedState; - } - return (sharedState); - } - - /// - /// Removes an entry in the registry by the given key. - /// - internal void Remove(object key) - { - Debug.Assert(_registry.Contains(key)); - _registry.Remove(key); - } - - private Hashtable _registry = new Hashtable(); // storage for shared state objects - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SharedSizeState.cs b/src/Avalonia.Controls/Grid/SharedSizeState.cs deleted file mode 100644 index 2b99c09861..0000000000 --- a/src/Avalonia.Controls/Grid/SharedSizeState.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Avalonia.Utilities; - -namespace Avalonia.Controls -{ - /// - /// Implementation of per shared group state object - /// - internal class SharedSizeState - { - private readonly SharedSizeScope _sharedSizeScope; // the scope this state belongs to - private readonly string _sharedSizeGroupId; // Id of the shared size group this object is servicing - private readonly List _registry; // registry of participating definitions - private readonly EventHandler _layoutUpdated; // instance event handler for layout updated event - private Control _layoutUpdatedHost; // Control for which layout updated event handler is registered - private bool _broadcastInvalidation; // "true" when broadcasting of invalidation is needed - private bool _userSizeValid; // "true" when _userSize is up to date - private GridLength _userSize; // shared state - private double _minSize; // shared state - - /// - /// Default ctor. - /// - internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) - { - Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null); - _sharedSizeScope = sharedSizeScope; - _sharedSizeGroupId = sharedSizeGroupId; - _registry = new List(); - _layoutUpdated = new EventHandler(OnLayoutUpdated); - _broadcastInvalidation = true; - } - - /// - /// Adds / registers a definition instance. - /// - internal void AddMember(DefinitionBase member) - { - Debug.Assert(!_registry.Contains(member)); - _registry.Add(member); - Invalidate(); - } - - /// - /// Removes / un-registers a definition instance. - /// - /// - /// If the collection of registered definitions becomes empty - /// instantiates self removal from owner's collection. - /// - internal void RemoveMember(DefinitionBase member) - { - Invalidate(); - _registry.Remove(member); - - if (_registry.Count == 0) - { - _sharedSizeScope.Remove(_sharedSizeGroupId); - } - } - - /// - /// Propogates invalidations for all registered definitions. - /// Resets its own state. - /// - internal void Invalidate() - { - _userSizeValid = false; - - if (_broadcastInvalidation) - { - for (int i = 0, count = _registry.Count; i < count; ++i) - { - Grid parentGrid = (Grid)(_registry[i].Parent); - parentGrid.Invalidate(); - } - _broadcastInvalidation = false; - } - } - - /// - /// Makes sure that one and only one layout updated handler is registered for this shared state. - /// - internal void EnsureDeferredValidation(Control layoutUpdatedHost) - { - if (_layoutUpdatedHost == null) - { - _layoutUpdatedHost = layoutUpdatedHost; - _layoutUpdatedHost.LayoutUpdated += _layoutUpdated; - } - } - - /// - /// DefinitionBase's specific code. - /// - internal double MinSize - { - get - { - if (!_userSizeValid) { EnsureUserSizeValid(); } - return (_minSize); - } - } - - /// - /// DefinitionBase's specific code. - /// - internal GridLength UserSize - { - get - { - if (!_userSizeValid) { EnsureUserSizeValid(); } - return (_userSize); - } - } - - private void EnsureUserSizeValid() - { - _userSize = new GridLength(1, GridUnitType.Auto); - - for (int i = 0, count = _registry.Count; i < count; ++i) - { - Debug.Assert(_userSize.GridUnitType == GridUnitType.Auto - || _userSize.GridUnitType == GridUnitType.Pixel); - - GridLength currentGridLength = _registry[i].UserSizeValueCache; - if (currentGridLength.GridUnitType == GridUnitType.Pixel) - { - if (_userSize.GridUnitType == GridUnitType.Auto) - { - _userSize = currentGridLength; - } - else if (_userSize.Value < currentGridLength.Value) - { - _userSize = currentGridLength; - } - } - } - // taking maximum with user size effectively prevents squishy-ness. - // this is a "solution" to avoid shared definitions from been sized to - // different final size at arrange time, if / when different grids receive - // different final sizes. - _minSize = _userSize.IsAbsolute ? _userSize.Value : 0.0; - - _userSizeValid = true; - } - - /// - /// OnLayoutUpdated handler. Validates that all participating definitions - /// have updated min size value. Forces another layout update cycle if needed. - /// - private void OnLayoutUpdated(object sender, EventArgs e) - { - double sharedMinSize = 0; - - // accumulate min size of all participating definitions - for (int i = 0, count = _registry.Count; i < count; ++i) - { - sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); - } - - bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); - - // compare accumulated min size with min sizes of the individual definitions - for (int i = 0, count = _registry.Count; i < count; ++i) - { - DefinitionBase definitionBase = _registry[i]; - - if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) - { - // if definition's min size is different, then need to re-measure - if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) - { - Grid parentGrid = (Grid)definitionBase.Parent; - parentGrid.InvalidateMeasure(); - definitionBase.UseSharedMinimum = true; - } - else - { - definitionBase.UseSharedMinimum = false; - - // if measure is valid then also need to check arrange. - // Note: definitionBase.SizeCache is volatile but at this point - // it contains up-to-date final size - if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) - { - Grid parentGrid = (Grid)definitionBase.Parent; - parentGrid.InvalidateArrange(); - } - } - - definitionBase.LayoutWasUpdated = false; - } - } - - _minSize = sharedMinSize; - - _layoutUpdatedHost.LayoutUpdated -= _layoutUpdated; - _layoutUpdatedHost = null; - - _broadcastInvalidation = true; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs b/src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs deleted file mode 100644 index f6fbf4d2bb..0000000000 --- a/src/Avalonia.Controls/Grid/SpanMaxDistributionOrderComparer.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; - -namespace Avalonia.Controls -{ - internal class SpanMaxDistributionOrderComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) - { - if (definitionX.UserSize.IsAuto) - { - if (definitionY.UserSize.IsAuto) - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - else - { - result = +1; - } - } - else - { - if (definitionY.UserSize.IsAuto) - { - result = -1; - } - else - { - result = definitionX.SizeCache.CompareTo(definitionY.SizeCache); - } - } - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs b/src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs deleted file mode 100644 index 1adb62590c..0000000000 --- a/src/Avalonia.Controls/Grid/SpanPreferredDistributionOrderComparer.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; - -namespace Avalonia.Controls -{ - internal class SpanPreferredDistributionOrderComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) - { - if (definitionX.UserSize.IsAuto) - { - if (definitionY.UserSize.IsAuto) - { - result = definitionX.MinSize.CompareTo(definitionY.MinSize); - } - else - { - result = -1; - } - } - else - { - if (definitionY.UserSize.IsAuto) - { - result = +1; - } - else - { - result = definitionX.PreferredSize.CompareTo(definitionY.PreferredSize); - } - } - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/StarWeightComparer.cs b/src/Avalonia.Controls/Grid/StarWeightComparer.cs deleted file mode 100644 index 216f97f2c1..0000000000 --- a/src/Avalonia.Controls/Grid/StarWeightComparer.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; - -namespace Avalonia.Controls -{ - /// - /// StarWeightComparer. - /// Sort by *-weight (stored in MeasureSize), ascending. - /// - internal class StarWeightComparer : IComparer - { - public int Compare(object x, object y) - { - DefinitionBase definitionX = x as DefinitionBase; - DefinitionBase definitionY = y as DefinitionBase; - - int result; - - if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs b/src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs deleted file mode 100644 index da5148e9a5..0000000000 --- a/src/Avalonia.Controls/Grid/StarWeightIndexComparer.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections; - -namespace Avalonia.Controls -{ - - internal class StarWeightIndexComparer : IComparer - { - private readonly DefinitionBase[] definitions; - - internal StarWeightIndexComparer(DefinitionBase[] definitions) - { - Contract.Requires(definitions != null); - this.definitions = definitions; - } - - public int Compare(object x, object y) - { - int? indexX = x as int?; - int? indexY = y as int?; - - DefinitionBase definitionX = null; - DefinitionBase definitionY = null; - - if (indexX != null) - { - definitionX = definitions[indexX.Value]; - } - if (indexY != null) - { - definitionY = definitions[indexY.Value]; - } - - int result; - - if (!Grid.CompareNullRefs(definitionX, definitionY, out result)) - { - result = definitionX.MeasureSize.CompareTo(definitionY.MeasureSize); - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs similarity index 93% rename from src/Avalonia.Controls/Grid/RowDefinition.cs rename to src/Avalonia.Controls/RowDefinition.cs index 1cb09e16e9..8e6ab3ae36 100644 --- a/src/Avalonia.Controls/Grid/RowDefinition.cs +++ b/src/Avalonia.Controls/RowDefinition.cs @@ -87,12 +87,5 @@ namespace Avalonia.Controls get { return GetValue(HeightProperty); } set { SetValue(HeightProperty, value); } } - - - internal override GridLength UserSizeValueCache => this.Height; - - internal override double UserMinSizeValueCache => this.MinHeight; - - internal override double UserMaxSizeValueCache => this.MaxHeight; } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid/RowDefinitions.cs b/src/Avalonia.Controls/RowDefinitions.cs similarity index 100% rename from src/Avalonia.Controls/Grid/RowDefinitions.cs rename to src/Avalonia.Controls/RowDefinitions.cs From 1c6814433eb43445824ea0339c3c4175531dd258 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 01:25:54 +0800 Subject: [PATCH 055/130] Trim header comments --- src/Avalonia.Controls/Grid.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index fa310b73ba..5e3e470bb2 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1,17 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// -// Description: Grid implementation. -// -// Specs -// Grid : Grid.mht -// Size Sharing: Size Information Sharing.doc -// -// Misc -// Grid Tutorial: Grid Tutorial.mht -// using MS.Internal; using MS.Internal.Controls; From 21a470b11f667077bc3b4c3fcc88b8d92d38f901 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 01:29:49 +0800 Subject: [PATCH 056/130] Remove pragma and GridParanoia regions and references to that code. --- src/Avalonia.Controls/Grid.cs | 226 ++++------------------------------ 1 file changed, 23 insertions(+), 203 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 5e3e470bb2..ac3aeb7c5c 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. + using MS.Internal; using MS.Internal.Controls; using MS.Internal.PresentationFramework; @@ -20,7 +21,7 @@ using System.Windows.Documents; using System.Windows.Media; using System.Windows.Markup; -#pragma warning disable 1634, 1691 // suppressing PreSharp warnings + namespace System.Windows.Controls { @@ -382,7 +383,7 @@ namespace System.Windows.Controls try { - EnterCounterScope(Counters.MeasureOverride); + ListenToNotifications = true; MeasureOverrideInProgress = true; @@ -658,17 +659,17 @@ namespace System.Windows.Controls MeasureCellsGroup(extData.CellGroup4, constraint, false, false); - EnterCounter(Counters._CalculateDesiredSize); + gridDesiredSize = new Size( CalculateDesiredSize(DefinitionsU), CalculateDesiredSize(DefinitionsV)); - ExitCounter(Counters._CalculateDesiredSize); + } } finally { MeasureOverrideInProgress = false; - ExitCounterScope(Counters.MeasureOverride); + } return (gridDesiredSize); @@ -682,7 +683,7 @@ namespace System.Windows.Controls { try { - EnterCounterScope(Counters.ArrangeOverride); + ArrangeOverrideInProgress = true; @@ -703,12 +704,12 @@ namespace System.Windows.Controls { Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); - EnterCounter(Counters._SetFinalSize); + SetFinalSize(DefinitionsU, arrangeSize.Width, true); SetFinalSize(DefinitionsV, arrangeSize.Height, false); - ExitCounter(Counters._SetFinalSize); + UIElementCollection children = InternalChildren; @@ -731,9 +732,9 @@ namespace System.Windows.Controls GetFinalSizeForRange(DefinitionsU, columnIndex, columnSpan), GetFinalSizeForRange(DefinitionsV, rowIndex, rowSpan) ); - EnterCounter(Counters._ArrangeChildHelper2); + cell.Arrange(cellRect); - ExitCounter(Counters._ArrangeChildHelper2); + } // update render bound on grid lines renderer visual @@ -748,7 +749,7 @@ namespace System.Windows.Controls { SetValid(); ArrangeOverrideInProgress = false; - ExitCounterScope(Counters.ArrangeOverride); + } return (arrangeSize); } @@ -889,7 +890,7 @@ namespace System.Windows.Controls /// private void ValidateCells() { - EnterCounter(Counters._ValidateCells); + if (CellsStructureDirty) { @@ -897,7 +898,7 @@ namespace System.Windows.Controls CellsStructureDirty = false; } - ExitCounter(Counters._ValidateCells); + } /// @@ -1016,7 +1017,7 @@ namespace System.Windows.Controls /// private void ValidateDefinitionsUStructure() { - EnterCounter(Counters._ValidateColsStructure); + if (ColumnDefinitionCollectionDirty) { @@ -1050,7 +1051,7 @@ namespace System.Windows.Controls Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Length > 0); - ExitCounter(Counters._ValidateColsStructure); + } /// @@ -1063,7 +1064,7 @@ namespace System.Windows.Controls /// private void ValidateDefinitionsVStructure() { - EnterCounter(Counters._ValidateRowsStructure); + if (RowDefinitionCollectionDirty) { @@ -1097,7 +1098,7 @@ namespace System.Windows.Controls Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Length > 0); - ExitCounter(Counters._ValidateRowsStructure); + } /// @@ -1338,7 +1339,7 @@ namespace System.Windows.Controls int cell, bool forceInfinityV) { - EnterCounter(Counters._MeasureCell); + double cellMeasureWidth; double cellMeasureHeight; @@ -1380,16 +1381,16 @@ namespace System.Windows.Controls PrivateCells[cell].RowSpan); } - EnterCounter(Counters.__MeasureChild); + UIElement child = InternalChildren[cell]; if (child != null) { Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); child.Measure(childConstraint); } - ExitCounter(Counters.__MeasureChild); + - ExitCounter(Counters._MeasureCell); + } @@ -4227,186 +4228,5 @@ namespace System.Windows.Controls } #endregion Private Structures Classes - - //------------------------------------------------------ - // - // Extended debugging for grid - // - //------------------------------------------------------ - -#if GRIDPARANOIA - private static double _performanceFrequency; - private static readonly bool _performanceFrequencyInitialized = InitializePerformanceFrequency(); - - //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")] - private static extern bool QueryPerformanceCounter(out long lpPerformanceCount); - - //CASRemoval:[System.Security.SuppressUnmanagedCodeSecurity, System.Runtime.InteropServices.DllImport("kernel32.dll")] - private static extern bool QueryPerformanceFrequency(out long lpFrequency); - - private static double CostInMilliseconds(long count) - { - return ((double)count / _performanceFrequency); - } - - private static long Cost(long startCount, long endCount) - { - long l = endCount - startCount; - if (l < 0) { l += long.MaxValue; } - return (l); - } - - private static bool InitializePerformanceFrequency() - { - long l; - QueryPerformanceFrequency(out l); - _performanceFrequency = (double)l * 0.001; - return (true); - } - - private struct Counter - { - internal long Start; - internal long Total; - internal int Calls; - } - - private Counter[] _counters; - private bool _hasNewCounterInfo; -#endif // GRIDPARANOIA - - // - // This property - // 1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject - // 2. This is a performance optimization - // - internal override int EffectiveValuesInitialSize - { - get { return 9; } - } - - [Conditional("GRIDPARANOIA")] - internal void EnterCounterScope(Counters scopeCounter) - { - #if GRIDPARANOIA - if (ID == "CountThis") - { - if (_counters == null) - { - _counters = new Counter[(int)Counters.Count]; - } - ExitCounterScope(Counters.Default); - EnterCounter(scopeCounter); - } - else - { - _counters = null; - } - #endif // GRIDPARANOIA - } - - [Conditional("GRIDPARANOIA")] - internal void ExitCounterScope(Counters scopeCounter) - { - #if GRIDPARANOIA - if (_counters != null) - { - if (scopeCounter != Counters.Default) - { - ExitCounter(scopeCounter); - } - - if (_hasNewCounterInfo) - { - string NFormat = "F6"; - Console.WriteLine( - "\ncounter name | total t (ms) | # of calls | per call t (ms)" - + "\n----------------------+---------------+---------------+----------------------" ); - - for (int i = 0; i < _counters.Length; ++i) - { - if (_counters[i].Calls > 0) - { - Counters counter = (Counters)i; - double total = CostInMilliseconds(_counters[i].Total); - double single = total / _counters[i].Calls; - string counterName = counter.ToString(); - string separator; - - if (counterName.Length < 8) { separator = "\t\t\t"; } - else if (counterName.Length < 16) { separator = "\t\t"; } - else { separator = "\t"; } - - Console.WriteLine( - counter.ToString() + separator - + total.ToString(NFormat) + "\t" - + _counters[i].Calls + "\t\t" - + single.ToString(NFormat)); - - _counters[i] = new Counter(); - } - } - } - _hasNewCounterInfo = false; - } - #endif // GRIDPARANOIA - } - - [Conditional("GRIDPARANOIA")] - internal void EnterCounter(Counters counter) - { - #if GRIDPARANOIA - if (_counters != null) - { - Debug.Assert((int)counter < _counters.Length); - - int i = (int)counter; - QueryPerformanceCounter(out _counters[i].Start); - } - #endif // GRIDPARANOIA - } - - [Conditional("GRIDPARANOIA")] - internal void ExitCounter(Counters counter) - { - #if GRIDPARANOIA - if (_counters != null) - { - Debug.Assert((int)counter < _counters.Length); - - int i = (int)counter; - long l; - QueryPerformanceCounter(out l); - l = Cost(_counters[i].Start, l); - _counters[i].Total += l; - _counters[i].Calls++; - _hasNewCounterInfo = true; - } - #endif // GRIDPARANOIA - } - - internal enum Counters : int - { - Default = -1, - - MeasureOverride, - _ValidateColsStructure, - _ValidateRowsStructure, - _ValidateCells, - _MeasureCell, - __MeasureChild, - _CalculateDesiredSize, - - ArrangeOverride, - _SetFinalSize, - _ArrangeChildHelper2, - _PositionCell, - - Count, - } } -} - - - - +} \ No newline at end of file From 7ee409a7a6a5525920f4c32d3e10898d7169e5e2 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 01:32:47 +0800 Subject: [PATCH 057/130] Simplify usings. --- src/Avalonia.Controls/Grid.cs | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index ac3aeb7c5c..b0d028b913 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1,29 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - -using MS.Internal; -using MS.Internal.Controls; -using MS.Internal.PresentationFramework; -using MS.Internal.Telemetry.PresentationFramework; -using MS.Utility; - using System; using System.Collections; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; -using System.Windows.Threading; -using System.Threading; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; -using System.Windows.Media; -using System.Windows.Markup; - +using Avalonia; +using Avalonia.Collections; -namespace System.Windows.Controls +namespace Avalonia.Controls { /// /// Grid From 228bfbfdf8b8112f58e4771b4ceabeffb5093bff Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 01:34:45 +0800 Subject: [PATCH 058/130] Remove IAddChild and its interface implementations --- src/Avalonia.Controls/Grid.cs | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index b0d028b913..b8c4897287 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// /// Grid /// - public class Grid : Panel, IAddChild + public class Grid : Panel { //------------------------------------------------------ // @@ -26,7 +26,6 @@ namespace Avalonia.Controls static Grid() { - ControlsTraceLogger.AddControl(TelemetryControls.Grid); } /// @@ -47,33 +46,6 @@ namespace Avalonia.Controls #region Public Methods - /// - /// - /// - void IAddChild.AddChild(object value) - { - if (value == null) - { - throw new ArgumentNullException("value"); - } - - UIElement cell = value as UIElement; - if (cell != null) - { - Children.Add(cell); - return; - } - - throw (new ArgumentException(SR.Get(SRID.Grid_UnexpectedParameterType, value.GetType(), typeof(UIElement)), "value")); - } - - /// - /// - /// - void IAddChild.AddText(string text) - { - XamlSerializerUtil.ThrowIfNonWhiteSpaceInAddText(text, this); - } /// /// From c4a306b59ff90e800373d7cdbbcd816c8fa62c9e Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 02:01:43 +0800 Subject: [PATCH 059/130] Replace header legalese with the appropiate one. --- src/Avalonia.Controls/DefinitionBase.cs | 19 ++++--------------- src/Avalonia.Controls/Grid.cs | 6 ++++-- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 4878523a70..8c069b6786 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -1,18 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// -// Specs -// Grid : Grid.mht -// Size Sharing: Size Information Sharing.doc -// -// Misc -// Grid Tutorial: Grid Tutorial.mht -// -// Description: Implementation of base abstract class for ColumnDefinition -// and RowDefinition. -// +// This source file is adapted from the Windows Presentation Foundation project. +// (https://github.com/dotnet/wpf/) +// +// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using MS.Internal; using MS.Internal.KnownBoxes; diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index b8c4897287..b1e7851200 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1,5 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +// This source file is adapted from the Windows Presentation Foundation project. +// (https://github.com/dotnet/wpf/) +// +// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using System; using System.Collections; From f34ba5ac636eec5f13ed25ca2c2a1fbc74c710cc Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 02:02:08 +0800 Subject: [PATCH 060/130] Fix usings and namespace on DefBase --- src/Avalonia.Controls/DefinitionBase.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 8c069b6786..56e2cab298 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -3,18 +3,15 @@ // // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. -using MS.Internal; -using MS.Internal.KnownBoxes; using System; using System.Collections; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; -using System.Security.Permissions; -using System.Windows; -using System.Windows.Threading; -namespace System.Windows.Controls +using Avalonia; +using Avalonia.Collections; + +namespace Avalonia.Controls { /// /// DefinitionBase provides core functionality used internally by Grid From e52a8df10c036afeb756477e1c7161de2a575a74 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 13:07:19 +0800 Subject: [PATCH 061/130] Remove regions and remaining Pragmas + Remove Localization attribute in DefBase. --- src/Avalonia.Controls/DefinitionBase.cs | 1 - src/Avalonia.Controls/Grid.cs | 59 +------------------------ 2 files changed, 1 insertion(+), 59 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 56e2cab298..81c88b2638 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -17,7 +17,6 @@ namespace Avalonia.Controls /// DefinitionBase provides core functionality used internally by Grid /// and ColumnDefinitionCollection / RowDefinitionCollection /// - [Localizability(LocalizationCategory.Ignore)] public abstract class DefinitionBase : FrameworkContentElement { //------------------------------------------------------ diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index b1e7851200..4ba5c324f1 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using Avalonia; using Avalonia.Collections; +using Avalonia.Media; namespace Avalonia.Controls { @@ -24,8 +25,6 @@ namespace Avalonia.Controls // //------------------------------------------------------ - #region Constructors - static Grid() { } @@ -38,16 +37,12 @@ namespace Avalonia.Controls SetFlags((bool) ShowGridLinesProperty.GetDefaultValue(DependencyObjectType), Flags.ShowGridLinesPropertyValue); } - #endregion Constructors - //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ - #region Public Methods - /// /// @@ -232,16 +227,12 @@ namespace Avalonia.Controls return ((bool)element.GetValue(IsSharedSizeScopeProperty)); } - #endregion Public Methods - //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ - #region Public Properties - /// /// ShowGridLines property. /// @@ -281,16 +272,12 @@ namespace Avalonia.Controls } } - #endregion Public Properties - //------------------------------------------------------ // // Protected Methods // //------------------------------------------------------ - #region Protected Methods - /// /// Derived class must implement to support Visual children. The method must return /// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1. @@ -726,16 +713,12 @@ namespace Avalonia.Controls base.OnVisualChildrenChanged(visualAdded, visualRemoved); } - #endregion Protected Methods - //------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ - #region Internal Methods - /// /// Invalidates grid caches and makes the grid dirty for measure. /// @@ -789,16 +772,12 @@ namespace Avalonia.Controls return (value); } - #endregion Internal Methods - //------------------------------------------------------ // // Internal Properties // //------------------------------------------------------ - #region Internal Properties - /// /// Convenience accessor to MeasureOverrideInProgress bit flag. /// @@ -835,16 +814,12 @@ namespace Avalonia.Controls set { SetFlags(!value, Flags.ValidDefinitionsVStructure); } } - #endregion Internal Properties - //------------------------------------------------------ // // Private Methods // //------------------------------------------------------ - #region Private Methods - /// /// Lays out cells according to rows and columns, and creates lookup grids. /// @@ -3021,16 +2996,12 @@ namespace Avalonia.Controls return (result != 2); } - #endregion Private Methods - //------------------------------------------------------ // // Private Properties // //------------------------------------------------------ - #region Private Properties - /// /// Private version returning array of column definitions. /// @@ -3238,15 +3209,11 @@ namespace Avalonia.Controls } } - #endregion Private Properties - //------------------------------------------------------ // // Private Fields // //------------------------------------------------------ - - #region Private Fields private ExtendedData _data; // extended data instantiated on demand, for non-trivial case handling only private Flags _flags; // grid validity / property caches dirtiness flags private GridLinesRenderer _gridLinesRenderer; @@ -3257,15 +3224,11 @@ namespace Avalonia.Controls // Stores unrounded values and rounding errors during layout rounding. double[] _roundingErrors; - #endregion Private Fields - //------------------------------------------------------ // // Static Fields // //------------------------------------------------------ - - #region Static Fields private const double c_epsilon = 1e-5; // used in fp calculations private const double c_starClip = 1e298; // used as maximum for clipping star values during normalization private const int c_layoutLoopMaxCount = 5; // 5 is an arbitrary constant chosen to end the measure loop @@ -3278,16 +3241,12 @@ namespace Avalonia.Controls private static readonly IComparer s_maxRatioComparer = new MaxRatioComparer(); private static readonly IComparer s_starWeightComparer = new StarWeightComparer(); - #endregion Static Fields - //------------------------------------------------------ // // Private Structures / Classes // //------------------------------------------------------ - #region Private Structures Classes - /// /// Extended data instantiated on demand, when grid handles non-trivial case. /// @@ -3340,16 +3299,12 @@ namespace Avalonia.Controls ArrangeOverrideInProgress = 0x00080000, // "1" while in the context of Grid.ArrangeOverride } - #endregion Private Structures Classes - //------------------------------------------------------ // // Properties // //------------------------------------------------------ - #region Properties - /// /// ShowGridLines property. This property is used mostly /// for simplification of visual debuggig. When it is set @@ -3464,16 +3419,12 @@ namespace Avalonia.Controls false, new PropertyChangedCallback(DefinitionBase.OnIsSharedSizeScopePropertyChanged))); - #endregion Properties - //------------------------------------------------------ // // Internal Structures / Classes // //------------------------------------------------------ - #region Internal Structures Classes - /// /// LayoutTimeSizeType is used internally and reflects layout-time size type. /// @@ -3486,16 +3437,12 @@ namespace Avalonia.Controls Star = 0x04, } - #endregion Internal Structures Classes - //------------------------------------------------------ // // Private Structures / Classes // //------------------------------------------------------ - #region Private Structures Classes - /// /// CellCache stored calculated values of /// 1. attached cell positioning properties; @@ -4071,12 +4018,10 @@ namespace Avalonia.Controls { if (_currentEnumerator == -1) { - #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception throw new InvalidOperationException(SR.Get(SRID.EnumeratorNotStarted)); } if (_currentEnumerator >= 3) { - #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception throw new InvalidOperationException(SR.Get(SRID.EnumeratorReachedEnd)); } @@ -4186,7 +4131,5 @@ namespace Avalonia.Controls private static readonly Pen s_evenDashPen; // second pen to draw dash private static readonly Point c_zeroPoint = new Point(0, 0); } - - #endregion Private Structures Classes } } \ No newline at end of file From 37b88978b4330066d0e88e87325f5a899bb73302 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 13:24:45 +0800 Subject: [PATCH 062/130] Replace some Types from WPF to Avalonia equivalents. --- src/Avalonia.Controls/Grid.cs | 145 +++++++++++++++++----------------- 1 file changed, 73 insertions(+), 72 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 4ba5c324f1..a88f8efb04 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -11,6 +11,7 @@ using System.Diagnostics; using Avalonia; using Avalonia.Collections; using Avalonia.Media; +using Avalonia.Utilities; namespace Avalonia.Controls { @@ -74,11 +75,11 @@ namespace Avalonia.Controls } /// - /// Helper for setting Column property on a UIElement. + /// Helper for setting Column property on a Control. /// - /// UIElement to set Column property on. + /// Control to set Column property on. /// Column property value. - public static void SetColumn(UIElement element, int value) + public static void SetColumn(Control element, int value) { if (element == null) { @@ -89,12 +90,12 @@ namespace Avalonia.Controls } /// - /// Helper for reading Column property from a UIElement. + /// Helper for reading Column property from a Control. /// - /// UIElement to read Column property from. + /// Control to read Column property from. /// Column property value. [AttachedPropertyBrowsableForChildren()] - public static int GetColumn(UIElement element) + public static int GetColumn(Control element) { if (element == null) { @@ -105,11 +106,11 @@ namespace Avalonia.Controls } /// - /// Helper for setting Row property on a UIElement. + /// Helper for setting Row property on a Control. /// - /// UIElement to set Row property on. + /// Control to set Row property on. /// Row property value. - public static void SetRow(UIElement element, int value) + public static void SetRow(Control element, int value) { if (element == null) { @@ -120,12 +121,12 @@ namespace Avalonia.Controls } /// - /// Helper for reading Row property from a UIElement. + /// Helper for reading Row property from a Control. /// - /// UIElement to read Row property from. + /// Control to read Row property from. /// Row property value. [AttachedPropertyBrowsableForChildren()] - public static int GetRow(UIElement element) + public static int GetRow(Control element) { if (element == null) { @@ -136,11 +137,11 @@ namespace Avalonia.Controls } /// - /// Helper for setting ColumnSpan property on a UIElement. + /// Helper for setting ColumnSpan property on a Control. /// - /// UIElement to set ColumnSpan property on. + /// Control to set ColumnSpan property on. /// ColumnSpan property value. - public static void SetColumnSpan(UIElement element, int value) + public static void SetColumnSpan(Control element, int value) { if (element == null) { @@ -151,12 +152,12 @@ namespace Avalonia.Controls } /// - /// Helper for reading ColumnSpan property from a UIElement. + /// Helper for reading ColumnSpan property from a Control. /// - /// UIElement to read ColumnSpan property from. + /// Control to read ColumnSpan property from. /// ColumnSpan property value. [AttachedPropertyBrowsableForChildren()] - public static int GetColumnSpan(UIElement element) + public static int GetColumnSpan(Control element) { if (element == null) { @@ -167,11 +168,11 @@ namespace Avalonia.Controls } /// - /// Helper for setting RowSpan property on a UIElement. + /// Helper for setting RowSpan property on a Control. /// - /// UIElement to set RowSpan property on. + /// Control to set RowSpan property on. /// RowSpan property value. - public static void SetRowSpan(UIElement element, int value) + public static void SetRowSpan(Control element, int value) { if (element == null) { @@ -182,12 +183,12 @@ namespace Avalonia.Controls } /// - /// Helper for reading RowSpan property from a UIElement. + /// Helper for reading RowSpan property from a Control. /// - /// UIElement to read RowSpan property from. + /// Control to read RowSpan property from. /// RowSpan property value. [AttachedPropertyBrowsableForChildren()] - public static int GetRowSpan(UIElement element) + public static int GetRowSpan(Control element) { if (element == null) { @@ -198,11 +199,11 @@ namespace Avalonia.Controls } /// - /// Helper for setting IsSharedSizeScope property on a UIElement. + /// Helper for setting IsSharedSizeScope property on a Control. /// - /// UIElement to set IsSharedSizeScope property on. + /// Control to set IsSharedSizeScope property on. /// IsSharedSizeScope property value. - public static void SetIsSharedSizeScope(UIElement element, bool value) + public static void SetIsSharedSizeScope(Control element, bool value) { if (element == null) { @@ -213,11 +214,11 @@ namespace Avalonia.Controls } /// - /// Helper for reading IsSharedSizeScope property from a UIElement. + /// Helper for reading IsSharedSizeScope property from a Control. /// - /// UIElement to read IsSharedSizeScope property from. + /// Control to read IsSharedSizeScope property from. /// IsSharedSizeScope property value. - public static bool GetIsSharedSizeScope(UIElement element) + public static bool GetIsSharedSizeScope(Control element) { if (element == null) { @@ -243,30 +244,30 @@ namespace Avalonia.Controls } /// - /// Returns a ColumnDefinitionCollection of column definitions. + /// Returns a ColumnDefinitions of column definitions. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public ColumnDefinitionCollection ColumnDefinitions + public ColumnDefinitions ColumnDefinitions { get { if (_data == null) { _data = new ExtendedData(); } - if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitionCollection(this); } + if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions(this); } return (_data.ColumnDefinitions); } } /// - /// Returns a RowDefinitionCollection of row definitions. + /// Returns a RowDefinitions of row definitions. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public RowDefinitionCollection RowDefinitions + public RowDefinitions RowDefinitions { get { if (_data == null) { _data = new ExtendedData(); } - if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitionCollection(this); } + if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions(this); } return (_data.RowDefinitions); } @@ -338,11 +339,11 @@ namespace Avalonia.Controls if (extData == null) { gridDesiredSize = new Size(); - UIElementCollection children = InternalChildren; + Controls children = InternalChildren; for (int i = 0, count = children.Count; i < count; ++i) { - UIElement child = children[i]; + Control child = children[i]; if (child != null) { child.Measure(constraint); @@ -358,7 +359,7 @@ namespace Avalonia.Controls bool sizeToContentV = double.IsPositiveInfinity(constraint.Height); // Clear index information and rounding errors - if (RowDefinitionCollectionDirty || ColumnDefinitionCollectionDirty) + if (RowDefinitionsDirty || ColumnDefinitionsDirty) { if (_definitionIndices != null) { @@ -636,11 +637,11 @@ namespace Avalonia.Controls if (_data == null) { - UIElementCollection children = InternalChildren; + Controls children = InternalChildren; for (int i = 0, count = children.Count; i < count; ++i) { - UIElement child = children[i]; + Control child = children[i]; if (child != null) { child.Arrange(new Rect(arrangeSize)); @@ -658,11 +659,11 @@ namespace Avalonia.Controls - UIElementCollection children = InternalChildren; + Controls children = InternalChildren; for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) { - UIElement cell = children[currentCell]; + Control cell = children[currentCell]; if (cell == null) { continue; @@ -741,7 +742,7 @@ namespace Avalonia.Controls Invariant.Assert(_data != null); // actual value calculations require structure to be up-to-date - if (!ColumnDefinitionCollectionDirty) + if (!ColumnDefinitionsDirty) { DefinitionBase[] definitions = DefinitionsU; value = definitions[(columnIndex + 1) % definitions.Length].FinalOffset; @@ -763,7 +764,7 @@ namespace Avalonia.Controls Invariant.Assert(_data != null); // actual value calculations require structure to be up-to-date - if (!RowDefinitionCollectionDirty) + if (!RowDefinitionsDirty) { DefinitionBase[] definitions = DefinitionsV; value = definitions[(rowIndex + 1) % definitions.Length].FinalOffset; @@ -799,7 +800,7 @@ namespace Avalonia.Controls /// /// Convenience accessor to ValidDefinitionsUStructure bit flag. /// - internal bool ColumnDefinitionCollectionDirty + internal bool ColumnDefinitionsDirty { get { return (!CheckFlagsAnd(Flags.ValidDefinitionsUStructure)); } set { SetFlags(!value, Flags.ValidDefinitionsUStructure); } @@ -808,7 +809,7 @@ namespace Avalonia.Controls /// /// Convenience accessor to ValidDefinitionsVStructure bit flag. /// - internal bool RowDefinitionCollectionDirty + internal bool RowDefinitionsDirty { get { return (!CheckFlagsAnd(Flags.ValidDefinitionsVStructure)); } set { SetFlags(!value, Flags.ValidDefinitionsVStructure); } @@ -841,7 +842,7 @@ namespace Avalonia.Controls /// private void ValidateCellsCore() { - UIElementCollection children = InternalChildren; + Controls children = InternalChildren; ExtendedData extData = ExtData; extData.CellCachesCollection = new CellCache[children.Count]; @@ -856,7 +857,7 @@ namespace Avalonia.Controls for (int i = PrivateCells.Length - 1; i >= 0; --i) { - UIElement child = children[i]; + Control child = children[i]; if (child == null) { continue; @@ -954,7 +955,7 @@ namespace Avalonia.Controls { - if (ColumnDefinitionCollectionDirty) + if (ColumnDefinitionsDirty) { ExtendedData extData = ExtData; @@ -981,7 +982,7 @@ namespace Avalonia.Controls } } - ColumnDefinitionCollectionDirty = false; + ColumnDefinitionsDirty = false; } Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Length > 0); @@ -1001,7 +1002,7 @@ namespace Avalonia.Controls { - if (RowDefinitionCollectionDirty) + if (RowDefinitionsDirty) { ExtendedData extData = ExtData; @@ -1028,7 +1029,7 @@ namespace Avalonia.Controls } } - RowDefinitionCollectionDirty = false; + RowDefinitionsDirty = false; } Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Length > 0); @@ -1167,7 +1168,7 @@ namespace Avalonia.Controls return; } - UIElementCollection children = InternalChildren; + Controls children = InternalChildren; Hashtable spanStore = null; bool ignoreDesiredSizeV = forceInfinityV; @@ -1178,7 +1179,7 @@ namespace Avalonia.Controls MeasureCell(i, forceInfinityV); - hasDesiredSizeUChanged |= !DoubleUtil.AreClose(oldWidth, children[i].DesiredSize.Width); + hasDesiredSizeUChanged |= !MathUtilities.AreClose(oldWidth, children[i].DesiredSize.Width); if (!ignoreDesiredSizeU) { @@ -1317,7 +1318,7 @@ namespace Avalonia.Controls } - UIElement child = InternalChildren[cell]; + Control child = InternalChildren[cell]; if (child != null) { Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); @@ -1542,10 +1543,10 @@ namespace Avalonia.Controls // sanity check: totalRemainingSize and sizeToDistribute must be real positive numbers Debug.Assert( !double.IsInfinity(totalRemainingSize) - && !DoubleUtil.IsNaN(totalRemainingSize) + && !double.IsNaN(totalRemainingSize) && totalRemainingSize > 0 && !double.IsInfinity(sizeToDistribute) - && !DoubleUtil.IsNaN(sizeToDistribute) + && !double.IsNaN(sizeToDistribute) && sizeToDistribute > 0 ); for (int i = 0; i < count; ++i) @@ -2076,7 +2077,7 @@ namespace Avalonia.Controls if (useLayoutRounding) { roundingErrors[i] = definitions[i].SizeCache; - definitions[i].SizeCache = UIElement.RoundLayoutValue(definitions[i].SizeCache, dpi); + definitions[i].SizeCache = Control.RoundLayoutValue(definitions[i].SizeCache, dpi); } } definitionIndices[starDefinitionsCount++] = i; @@ -2115,7 +2116,7 @@ namespace Avalonia.Controls if (useLayoutRounding) { roundingErrors[i] = definitions[i].SizeCache; - definitions[i].SizeCache = UIElement.RoundLayoutValue(definitions[i].SizeCache, dpi); + definitions[i].SizeCache = Control.RoundLayoutValue(definitions[i].SizeCache, dpi); } allPreferredArrangeSize += definitions[i].SizeCache; @@ -2166,7 +2167,7 @@ namespace Avalonia.Controls if (useLayoutRounding) { roundingErrors[definitionIndices[i]] = definitions[definitionIndices[i]].SizeCache; - definitions[definitionIndices[i]].SizeCache = UIElement.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi); + definitions[definitionIndices[i]].SizeCache = Control.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi); } allPreferredArrangeSize += definitions[definitionIndices[i]].SizeCache; @@ -2191,7 +2192,7 @@ namespace Avalonia.Controls if (useLayoutRounding) { roundingErrors[definitionIndex] = final; - final = UIElement.RoundLayoutValue(finalOld, dpi); + final = Control.RoundLayoutValue(finalOld, dpi); final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); final = Math.Min(final, definitions[definitionIndex].SizeCache); } @@ -2218,7 +2219,7 @@ namespace Avalonia.Controls RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); double adjustedSize = allPreferredArrangeSize; - double dpiIncrement = UIElement.RoundLayoutValue(1.0, dpi); + double dpiIncrement = Control.RoundLayoutValue(1.0, dpi); if (allPreferredArrangeSize > finalSize) { @@ -2617,7 +2618,7 @@ namespace Avalonia.Controls for (int i = 0; i < definitions.Length; ++i) { DefinitionBase def = definitions[i]; - double roundedSize = UIElement.RoundLayoutValue(def.SizeCache, dpi); + double roundedSize = Control.RoundLayoutValue(def.SizeCache, dpi); roundingErrors[i] = (roundedSize - def.SizeCache); def.SizeCache = roundedSize; roundedTakenSize += roundedSize; @@ -3252,8 +3253,8 @@ namespace Avalonia.Controls /// private class ExtendedData { - internal ColumnDefinitionCollection ColumnDefinitions; // collection of column definitions (logical tree support) - internal RowDefinitionCollection RowDefinitions; // collection of row definitions (logical tree support) + internal ColumnDefinitions ColumnDefinitions; // collection of column definitions (logical tree support) + internal RowDefinitions RowDefinitions; // collection of row definitions (logical tree support) internal DefinitionBase[] DefinitionsU; // collection of column definitions used during calc internal DefinitionBase[] DefinitionsV; // collection of row definitions used during calc internal CellCache[] CellCachesCollection; // backing store for logical children @@ -3972,8 +3973,8 @@ namespace Avalonia.Controls { Debug.Assert(grid != null); _currentEnumerator = -1; - _enumerator0 = new ColumnDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.ColumnDefinitions : null); - _enumerator1 = new RowDefinitionCollection.Enumerator(grid.ExtData != null ? grid.ExtData.RowDefinitions : null); + _enumerator0 = new ColumnDefinitions.Enumerator(grid.ExtData != null ? grid.ExtData.ColumnDefinitions : null); + _enumerator1 = new RowDefinitions.Enumerator(grid.ExtData != null ? grid.ExtData.RowDefinitions : null); // GridLineRenderer is NOT included into this enumerator. _enumerator2Index = 0; if (includeChildren) @@ -4025,7 +4026,7 @@ namespace Avalonia.Controls throw new InvalidOperationException(SR.Get(SRID.EnumeratorReachedEnd)); } - // assert below is not true anymore since UIElementCollection allowes for null children + // assert below is not true anymore since Controls allowes for null children //Debug.Assert(_currentChild != null); return (_currentChild); } @@ -4042,9 +4043,9 @@ namespace Avalonia.Controls private int _currentEnumerator; private Object _currentChild; - private ColumnDefinitionCollection.Enumerator _enumerator0; - private RowDefinitionCollection.Enumerator _enumerator1; - private UIElementCollection _enumerator2Collection; + private ColumnDefinitions.Enumerator _enumerator0; + private RowDefinitions.Enumerator _enumerator1; + private Controls _enumerator2Collection; private int _enumerator2Index; private int _enumerator2Count; } From 99c400aa061eee8ec57cc646422c39277feda0ad Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 13:29:21 +0800 Subject: [PATCH 063/130] Remove AttachedPropertyBrowsableForChildren attribute --- src/Avalonia.Controls/Grid.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index a88f8efb04..f58b33faa6 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -94,7 +94,6 @@ namespace Avalonia.Controls /// /// Control to read Column property from. /// Column property value. - [AttachedPropertyBrowsableForChildren()] public static int GetColumn(Control element) { if (element == null) @@ -125,7 +124,6 @@ namespace Avalonia.Controls /// /// Control to read Row property from. /// Row property value. - [AttachedPropertyBrowsableForChildren()] public static int GetRow(Control element) { if (element == null) @@ -156,7 +154,6 @@ namespace Avalonia.Controls /// /// Control to read ColumnSpan property from. /// ColumnSpan property value. - [AttachedPropertyBrowsableForChildren()] public static int GetColumnSpan(Control element) { if (element == null) @@ -187,7 +184,6 @@ namespace Avalonia.Controls /// /// Control to read RowSpan property from. /// RowSpan property value. - [AttachedPropertyBrowsableForChildren()] public static int GetRowSpan(Control element) { if (element == null) From 5c75696d61f5c284f00cf7562a7eb0ff2bd56f14 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 13:47:46 +0800 Subject: [PATCH 064/130] Remove DesignerSerializationVisibility attribute --- src/Avalonia.Controls/Grid.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index f58b33faa6..67f7980939 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -242,7 +242,6 @@ namespace Avalonia.Controls /// /// Returns a ColumnDefinitions of column definitions. /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public ColumnDefinitions ColumnDefinitions { get @@ -257,7 +256,6 @@ namespace Avalonia.Controls /// /// Returns a RowDefinitions of row definitions. /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public RowDefinitions RowDefinitions { get From 2743c605e64618ed204b2b6d1395459dd8c9b914 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 14:02:21 +0800 Subject: [PATCH 065/130] Replace invariant.assert with Contract.Requires. --- src/Avalonia.Controls/Grid.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 67f7980939..401c8a4d87 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -733,7 +733,7 @@ namespace Avalonia.Controls { double value = 0.0; - Invariant.Assert(_data != null); + Contract.Requires(_data != null); // actual value calculations require structure to be up-to-date if (!ColumnDefinitionsDirty) @@ -755,7 +755,7 @@ namespace Avalonia.Controls { double value = 0.0; - Invariant.Assert(_data != null); + Contract.Requires(_data != null); // actual value calculations require structure to be up-to-date if (!RowDefinitionsDirty) @@ -3661,7 +3661,7 @@ namespace Avalonia.Controls internal StarDistributionOrderIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } @@ -3702,7 +3702,7 @@ namespace Avalonia.Controls internal DistributionOrderIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } @@ -3745,7 +3745,7 @@ namespace Avalonia.Controls internal RoundingErrorIndexComparer(double[] errors) { - Invariant.Assert(errors != null); + Contract.Requires(errors != null); this.errors = errors; } @@ -3844,7 +3844,7 @@ namespace Avalonia.Controls internal MinRatioIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } @@ -3885,7 +3885,7 @@ namespace Avalonia.Controls internal MaxRatioIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } @@ -3926,7 +3926,7 @@ namespace Avalonia.Controls internal StarWeightIndexComparer(DefinitionBase[] definitions) { - Invariant.Assert(definitions != null); + Contract.Requires(definitions != null); this.definitions = definitions; } From cd0e1a34e366fd766dd2b2fa86f20c8b83a84b7d Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 14:03:29 +0800 Subject: [PATCH 066/130] Replace WPF types with equivalent Avalonia ones part 2. --- src/Avalonia.Controls/Grid.cs | 46 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 401c8a4d87..c46d221dff 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -35,7 +35,7 @@ namespace Avalonia.Controls /// public Grid() { - SetFlags((bool) ShowGridLinesProperty.GetDefaultValue(DependencyObjectType), Flags.ShowGridLinesPropertyValue); + SetFlags((bool) ShowGridLinesProperty.GetDefaultValue(AvaloniaObjectType), Flags.ShowGridLinesPropertyValue); } //------------------------------------------------------ @@ -700,8 +700,8 @@ namespace Avalonia.Controls /// /// protected internal override void OnVisualChildrenChanged( - DependencyObject visualAdded, - DependencyObject visualRemoved) + AvaloniaObject visualAdded, + AvaloniaObject visualRemoved) { CellsStructureDirty = true; @@ -2909,7 +2909,7 @@ namespace Avalonia.Controls /// /// /// - private static void OnShowGridLinesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnShowGridLinesPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { Grid grid = (Grid)d; @@ -2925,7 +2925,7 @@ namespace Avalonia.Controls /// /// /// - private static void OnCellAttachedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnCellAttachedPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { Visual child = d as Visual; @@ -2943,7 +2943,7 @@ namespace Avalonia.Controls } /// - /// + /// /// private static bool IsIntValueNotNegative(object value) { @@ -2951,7 +2951,7 @@ namespace Avalonia.Controls } /// - /// + /// /// private static bool IsIntValueGreaterThanZero(object value) { @@ -3306,8 +3306,8 @@ namespace Avalonia.Controls /// to true grid lines are drawn to visualize location /// of grid lines. /// - public static readonly DependencyProperty ShowGridLinesProperty = - DependencyProperty.Register( + public static readonly AvaloniaProperty ShowGridLinesProperty = + AvaloniaProperty.Register( "ShowGridLines", typeof(bool), typeof(Grid), @@ -3326,9 +3326,9 @@ namespace Avalonia.Controls /// should have Column property set to 0. /// Default value for the property is 0. /// - [CommonDependencyProperty] - public static readonly DependencyProperty ColumnProperty = - DependencyProperty.RegisterAttached( + [CommonAvaloniaProperty] + public static readonly AvaloniaProperty ColumnProperty = + AvaloniaProperty.RegisterAttached( "Column", typeof(int), typeof(Grid), @@ -3348,9 +3348,9 @@ namespace Avalonia.Controls /// Default value for the property is 0. /// /// - [CommonDependencyProperty] - public static readonly DependencyProperty RowProperty = - DependencyProperty.RegisterAttached( + [CommonAvaloniaProperty] + public static readonly AvaloniaProperty RowProperty = + AvaloniaProperty.RegisterAttached( "Row", typeof(int), typeof(Grid), @@ -3369,9 +3369,9 @@ namespace Avalonia.Controls /// /// Default value for the property is 1. /// - [CommonDependencyProperty] - public static readonly DependencyProperty ColumnSpanProperty = - DependencyProperty.RegisterAttached( + [CommonAvaloniaProperty] + public static readonly AvaloniaProperty ColumnSpanProperty = + AvaloniaProperty.RegisterAttached( "ColumnSpan", typeof(int), typeof(Grid), @@ -3390,9 +3390,9 @@ namespace Avalonia.Controls /// /// Default value for the property is 1. /// - [CommonDependencyProperty] - public static readonly DependencyProperty RowSpanProperty = - DependencyProperty.RegisterAttached( + [CommonAvaloniaProperty] + public static readonly AvaloniaProperty RowSpanProperty = + AvaloniaProperty.RegisterAttached( "RowSpan", typeof(int), typeof(Grid), @@ -3405,8 +3405,8 @@ namespace Avalonia.Controls /// /// IsSharedSizeScope property marks scoping element for shared size. /// - public static readonly DependencyProperty IsSharedSizeScopeProperty = - DependencyProperty.RegisterAttached( + public static readonly AvaloniaProperty IsSharedSizeScopeProperty = + AvaloniaProperty.RegisterAttached( "IsSharedSizeScope", typeof(bool), typeof(Grid), From 45d7bb933108b07198114942bc09912d93dcc2ff Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 14:04:04 +0800 Subject: [PATCH 067/130] Add missing imports. --- src/Avalonia.Controls/Grid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index c46d221dff..4285ba98ec 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -7,7 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; - +using System.Threading; using Avalonia; using Avalonia.Collections; using Avalonia.Media; From 3c9f5316078e6407ce00e0c6e62ef121d3af9ce0 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 14:04:48 +0800 Subject: [PATCH 068/130] Remove EditorBrowsable attribute --- src/Avalonia.Controls/Grid.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 4285ba98ec..6f4d7bc063 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -2829,7 +2829,6 @@ namespace Avalonia.Controls /// /// Returns true if ColumnDefinitions collection is not empty /// - [EditorBrowsable(EditorBrowsableState.Never)] public bool ShouldSerializeColumnDefinitions() { ExtendedData extData = ExtData; @@ -2841,7 +2840,6 @@ namespace Avalonia.Controls /// /// Returns true if RowDefinitions collection is not empty /// - [EditorBrowsable(EditorBrowsableState.Never)] public bool ShouldSerializeRowDefinitions() { ExtendedData extData = ExtData; From 83827a93f594ce2a2ffc82847bc8d0f8b0f2c326 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 15:39:14 +0800 Subject: [PATCH 069/130] Convert DependencyProperties to Avalonia's. --- src/Avalonia.Controls/Grid.cs | 161 ++++++++++------------------------ 1 file changed, 47 insertions(+), 114 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 6f4d7bc063..4e77d8b307 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -28,6 +28,8 @@ namespace Avalonia.Controls static Grid() { + ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); + AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); } /// @@ -35,7 +37,6 @@ namespace Avalonia.Controls /// public Grid() { - SetFlags((bool) ShowGridLinesProperty.GetDefaultValue(AvaloniaObjectType), Flags.ShowGridLinesPropertyValue); } //------------------------------------------------------ @@ -81,11 +82,7 @@ namespace Avalonia.Controls /// Column property value. public static void SetColumn(Control element, int value) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - + Contract.Requires(element != null); element.SetValue(ColumnProperty, value); } @@ -96,12 +93,8 @@ namespace Avalonia.Controls /// Column property value. public static int GetColumn(Control element) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - return ((int)element.GetValue(ColumnProperty)); + Contract.Requires(element != null); + return element.GetValue(ColumnProperty); } /// @@ -111,11 +104,7 @@ namespace Avalonia.Controls /// Row property value. public static void SetRow(Control element, int value) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - + Contract.Requires(element != null); element.SetValue(RowProperty, value); } @@ -126,12 +115,8 @@ namespace Avalonia.Controls /// Row property value. public static int GetRow(Control element) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - return ((int)element.GetValue(RowProperty)); + Contract.Requires(element != null); + return element.GetValue(RowProperty); } /// @@ -141,11 +126,7 @@ namespace Avalonia.Controls /// ColumnSpan property value. public static void SetColumnSpan(Control element, int value) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - + Contract.Requires(element != null); element.SetValue(ColumnSpanProperty, value); } @@ -156,12 +137,8 @@ namespace Avalonia.Controls /// ColumnSpan property value. public static int GetColumnSpan(Control element) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - return ((int)element.GetValue(ColumnSpanProperty)); + Contract.Requires(element != null); + return element.GetValue(ColumnSpanProperty); } /// @@ -171,11 +148,7 @@ namespace Avalonia.Controls /// RowSpan property value. public static void SetRowSpan(Control element, int value) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - + Contract.Requires(element != null); element.SetValue(RowSpanProperty, value); } @@ -186,12 +159,8 @@ namespace Avalonia.Controls /// RowSpan property value. public static int GetRowSpan(Control element) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - return ((int)element.GetValue(RowSpanProperty)); + Contract.Requires(element != null); + return element.GetValue(RowSpanProperty); } /// @@ -201,11 +170,7 @@ namespace Avalonia.Controls /// IsSharedSizeScope property value. public static void SetIsSharedSizeScope(Control element, bool value) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - + Contract.Requires(element != null); element.SetValue(IsSharedSizeScopeProperty, value); } @@ -216,12 +181,8 @@ namespace Avalonia.Controls /// IsSharedSizeScope property value. public static bool GetIsSharedSizeScope(Control element) { - if (element == null) - { - throw new ArgumentNullException("element"); - } - - return ((bool)element.GetValue(IsSharedSizeScopeProperty)); + Contract.Requires(element != null); + return element.GetValue(IsSharedSizeScopeProperty); } //------------------------------------------------------ @@ -235,7 +196,7 @@ namespace Avalonia.Controls /// public bool ShowGridLines { - get { return (CheckFlagsAnd(Flags.ShowGridLinesPropertyValue)); } + get { return GetValue(ShowGridLinesProperty); } set { SetValue(ShowGridLinesProperty, value); } } @@ -3304,14 +3265,8 @@ namespace Avalonia.Controls /// to true grid lines are drawn to visualize location /// of grid lines. /// - public static readonly AvaloniaProperty ShowGridLinesProperty = - AvaloniaProperty.Register( - "ShowGridLines", - typeof(bool), - typeof(Grid), - new FrameworkPropertyMetadata( - false, - new PropertyChangedCallback(OnShowGridLinesPropertyChanged))); + public static readonly StyledProperty ShowGridLinesProperty = + AvaloniaProperty.Register(nameof(ShowGridLines)); /// /// Column property. This is an attached property. @@ -3324,16 +3279,12 @@ namespace Avalonia.Controls /// should have Column property set to 0. /// Default value for the property is 0. /// - [CommonAvaloniaProperty] - public static readonly AvaloniaProperty ColumnProperty = - AvaloniaProperty.RegisterAttached( - "Column", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 0, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueNotNegative)); + public static readonly AttachedProperty ColumnProperty = + AvaloniaProperty.RegisterAttached( + "Column", + defaultValue: 0, + validate: (_, v) => { if (v >= 0) return v; + else throw new ArgumentException("Invalid Grid.Column value."); }); /// /// Row property. This is an attached property. @@ -3346,16 +3297,12 @@ namespace Avalonia.Controls /// Default value for the property is 0. /// /// - [CommonAvaloniaProperty] - public static readonly AvaloniaProperty RowProperty = - AvaloniaProperty.RegisterAttached( - "Row", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 0, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueNotNegative)); + public static readonly AttachedProperty RowProperty = + AvaloniaProperty.RegisterAttached( + "Row", + defaultValue: 0, + validate: (_, v) => { if (v >= 0) return v; + else throw new ArgumentException("Invalid Grid.Row value."); }); /// /// ColumnSpan property. This is an attached property. @@ -3367,16 +3314,12 @@ namespace Avalonia.Controls /// /// Default value for the property is 1. /// - [CommonAvaloniaProperty] - public static readonly AvaloniaProperty ColumnSpanProperty = - AvaloniaProperty.RegisterAttached( - "ColumnSpan", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 1, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueGreaterThanZero)); + public static readonly AttachedProperty ColumnSpanProperty = + AvaloniaProperty.RegisterAttached( + "ColumnSpan", + defaultValue: 1, + validate: (_, v) => { if (v >= 1) return v; + else throw new ArgumentException("Invalid Grid.ColumnSpan value."); }); /// /// RowSpan property. This is an attached property. @@ -3388,29 +3331,19 @@ namespace Avalonia.Controls /// /// Default value for the property is 1. /// - [CommonAvaloniaProperty] - public static readonly AvaloniaProperty RowSpanProperty = - AvaloniaProperty.RegisterAttached( - "RowSpan", - typeof(int), - typeof(Grid), - new FrameworkPropertyMetadata( - 1, - new PropertyChangedCallback(OnCellAttachedPropertyChanged)), - new ValidateValueCallback(IsIntValueGreaterThanZero)); - + public static readonly AttachedProperty RowSpanProperty = + AvaloniaProperty.RegisterAttached( + "RowSpan", + defaultValue: 1, + validate: (_, v) => { if (v >= 1) return v; + else throw new ArgumentException("Invalid Grid.RowSpan value."); }); /// /// IsSharedSizeScope property marks scoping element for shared size. /// - public static readonly AvaloniaProperty IsSharedSizeScopeProperty = - AvaloniaProperty.RegisterAttached( - "IsSharedSizeScope", - typeof(bool), - typeof(Grid), - new FrameworkPropertyMetadata( - false, - new PropertyChangedCallback(DefinitionBase.OnIsSharedSizeScopePropertyChanged))); + public static readonly AttachedProperty IsSharedSizeScopeProperty = + AvaloniaProperty.RegisterAttached( + "IsSharedSizeScope"); //------------------------------------------------------ // From 2ebe05c6763a204b4cad5f39eff118161fc0a9d7 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 15:52:03 +0800 Subject: [PATCH 070/130] Restore deleted GridLength.cs --- src/Avalonia.Controls/GridLength.cs | 220 ++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 src/Avalonia.Controls/GridLength.cs diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs new file mode 100644 index 0000000000..02be95b647 --- /dev/null +++ b/src/Avalonia.Controls/GridLength.cs @@ -0,0 +1,220 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using Avalonia.Utilities; + +namespace Avalonia.Controls +{ + /// + /// Defines the valid units for a . + /// + public enum GridUnitType + { + /// + /// The row or column is auto-sized to fit its content. + /// + Auto = 0, + + /// + /// The row or column is sized in device independent pixels. + /// + Pixel = 1, + + /// + /// The row or column is sized as a weighted proportion of available space. + /// + Star = 2, + } + + /// + /// Holds the width or height of a 's column and row definitions. + /// + public struct GridLength : IEquatable + { + private readonly GridUnitType _type; + + private readonly double _value; + + /// + /// Initializes a new instance of the struct. + /// + /// The size of the GridLength in device independent pixels. + public GridLength(double value) + : this(value, GridUnitType.Pixel) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The size of the GridLength. + /// The unit of the GridLength. + public GridLength(double value, GridUnitType type) + { + if (value < 0 || double.IsNaN(value) || double.IsInfinity(value)) + { + throw new ArgumentException("Invalid value", nameof(value)); + } + + if (type < GridUnitType.Auto || type > GridUnitType.Star) + { + throw new ArgumentException("Invalid value", nameof(type)); + } + + _type = type; + _value = value; + } + + /// + /// Gets an instance of that indicates that a row or column should + /// auto-size to fit its content. + /// + public static GridLength Auto => new GridLength(0, GridUnitType.Auto); + + /// + /// Gets the unit of the . + /// + public GridUnitType GridUnitType => _type; + + /// + /// Gets a value that indicates whether the has a of Pixel. + /// + public bool IsAbsolute => _type == GridUnitType.Pixel; + + /// + /// Gets a value that indicates whether the has a of Auto. + /// + public bool IsAuto => _type == GridUnitType.Auto; + + /// + /// Gets a value that indicates whether the has a of Star. + /// + public bool IsStar => _type == GridUnitType.Star; + + /// + /// Gets the length. + /// + public double Value => _value; + + /// + /// Compares two GridLength structures for equality. + /// + /// The first GridLength. + /// The second GridLength. + /// True if the structures are equal, otherwise false. + public static bool operator ==(GridLength a, GridLength b) + { + return (a.IsAuto && b.IsAuto) || (a._value == b._value && a._type == b._type); + } + + /// + /// Compares two GridLength structures for inequality. + /// + /// The first GridLength. + /// The first GridLength. + /// True if the structures are unequal, otherwise false. + public static bool operator !=(GridLength gl1, GridLength gl2) + { + return !(gl1 == gl2); + } + + /// + /// Determines whether the is equal to the specified object. + /// + /// The object with which to test equality. + /// True if the objects are equal, otherwise false. + public override bool Equals(object o) + { + if (o == null) + { + return false; + } + + if (!(o is GridLength)) + { + return false; + } + + return this == (GridLength)o; + } + + /// + /// Compares two GridLength structures for equality. + /// + /// The structure with which to test equality. + /// True if the structures are equal, otherwise false. + public bool Equals(GridLength gridLength) + { + return this == gridLength; + } + + /// + /// Gets a hash code for the GridLength. + /// + /// The hash code. + public override int GetHashCode() + { + return _value.GetHashCode() ^ _type.GetHashCode(); + } + + /// + /// Gets a string representation of the . + /// + /// The string representation. + public override string ToString() + { + if (IsAuto) + { + return "Auto"; + } + + string s = _value.ToString(); + return IsStar ? s + "*" : s; + } + + /// + /// Parses a string to return a . + /// + /// The string. + /// The . + public static GridLength Parse(string s) + { + s = s.ToUpperInvariant(); + + if (s == "AUTO") + { + return Auto; + } + else if (s.EndsWith("*")) + { + var valueString = s.Substring(0, s.Length - 1).Trim(); + var value = valueString.Length > 0 ? double.Parse(valueString, CultureInfo.InvariantCulture) : 1; + return new GridLength(value, GridUnitType.Star); + } + else + { + var value = double.Parse(s, CultureInfo.InvariantCulture); + return new GridLength(value, GridUnitType.Pixel); + } + } + + /// + /// Parses a string to return a collection of s. + /// + /// The string. + /// The . + public static IEnumerable ParseLengths(string s) + { + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture)) + { + while (tokenizer.TryReadString(out var item)) + { + yield return Parse(item); + } + } + } + } +} From 0b19cd6e59a565180405999ff946e7cec7b9909a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 15:57:24 +0800 Subject: [PATCH 071/130] Fix GridLinesRenderer --- src/Avalonia.Controls/Grid.cs | 97 +++++++++++++++++------------------ 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 4e77d8b307..460c2bb5ee 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -12,6 +12,7 @@ using Avalonia; using Avalonia.Collections; using Avalonia.Media; using Avalonia.Utilities; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -3978,60 +3979,52 @@ namespace Avalonia.Controls /// /// Helper to render grid lines. /// - internal class GridLinesRenderer : DrawingVisual + internal class GridLinesRenderer : Control { /// /// Static initialization /// static GridLinesRenderer() { - s_oddDashPen = new Pen(Brushes.Blue, c_penWidth); - DoubleCollection oddDashArray = new DoubleCollection(); - oddDashArray.Add(c_dashLength); - oddDashArray.Add(c_dashLength); - s_oddDashPen.DashStyle = new DashStyle(oddDashArray, 0); - s_oddDashPen.DashCap = PenLineCap.Flat; - s_oddDashPen.Freeze(); + var dashArray = new List() { _dashLength, _dashLength }; - s_evenDashPen = new Pen(Brushes.Yellow, c_penWidth); - DoubleCollection evenDashArray = new DoubleCollection(); - evenDashArray.Add(c_dashLength); - evenDashArray.Add(c_dashLength); - s_evenDashPen.DashStyle = new DashStyle(evenDashArray, c_dashLength); - s_evenDashPen.DashCap = PenLineCap.Flat; - s_evenDashPen.Freeze(); + var ds1 = new DashStyle(dashArray, 0); + _oddDashPen = new Pen(Brushes.Blue, + _penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds1); + + var ds2 = new DashStyle(dashArray, _dashLength); + _evenDashPen = new Pen(Brushes.Yellow, + _penWidth, + lineCap: PenLineCap.Flat, + dashStyle: ds2); } /// /// UpdateRenderBounds. /// - /// Size of render bounds - internal void UpdateRenderBounds(Size boundsSize) + public override void Render(DrawingContext drawingContext) { - using (DrawingContext drawingContext = RenderOpen()) - { - Grid grid = VisualTreeHelper.GetParent(this) as Grid; - if ( grid == null - || grid.ShowGridLines == false ) - { - return; - } + var grid = this.GetVisualParent(); - for (int i = 1; i < grid.DefinitionsU.Length; ++i) - { - DrawGridLine( - drawingContext, - grid.DefinitionsU[i].FinalOffset, 0.0, - grid.DefinitionsU[i].FinalOffset, boundsSize.Height); - } + if (grid == null || !grid.ShowGridLines) + return; - for (int i = 1; i < grid.DefinitionsV.Length; ++i) - { - DrawGridLine( - drawingContext, - 0.0, grid.DefinitionsV[i].FinalOffset, - boundsSize.Width, grid.DefinitionsV[i].FinalOffset); - } + for (int i = 1; i < grid.ColumnDefinitions.Count; ++i) + { + DrawGridLine( + drawingContext, + grid.ColumnDefinitions[i].FinalOffset, 0.0, + grid.ColumnDefinitions[i].FinalOffset, _lastArrangeSize.Height); + } + + for (int i = 1; i < grid.RowDefinitions.Count; ++i) + { + DrawGridLine( + drawingContext, + 0.0, grid.RowDefinitions[i].FinalOffset, + _lastArrangeSize.Width, grid.RowDefinitions[i].FinalOffset); } } @@ -4045,17 +4038,23 @@ namespace Avalonia.Controls double endX, double endY) { - Point start = new Point(startX, startY); - Point end = new Point(endX, endY); - drawingContext.DrawLine(s_oddDashPen, start, end); - drawingContext.DrawLine(s_evenDashPen, start, end); + var start = new Point(startX, startY); + var end = new Point(endX, endY); + drawingContext.DrawLine(_oddDashPen, start, end); + drawingContext.DrawLine(_evenDashPen, start, end); } - private const double c_dashLength = 4.0; // - private const double c_penWidth = 1.0; // - private static readonly Pen s_oddDashPen; // first pen to draw dash - private static readonly Pen s_evenDashPen; // second pen to draw dash - private static readonly Point c_zeroPoint = new Point(0, 0); - } + internal void UpdateRenderBounds(Size arrangeSize) + { + _lastArrangeSize = arrangeSize; + this.InvalidateVisual(); + } + + private static Size _lastArrangeSize; + private const double _dashLength = 4.0; // + private const double _penWidth = 1.0; // + private static readonly Pen _oddDashPen; // first pen to draw dash + private static readonly Pen _evenDashPen; // second pen to draw dash + } } } \ No newline at end of file From 7437421704c5b19e15ea327ca03051ebb6c9a446 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 18:16:18 +0800 Subject: [PATCH 072/130] Comment-out code that is not going to be used. --- src/Avalonia.Controls/Grid.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 460c2bb5ee..1bb2cd556f 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -50,7 +50,7 @@ namespace Avalonia.Controls /// /// /// - protected internal override IEnumerator LogicalChildren + /* protected internal override IEnumerator LogicalChildren { get { @@ -74,7 +74,7 @@ namespace Avalonia.Controls return (new GridChildrenCollectionEnumeratorSimple(this, !noChildren)); } - } + } */ /// /// Helper for setting Column property on a Control. @@ -235,7 +235,7 @@ namespace Avalonia.Controls // //------------------------------------------------------ - /// + /* /// /// Derived class must implement to support Visual children. The method must return /// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1. /// @@ -272,7 +272,7 @@ namespace Avalonia.Controls { //since GridLinesRenderer has not been added as a child, so we do not subtract get { return base.VisualChildrenCount + (_gridLinesRenderer != null ? 1 : 0); } - } + }*/ /// @@ -2902,7 +2902,7 @@ namespace Avalonia.Controls } } - /// + /* /// /// /// private static bool IsIntValueNotNegative(object value) @@ -2916,7 +2916,7 @@ namespace Avalonia.Controls private static bool IsIntValueGreaterThanZero(object value) { return ((int)value > 0); - } + }*/ /// /// Helper for Comparer methods. @@ -3890,7 +3890,7 @@ namespace Avalonia.Controls } } - /// + /* /// /// Implementation of a simple enumerator of grid's logical children /// private class GridChildrenCollectionEnumeratorSimple : IEnumerator @@ -3974,7 +3974,7 @@ namespace Avalonia.Controls private Controls _enumerator2Collection; private int _enumerator2Index; private int _enumerator2Count; - } + }*/ /// /// Helper to render grid lines. From ec99f7d1a93d6ba9720044dd27ca612b5361ab32 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 30 May 2019 13:17:42 +0300 Subject: [PATCH 073/130] Initial scroll gesture implementation --- src/Avalonia.Controls/Button.cs | 7 +- .../Presenters/ScrollContentPresenter.cs | 69 ++++++++++ src/Avalonia.Controls/TabControl.cs | 17 ++- .../GestureRecognizerCollection.cs | 127 +++++++++++++++++ .../GestureRecognizers/IGestureRecognizer.cs | 23 ++++ .../ScrollGestureRecognizer.cs | 129 ++++++++++++++++++ src/Avalonia.Input/Gestures.cs | 8 ++ src/Avalonia.Input/InputElement.cs | 39 ++++++ src/Avalonia.Input/MouseDevice.cs | 65 +++------ src/Avalonia.Input/Pointer.cs | 44 +++--- src/Avalonia.Input/PointerEventArgs.cs | 11 ++ src/Avalonia.Input/Properties/AssemblyInfo.cs | 1 + src/Avalonia.Input/ScrollGestureEventArgs.cs | 29 ++++ src/Avalonia.Input/TouchDevice.cs | 18 +-- src/Avalonia.Themes.Default/ScrollViewer.xaml | 11 +- 15 files changed, 521 insertions(+), 77 deletions(-) create mode 100644 src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs create mode 100644 src/Avalonia.Input/GestureRecognizers/IGestureRecognizer.cs create mode 100644 src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs create mode 100644 src/Avalonia.Input/ScrollGestureEventArgs.cs diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index cc9e6b7444..70f26288af 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -252,7 +252,6 @@ namespace Avalonia.Controls if (e.MouseButton == MouseButton.Left) { - e.Device.Capture(this); IsPressed = true; e.Handled = true; @@ -270,7 +269,6 @@ namespace Avalonia.Controls if (IsPressed && e.MouseButton == MouseButton.Left) { - e.Device.Capture(null); IsPressed = false; e.Handled = true; @@ -282,6 +280,11 @@ namespace Avalonia.Controls } } + protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) + { + IsPressed = false; + } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status) { base.UpdateDataValidation(property, status); diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 30330ef9ac..e7d8018a42 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; @@ -64,6 +65,7 @@ namespace Avalonia.Controls.Presenters private Vector _offset; private IDisposable _logicalScrollSubscription; private Size _viewport; + private Dictionary _activeLogicalGestureScrolls; /// /// Initializes static members of the class. @@ -81,6 +83,7 @@ namespace Avalonia.Controls.Presenters public ScrollContentPresenter() { AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested); + AddHandler(Gestures.ScrollGestureEvent, OnScrollGesture); this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription); } @@ -227,6 +230,72 @@ namespace Avalonia.Controls.Presenters return finalSize; } + // Arbitrary chosen value, probably need to ask ILogicalScrollable + private const int LogicalScrollItemSize = 50; + private void OnScrollGesture(object sender, ScrollGestureEventArgs e) + { + if (Extent.Height > Viewport.Height || Extent.Width > Viewport.Width) + { + var scrollable = Child as ILogicalScrollable; + bool isLogical = scrollable?.IsLogicalScrollEnabled == true; + + double x = Offset.X; + double y = Offset.Y; + + Vector delta = default; + if (isLogical) + _activeLogicalGestureScrolls?.TryGetValue(e.Id, out delta); + delta += e.Delta; + + if (Extent.Height > Viewport.Height) + { + double dy; + if (isLogical) + { + var logicalUnits = delta.Y / LogicalScrollItemSize; + delta = delta.WithY(delta.Y - logicalUnits * LogicalScrollItemSize); + dy = logicalUnits * scrollable.ScrollSize.Height; + } + else + dy = delta.Y; + + + y += dy; + y = Math.Max(y, 0); + y = Math.Min(y, Extent.Height - Viewport.Height); + } + + if (Extent.Width > Viewport.Width) + { + double dx; + if (isLogical) + { + var logicalUnits = delta.X / LogicalScrollItemSize; + delta = delta.WithX(delta.X - logicalUnits * LogicalScrollItemSize); + dx = logicalUnits * scrollable.ScrollSize.Width; + } + else + dx = delta.X; + x += dx; + x = Math.Max(x, 0); + x = Math.Min(x, Extent.Width - Viewport.Width); + } + + if (isLogical) + { + if (_activeLogicalGestureScrolls == null) + _activeLogicalGestureScrolls = new Dictionary(); + _activeLogicalGestureScrolls[e.Id] = delta; + } + + Offset = new Vector(x, y); + e.Handled = true; + } + } + + private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e) + => _activeLogicalGestureScrolls?.Remove(e.Id); + /// protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index d6537ebbca..88b9a84111 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; @@ -8,6 +9,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -166,10 +168,23 @@ namespace Avalonia.Controls { base.OnPointerPressed(e); - if (e.MouseButton == MouseButton.Left) + if (e.MouseButton == MouseButton.Left && e.Pointer.Type == PointerType.Mouse) { e.Handled = UpdateSelectionFromEventSource(e.Source); } } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + if (e.MouseButton == MouseButton.Left && e.Pointer.Type != PointerType.Mouse) + { + var container = GetContainerFromEventSource(e.Source); + if (container.GetVisualsAt(e.GetPosition(container)) + .Any(c => container == c || container.IsVisualAncestorOf(c))) + { + e.Handled = UpdateSelectionFromEventSource(e.Source); + } + } + } } } diff --git a/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs new file mode 100644 index 0000000000..91b224e65a --- /dev/null +++ b/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.LogicalTree; +using Avalonia.Styling; + +namespace Avalonia.Input.GestureRecognizers +{ + public class GestureRecognizerCollection : IReadOnlyCollection, IGestureRecognizerActionsDispatcher + { + private readonly IInputElement _inputElement; + private List _recognizers; + private Dictionary _pointerGrabs; + + + public GestureRecognizerCollection(IInputElement inputElement) + { + _inputElement = inputElement; + } + + public void Add(IGestureRecognizer recognizer) + { + if (_recognizers == null) + { + // We initialize the collection when the first recognizer is added + _recognizers = new List(); + _pointerGrabs = new Dictionary(); + } + + _recognizers.Add(recognizer); + recognizer.Initialize(_inputElement, this); + + // Hacks to make bindings work + + if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical) + { + logical.SetParent(logicalParent); + if (recognizer is IStyleable styleableRecognizer + && _inputElement is IStyleable styleableParent) + styleableRecognizer.Bind(StyledElement.TemplatedParentProperty, + styleableParent.GetObservable(StyledElement.TemplatedParentProperty)); + } + } + + static readonly List s_Empty = new List(); + + public IEnumerator GetEnumerator() + => _recognizers?.GetEnumerator() ?? s_Empty.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count => _recognizers?.Count ?? 0; + + + internal bool HandlePointerPressed(PointerPressedEventArgs e) + { + if (_recognizers == null) + return false; + foreach (var r in _recognizers) + { + if(e.Handled) + break; + r.PointerPressed(e); + } + + return e.Handled; + } + + internal bool HandlePointerReleased(PointerReleasedEventArgs e) + { + if (_recognizers == null) + return false; + if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) + { + capture.PointerReleased(e); + } + else + foreach (var r in _recognizers) + { + if (e.Handled) + break; + r.PointerReleased(e); + } + return e.Handled; + } + + internal bool HandlePointerMoved(PointerEventArgs e) + { + if (_recognizers == null) + return false; + if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) + { + capture.PointerMoved(e); + } + else + foreach (var r in _recognizers) + { + if (e.Handled) + break; + r.PointerMoved(e); + } + return e.Handled; + } + + internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e) + { + if (_recognizers == null) + return; + _pointerGrabs.Remove(e.Pointer); + foreach (var r in _recognizers) + { + if(e.Handled) + break; + r.PointerCaptureLost(e); + } + } + + void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) + { + pointer.Capture(_inputElement); + _pointerGrabs[pointer] = recognizer; + } + + } +} diff --git a/src/Avalonia.Input/GestureRecognizers/IGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/IGestureRecognizer.cs new file mode 100644 index 0000000000..b8ba9e529c --- /dev/null +++ b/src/Avalonia.Input/GestureRecognizers/IGestureRecognizer.cs @@ -0,0 +1,23 @@ +namespace Avalonia.Input.GestureRecognizers +{ + public interface IGestureRecognizer + { + void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions); + void PointerPressed(PointerPressedEventArgs e); + void PointerReleased(PointerReleasedEventArgs e); + void PointerMoved(PointerEventArgs e); + void PointerCaptureLost(PointerCaptureLostEventArgs e); + } + + public interface IGestureRecognizerActionsDispatcher + { + void Capture(IPointer pointer, IGestureRecognizer recognizer); + } + + public enum GestureRecognizerResult + { + None, + Capture, + ReleaseCapture + } +} diff --git a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs new file mode 100644 index 0000000000..e560230bfe --- /dev/null +++ b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -0,0 +1,129 @@ +using System; +using Avalonia.Interactivity; + +namespace Avalonia.Input.GestureRecognizers +{ + public class ScrollGestureRecognizer + : StyledElement, // It's not an "element" in any way, shape or form, but TemplateBinding refuse to work otherwise + IGestureRecognizer + { + private bool _scrolling; + private Point _trackedRootPoint; + private IPointer _tracking; + private IInputElement _target; + private IGestureRecognizerActionsDispatcher _actions; + private bool _canHorizontallyScroll; + private bool _canVerticallyScroll; + private int _gestureId; + + /// + /// Defines the property. + /// + public static readonly DirectProperty CanHorizontallyScrollProperty = + AvaloniaProperty.RegisterDirect( + nameof(CanHorizontallyScroll), + o => o.CanHorizontallyScroll, + (o, v) => o.CanHorizontallyScroll = v); + + /// + /// Defines the property. + /// + public static readonly DirectProperty CanVerticallyScrollProperty = + AvaloniaProperty.RegisterDirect( + nameof(CanVerticallyScroll), + o => o.CanVerticallyScroll, + (o, v) => o.CanVerticallyScroll = v); + + /// + /// Gets or sets a value indicating whether the content can be scrolled horizontally. + /// + public bool CanHorizontallyScroll + { + get => _canHorizontallyScroll; + set => SetAndRaise(CanHorizontallyScrollProperty, ref _canHorizontallyScroll, value); + } + + /// + /// Gets or sets a value indicating whether the content can be scrolled horizontally. + /// + public bool CanVerticallyScroll + { + get => _canVerticallyScroll; + set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); + } + + + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + { + _target = target; + _actions = actions; + } + + public void PointerPressed(PointerPressedEventArgs e) + { + if (e.Pointer.IsPrimary && e.Pointer.Type == PointerType.Touch) + { + _tracking = e.Pointer; + _scrolling = false; + _trackedRootPoint = e.GetPosition(null); + } + } + + // Arbitrary chosen value, probably need to move that to platform settings or something + private const double ScrollStartDistance = 30; + public void PointerMoved(PointerEventArgs e) + { + if (e.Pointer == _tracking) + { + var rootPoint = e.GetPosition(null); + if (!_scrolling) + { + if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) + _scrolling = true; + if (CanVerticallyScroll && Math.Abs(_trackedRootPoint.Y - rootPoint.Y) > ScrollStartDistance) + _scrolling = true; + if (_scrolling) + { + _actions.Capture(e.Pointer, this); + _gestureId = ScrollGestureEventArgs.GetNextFreeId(); + } + } + + if (_scrolling) + { + var vector = _trackedRootPoint - rootPoint; + _trackedRootPoint = rootPoint; + _target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); + e.Handled = true; + } + } + } + + public void PointerCaptureLost(PointerCaptureLostEventArgs e) + { + if (e.Pointer == _tracking) EndGesture(); + } + + void EndGesture() + { + _tracking = null; + if (_scrolling) + { + _scrolling = false; + _target.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); + } + + } + + + public void PointerReleased(PointerReleasedEventArgs e) + { + // TODO: handle inertia + if (e.Pointer == _tracking && _scrolling) + { + e.Handled = true; + EndGesture(); + } + } + } +} diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index 23b0ad466e..65195394ab 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -18,6 +18,14 @@ namespace Avalonia.Input RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent ScrollGestureEvent = + RoutedEvent.Register( + "ScrollGesture", RoutingStrategies.Bubble, typeof(Gestures)); + + public static readonly RoutedEvent ScrollGestureEndedEvent = + RoutedEvent.Register( + "ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); + private static WeakReference s_lastPress; static Gestures() diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 07e04486ec..7c687f0d7e 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Avalonia.Input.GestureRecognizers; using Avalonia.Interactivity; using Avalonia.VisualTree; @@ -127,6 +128,14 @@ namespace Avalonia.Input RoutedEvent.Register( "PointerReleased", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + + /// + /// Defines the routed event. + /// + public static readonly RoutedEvent PointerCaptureLostEvent = + RoutedEvent.Register( + "PointerCaptureLost", + RoutingStrategies.Direct); /// /// Defines the event. @@ -148,6 +157,7 @@ namespace Avalonia.Input private bool _isFocused; private bool _isPointerOver; + private GestureRecognizerCollection _gestureRecognizers; /// /// Initializes static members of the class. @@ -166,6 +176,7 @@ namespace Avalonia.Input PointerMovedEvent.AddClassHandler(x => x.OnPointerMoved); PointerPressedEvent.AddClassHandler(x => x.OnPointerPressed); PointerReleasedEvent.AddClassHandler(x => x.OnPointerReleased); + PointerCaptureLostEvent.AddClassHandler(x => x.OnPointerCaptureLost); PointerWheelChangedEvent.AddClassHandler(x => x.OnPointerWheelChanged); PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled"); @@ -263,6 +274,16 @@ namespace Avalonia.Input remove { RemoveHandler(PointerReleasedEvent, value); } } + /// + /// Occurs when the control or its child control loses the pointer capture for any reason, + /// event will not be triggered for a parent control if capture was transferred to another child of that parent control + /// + public event EventHandler PointerCaptureLost + { + add => AddHandler(PointerCaptureLostEvent, value); + remove => RemoveHandler(PointerCaptureLostEvent, value); + } + /// /// Occurs when the mouse wheen is scrolled over the control. /// @@ -370,6 +391,9 @@ namespace Avalonia.Input public List KeyBindings { get; } = new List(); + public GestureRecognizerCollection GestureRecognizers + => _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this)); + /// /// Focuses the control. /// @@ -460,6 +484,8 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerMoved(PointerEventArgs e) { + if (_gestureRecognizers?.HandlePointerMoved(e) == true) + e.Handled = true; } /// @@ -468,6 +494,8 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerPressed(PointerPressedEventArgs e) { + if (_gestureRecognizers?.HandlePointerPressed(e) == true) + e.Handled = true; } /// @@ -476,6 +504,17 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerReleased(PointerReleasedEventArgs e) { + if (_gestureRecognizers?.HandlePointerReleased(e) == true) + e.Handled = true; + } + + /// + /// Called before the event occurs. + /// + /// The event args. + protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e) + { + _gestureRecognizers?.HandlePointerCaptureLost(e); } /// diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 90d9c37bd4..05840660e2 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -14,18 +14,14 @@ namespace Avalonia.Input /// /// Represents a mouse device. /// - public class MouseDevice : IMouseDevice, IPointer + public class MouseDevice : IMouseDevice { private int _clickCount; private Rect _lastClickRect; private ulong _lastClickTime; - private IInputElement _captured; - private IDisposable _capturedSubscription; - PointerType IPointer.Type => PointerType.Mouse; - bool IPointer.IsPrimary => true; - int IPointer.Id { get; } = Pointer.GetNextFreeId(); - + private readonly Pointer _pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); + /// /// Gets the control that is currently capturing by the mouse, if any. /// @@ -34,27 +30,9 @@ namespace Avalonia.Input /// within the control's bounds or not. To set the mouse capture, call the /// method. /// - public IInputElement Captured - { - get => _captured; - protected set - { - _capturedSubscription?.Dispose(); - _capturedSubscription = null; - - if (value != null) - { - _capturedSubscription = Observable.FromEventPattern( - x => value.DetachedFromVisualTree += x, - x => value.DetachedFromVisualTree -= x) - .Take(1) - .Subscribe(_ => Captured = null); - } + [Obsolete("Use IPointer instead")] + public IInputElement Captured => _pointer.Captured; - _captured = value; - } - } - /// /// Gets the mouse position, in screen coordinates. /// @@ -75,8 +53,7 @@ namespace Avalonia.Input /// public virtual void Capture(IInputElement control) { - // TODO: Check visibility and enabled state before setting capture. - Captured = control; + _pointer.Capture(control); } /// @@ -110,13 +87,13 @@ namespace Avalonia.Input if (rect.Contains(clientPoint)) { - if (Captured == null) + if (_pointer.Captured == null) { SetPointerOver(this, root, clientPoint, InputModifiers.None); } else { - SetPointerOver(this, root, Captured, InputModifiers.None); + SetPointerOver(this, root, _pointer.Captured, InputModifiers.None); } } } @@ -212,8 +189,8 @@ namespace Avalonia.Input if (hit != null) { - IInteractive source = GetSource(hit); - + _pointer.Capture(hit); + var source = GetSource(hit); if (source != null) { var settings = AvaloniaLocator.Current.GetService(); @@ -229,8 +206,7 @@ namespace Avalonia.Input _lastClickRect = new Rect(p, new Size()) .Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2)); _lastMouseDownButton = properties.GetObsoleteMouseButton(); - var e = new PointerPressedEventArgs(source, this, root, p, properties, inputModifiers, _clickCount); - + var e = new PointerPressedEventArgs(source, _pointer, root, p, properties, inputModifiers, _clickCount); source.RaiseEvent(e); return e.Handled; } @@ -247,17 +223,17 @@ namespace Avalonia.Input IInputElement source; - if (Captured == null) + if (_pointer.Captured == null) { source = SetPointerOver(this, root, p, inputModifiers); } else { - SetPointerOver(this, root, Captured, inputModifiers); - source = Captured; + SetPointerOver(this, root, _pointer.Captured, inputModifiers); + source = _pointer.Captured; } - var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, this, root, + var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root, p, properties, inputModifiers); source?.RaiseEvent(e); @@ -275,9 +251,10 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerReleasedEventArgs(source, this, root, p, props, inputModifiers, _lastMouseDownButton); + var e = new PointerReleasedEventArgs(source, _pointer, root, p, props, inputModifiers, _lastMouseDownButton); source?.RaiseEvent(e); + _pointer.Capture(null); return e.Handled; } @@ -296,7 +273,7 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerWheelEventArgs(source, this, root, p, props, inputModifiers, delta); + var e = new PointerWheelEventArgs(source, _pointer, root, p, props, inputModifiers, delta); source?.RaiseEvent(e); return e.Handled; @@ -309,7 +286,7 @@ namespace Avalonia.Input { Contract.Requires(hit != null); - return Captured ?? + return _pointer.Captured ?? (hit as IInteractive) ?? hit.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); } @@ -318,12 +295,12 @@ namespace Avalonia.Input { Contract.Requires(root != null); - return Captured ?? root.InputHitTest(p); + return _pointer.Captured ?? root.InputHitTest(p); } PointerEventArgs CreateSimpleEvent(RoutedEvent ev, IInteractive source, InputModifiers inputModifiers) { - return new PointerEventArgs(ev, source, this, null, default, + return new PointerEventArgs(ev, source, _pointer, null, default, new PointerPointProperties(inputModifiers), inputModifiers); } diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs index bdf2501b32..14703986e2 100644 --- a/src/Avalonia.Input/Pointer.cs +++ b/src/Avalonia.Input/Pointer.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using Avalonia.Interactivity; using Avalonia.VisualTree; namespace Avalonia.Input @@ -9,23 +11,40 @@ namespace Avalonia.Input private static int s_NextFreePointerId = 1000; public static int GetNextFreeId() => s_NextFreePointerId++; - public Pointer(int id, PointerType type, bool isPrimary, IInputElement implicitlyCaptured) + public Pointer(int id, PointerType type, bool isPrimary) { Id = id; Type = type; IsPrimary = isPrimary; - ImplicitlyCaptured = implicitlyCaptured; - if (ImplicitlyCaptured != null) - ImplicitlyCaptured.DetachedFromVisualTree += OnImplicitCaptureDetached; } public int Id { get; } + IInputElement FindCommonParent(IInputElement control1, IInputElement control2) + { + if (control1 == null || control2 == null) + return null; + var seen = new HashSet(control1.GetSelfAndVisualAncestors().OfType()); + return control2.GetSelfAndVisualAncestors().OfType().FirstOrDefault(seen.Contains); + } + public void Capture(IInputElement control) { if (Captured != null) Captured.DetachedFromVisualTree -= OnCaptureDetached; + var oldCapture = control; Captured = control; + if (oldCapture != null) + { + var commonParent = FindCommonParent(control, oldCapture); + foreach (var notifyTarget in oldCapture.GetSelfAndVisualAncestors().OfType()) + { + if (notifyTarget == commonParent) + return; + notifyTarget.RaiseEvent(new PointerCaptureLostEventArgs(notifyTarget, this)); + } + } + if (Captured != null) Captured.DetachedFromVisualTree += OnCaptureDetached; } @@ -38,26 +57,11 @@ namespace Avalonia.Input Capture(GetNextCapture(e.Parent)); } - private void OnImplicitCaptureDetached(object sender, VisualTreeAttachmentEventArgs e) - { - ImplicitlyCaptured.DetachedFromVisualTree -= OnImplicitCaptureDetached; - ImplicitlyCaptured = GetNextCapture(e.Parent); - if (ImplicitlyCaptured != null) - ImplicitlyCaptured.DetachedFromVisualTree += OnImplicitCaptureDetached; - } public IInputElement Captured { get; private set; } - public IInputElement ImplicitlyCaptured { get; private set; } - public IInputElement GetEffectiveCapture() => Captured ?? ImplicitlyCaptured; public PointerType Type { get; } public bool IsPrimary { get; } - public void Dispose() - { - if (ImplicitlyCaptured != null) - ImplicitlyCaptured.DetachedFromVisualTree -= OnImplicitCaptureDetached; - if (Captured != null) - Captured.DetachedFromVisualTree -= OnCaptureDetached; - } + public void Dispose() => Capture(null); } } diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index 1d07190a81..37d9ade839 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/src/Avalonia.Input/PointerEventArgs.cs @@ -116,4 +116,15 @@ namespace Avalonia.Input [Obsolete()] public MouseButton MouseButton { get; private set; } } + + public class PointerCaptureLostEventArgs : RoutedEventArgs + { + public IPointer Pointer { get; } + + public PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) + { + Pointer = pointer; + Source = source; + } + } } diff --git a/src/Avalonia.Input/Properties/AssemblyInfo.cs b/src/Avalonia.Input/Properties/AssemblyInfo.cs index 7025965f83..3a8d358931 100644 --- a/src/Avalonia.Input/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Input/Properties/AssemblyInfo.cs @@ -5,3 +5,4 @@ using System.Reflection; using Avalonia.Metadata; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Input")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Input.GestureRecognizers")] diff --git a/src/Avalonia.Input/ScrollGestureEventArgs.cs b/src/Avalonia.Input/ScrollGestureEventArgs.cs new file mode 100644 index 0000000000..a682e8f0a4 --- /dev/null +++ b/src/Avalonia.Input/ScrollGestureEventArgs.cs @@ -0,0 +1,29 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class ScrollGestureEventArgs : RoutedEventArgs + { + public int Id { get; } + public Vector Delta { get; } + private static int _nextId = 1; + + public static int GetNextFreeId() => _nextId++; + + public ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent) + { + Id = id; + Delta = delta; + } + } + + public class ScrollGestureEndedEventArgs : RoutedEventArgs + { + public int Id { get; } + + public ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent) + { + Id = id; + } + } +} diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index e9715bd87c..8db2b125a6 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -35,28 +35,30 @@ namespace Avalonia.Input var hit = args.Root.InputHitTest(args.Position); _pointers[args.TouchPointId] = pointer = new Pointer(Pointer.GetNextFreeId(), - PointerType.Touch, _pointers.Count == 0, hit); + PointerType.Touch, _pointers.Count == 0); + pointer.Capture(hit); } - var target = pointer.GetEffectiveCapture() ?? args.Root; + var target = pointer.Captured ?? args.Root; if (args.Type == RawPointerEventType.TouchBegin) { - var modifiers = GetModifiers(args.InputModifiers, false); target.RaiseEvent(new PointerPressedEventArgs(target, pointer, - args.Root, args.Position, new PointerPointProperties(modifiers), - modifiers)); + args.Root, args.Position, + new PointerPointProperties(GetModifiers(args.InputModifiers, pointer.IsPrimary)), + GetModifiers(args.InputModifiers, false))); } if (args.Type == RawPointerEventType.TouchEnd) { _pointers.Remove(args.TouchPointId); - var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary); using (pointer) { target.RaiseEvent(new PointerReleasedEventArgs(target, pointer, - args.Root, args.Position, new PointerPointProperties(modifiers), - modifiers, pointer.IsPrimary ? MouseButton.Left : MouseButton.None)); + args.Root, args.Position, + new PointerPointProperties(GetModifiers(args.InputModifiers, false)), + GetModifiers(args.InputModifiers, pointer.IsPrimary), + pointer.IsPrimary ? MouseButton.Left : MouseButton.None)); } } diff --git a/src/Avalonia.Themes.Default/ScrollViewer.xaml b/src/Avalonia.Themes.Default/ScrollViewer.xaml index 63440921d6..3e130cad67 100644 --- a/src/Avalonia.Themes.Default/ScrollViewer.xaml +++ b/src/Avalonia.Themes.Default/ScrollViewer.xaml @@ -12,7 +12,14 @@ Extent="{TemplateBinding Extent, Mode=TwoWay}" Margin="{TemplateBinding Padding}" Offset="{TemplateBinding Offset, Mode=TwoWay}" - Viewport="{TemplateBinding Viewport, Mode=TwoWay}"/> + Viewport="{TemplateBinding Viewport, Mode=TwoWay}"> + + + + - \ No newline at end of file + From 04b5b5468f5560e71659cf334d86a9ba37a7d7de Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 18:36:32 +0800 Subject: [PATCH 074/130] Disable legacy GridLength.Star algorithms. Turn DefBase[] into IReadOnlyList. Fix DefBase, turn it into AvaloniaObject, change WPF types to Avalonia's and add a Parent prop. --- src/Avalonia.Controls/DefinitionBase.cs | 53 +++---- src/Avalonia.Controls/Grid.cs | 190 ++++++++++++------------ 2 files changed, 123 insertions(+), 120 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 81c88b2638..7099befdcd 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using Avalonia; using Avalonia.Collections; +using Avalonia.Utilities; namespace Avalonia.Controls { @@ -17,7 +18,7 @@ namespace Avalonia.Controls /// DefinitionBase provides core functionality used internally by Grid /// and ColumnDefinitionCollection / RowDefinitionCollection /// - public abstract class DefinitionBase : FrameworkContentElement + public abstract class DefinitionBase : AvaloniaObject { //------------------------------------------------------ // @@ -27,11 +28,11 @@ namespace Avalonia.Controls #region Constructors - internal DefinitionBase(bool isColumnDefinition) + /* internal DefinitionBase(bool isColumnDefinition) { _isColumnDefinition = isColumnDefinition; _parentIndex = -1; - } + }*/ #endregion Constructors @@ -134,7 +135,7 @@ namespace Avalonia.Controls /// /// This method needs to be internal to be accessable from derived classes. /// - internal static void OnUserSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + internal static void OnUserSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { DefinitionBase definition = (DefinitionBase) d; @@ -161,7 +162,7 @@ namespace Avalonia.Controls } /// - /// + /// /// /// /// This method needs to be internal to be accessable from derived classes. @@ -177,7 +178,7 @@ namespace Avalonia.Controls /// /// This method needs to be internal to be accessable from derived classes. /// - internal static void OnUserMinSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + internal static void OnUserMinSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { DefinitionBase definition = (DefinitionBase) d; @@ -189,7 +190,7 @@ namespace Avalonia.Controls } /// - /// + /// /// /// /// This method needs to be internal to be accessable from derived classes. @@ -197,7 +198,7 @@ namespace Avalonia.Controls internal static bool IsUserMinSizePropertyValueValid(object value) { double v = (double)value; - return (!DoubleUtil.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); + return (!double.IsNaN(v) && v >= 0.0d && !Double.IsPositiveInfinity(v)); } /// @@ -206,7 +207,7 @@ namespace Avalonia.Controls /// /// This method needs to be internal to be accessable from derived classes. /// - internal static void OnUserMaxSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + internal static void OnUserMaxSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { DefinitionBase definition = (DefinitionBase) d; @@ -218,7 +219,7 @@ namespace Avalonia.Controls } /// - /// + /// /// /// /// This method needs to be internal to be accessable from derived classes. @@ -226,7 +227,7 @@ namespace Avalonia.Controls internal static bool IsUserMaxSizePropertyValueValid(object value) { double v = (double)value; - return (!DoubleUtil.IsNaN(v) && v >= 0.0d); + return (!double.IsNaN(v) && v >= 0.0d); } /// @@ -240,7 +241,7 @@ namespace Avalonia.Controls /// elements belonging to a certain scope can easily access SharedSizeState collection. As well /// as been norified about enter / exit a scope. /// - internal static void OnIsSharedSizeScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + internal static void OnIsSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { // is it possible to optimize here something like this: // if ((bool)d.GetValue(Grid.IsSharedSizeScopeProperty) == (d.GetLocalValue(PrivateSharedSizeScopeProperty) != null) @@ -456,6 +457,8 @@ namespace Avalonia.Controls get { return (_parentIndex != -1); } } + internal Grid Parent { get; set; } + #endregion Internal Properties //------------------------------------------------------ @@ -487,7 +490,7 @@ namespace Avalonia.Controls /// /// /// - private static void OnSharedSizeGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { DefinitionBase definition = (DefinitionBase) d; @@ -518,7 +521,7 @@ namespace Avalonia.Controls } /// - /// + /// /// /// /// Verifies that Shared Size Group Property string @@ -569,7 +572,7 @@ namespace Avalonia.Controls /// existing scope just left. In both cases if the DefinitionBase object is already registered /// in SharedSizeState, it should un-register and register itself in a new one. /// - private static void OnPrivateSharedSizeScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnPrivateSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { DefinitionBase definition = (DefinitionBase)d; @@ -646,7 +649,7 @@ namespace Avalonia.Controls #region Private Fields private readonly bool _isColumnDefinition; // when "true", this is a ColumnDefinition; when "false" this is a RowDefinition (faster than a type check) private Flags _flags; // flags reflecting various aspects of internal state - private int _parentIndex; // this instance's index in parent's children collection + private int _parentIndex = -1; // this instance's index in parent's children collection private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" @@ -783,7 +786,7 @@ namespace Avalonia.Controls /// /// Makes sure that one and only one layout updated handler is registered for this shared state. /// - internal void EnsureDeferredValidation(UIElement layoutUpdatedHost) + internal void EnsureDeferredValidation(Control layoutUpdatedHost) { if (_layoutUpdatedHost == null) { @@ -861,7 +864,7 @@ namespace Avalonia.Controls sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); } - bool sharedMinSizeChanged = !DoubleUtil.AreClose(_minSize, sharedMinSize); + bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); // compare accumulated min size with min sizes of the individual definitions for (int i = 0, count = _registry.Count; i < count; ++i) @@ -871,7 +874,7 @@ namespace Avalonia.Controls if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) { // if definition's min size is different, then need to re-measure - if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.MinSize)) + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) { Grid parentGrid = (Grid)definitionBase.Parent; parentGrid.InvalidateMeasure(); @@ -884,7 +887,7 @@ namespace Avalonia.Controls // if measure is valid then also need to check arrange. // Note: definitionBase.SizeCache is volatile but at this point // it contains up-to-date final size - if (!DoubleUtil.AreClose(sharedMinSize, definitionBase.SizeCache)) + if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) { Grid parentGrid = (Grid)definitionBase.Parent; parentGrid.InvalidateArrange(); @@ -907,7 +910,7 @@ namespace Avalonia.Controls private readonly string _sharedSizeGroupId; // Id of the shared size group this object is servicing private readonly List _registry; // registry of participating definitions private readonly EventHandler _layoutUpdated; // instance event handler for layout updated event - private UIElement _layoutUpdatedHost; // UIElement for which layout updated event handler is registered + private Control _layoutUpdatedHost; // Control for which layout updated event handler is registered private bool _broadcastInvalidation; // "true" when broadcasting of invalidation is needed private bool _userSizeValid; // "true" when _userSize is up to date private GridLength _userSize; // shared state @@ -928,8 +931,8 @@ namespace Avalonia.Controls /// Private shared size scope property holds a collection of shared state objects for the a given shared size scope. /// /// - internal static readonly DependencyProperty PrivateSharedSizeScopeProperty = - DependencyProperty.RegisterAttached( + internal static readonly AvaloniaProperty PrivateSharedSizeScopeProperty = + AvaloniaProperty.RegisterAttached( "PrivateSharedSizeScope", typeof(SharedSizeScope), typeof(DefinitionBase), @@ -954,8 +957,8 @@ namespace Avalonia.Controls /// /// /// - public static readonly DependencyProperty SharedSizeGroupProperty = - DependencyProperty.Register( + public static readonly AvaloniaProperty SharedSizeGroupProperty = + AvaloniaProperty.Register( "SharedSizeGroup", typeof(string), typeof(DefinitionBase), diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 1bb2cd556f..d32534faa5 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -209,7 +209,7 @@ namespace Avalonia.Controls get { if (_data == null) { _data = new ExtendedData(); } - if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions(this); } + if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions(); } return (_data.ColumnDefinitions); } @@ -223,7 +223,7 @@ namespace Avalonia.Controls get { if (_data == null) { _data = new ExtendedData(); } - if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions(this); } + if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions(); } return (_data.RowDefinitions); } @@ -347,7 +347,7 @@ namespace Avalonia.Controls ValidateCells(); - Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); + Debug.Assert(DefinitionsU.Count > 0 && DefinitionsV.Count > 0); // Grid classifies cells into four groups depending on // the column / row type a cell belongs to (number corresponds to @@ -606,7 +606,7 @@ namespace Avalonia.Controls } else { - Debug.Assert(DefinitionsU.Length > 0 && DefinitionsV.Length > 0); + Debug.Assert(DefinitionsU.Count > 0 && DefinitionsV.Count > 0); @@ -661,14 +661,14 @@ namespace Avalonia.Controls /// /// /// - protected internal override void OnVisualChildrenChanged( + /*protected internal override void OnVisualChildrenChanged( AvaloniaObject visualAdded, AvaloniaObject visualRemoved) { CellsStructureDirty = true; base.OnVisualChildrenChanged(visualAdded, visualRemoved); - } + }*/ //------------------------------------------------------ // @@ -700,8 +700,8 @@ namespace Avalonia.Controls // actual value calculations require structure to be up-to-date if (!ColumnDefinitionsDirty) { - DefinitionBase[] definitions = DefinitionsU; - value = definitions[(columnIndex + 1) % definitions.Length].FinalOffset; + IReadOnlyList definitions = DefinitionsU; + value = definitions[(columnIndex + 1) % definitions.Count].FinalOffset; if (columnIndex != 0) { value -= definitions[columnIndex].FinalOffset; } } return (value); @@ -722,8 +722,8 @@ namespace Avalonia.Controls // actual value calculations require structure to be up-to-date if (!RowDefinitionsDirty) { - DefinitionBase[] definitions = DefinitionsV; - value = definitions[(rowIndex + 1) % definitions.Length].FinalOffset; + IReadOnlyList definitions = DefinitionsV; + value = definitions[(rowIndex + 1) % definitions.Count].FinalOffset; if (rowIndex != 0) { value -= definitions[rowIndex].FinalOffset; } } return (value); @@ -828,22 +828,22 @@ namespace Avalonia.Controls // read indices from the corresponding properties // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Length - 1); + cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Count - 1); // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Length - 1); + cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Count - 1); // read span properties // clamp to not exceed beyond right side of the grid // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Length - cell.ColumnIndex); + cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Count - cell.ColumnIndex); // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Length - cell.RowIndex); + cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Count - cell.RowIndex); - Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Length); - Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Length); + Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Count); + Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Count); // // calculate and cache length types for the child @@ -924,9 +924,9 @@ namespace Avalonia.Controls } else { - extData.ColumnDefinitions.InternalTrimToSize(); + // extData.ColumnDefinitions.InternalTrimToSize(); - if (extData.ColumnDefinitions.InternalCount == 0) + if (extData.ColumnDefinitions.Count == 0) { // if column definitions collection is empty // mockup array with one column @@ -934,14 +934,14 @@ namespace Avalonia.Controls } else { - extData.DefinitionsU = extData.ColumnDefinitions.InternalItems; + extData.DefinitionsU = extData.ColumnDefinitions; } } ColumnDefinitionsDirty = false; } - Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Length > 0); + Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Count > 0); } @@ -971,9 +971,9 @@ namespace Avalonia.Controls } else { - extData.RowDefinitions.InternalTrimToSize(); + // extData.RowDefinitions.InternalTrimToSize(); - if (extData.RowDefinitions.InternalCount == 0) + if (extData.RowDefinitions.Count == 0) { // if row definitions collection is empty // mockup array with one row @@ -981,14 +981,14 @@ namespace Avalonia.Controls } else { - extData.DefinitionsV = extData.RowDefinitions.InternalItems; + extData.DefinitionsV = extData.RowDefinitions; } } RowDefinitionsDirty = false; } - Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Length > 0); + Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Count > 0); } @@ -1000,10 +1000,10 @@ namespace Avalonia.Controls /// Array of definitions to update. /// if "true" then star definitions are treated as Auto. private void ValidateDefinitionsLayout( - DefinitionBase[] definitions, + IReadOnlyList definitions, bool treatStarAsAuto) { - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { definitions[i].OnBeforeLayout(this); @@ -1047,7 +1047,7 @@ namespace Avalonia.Controls private double[] CacheMinSizes(int cellsHead, bool isRows) { - double[] minSizes = isRows ? new double[DefinitionsV.Length] : new double[DefinitionsU.Length]; + double[] minSizes = isRows ? new double[DefinitionsV.Count] : new double[DefinitionsU.Count]; for (int j=0; j private double GetMeasureSizeForRange( - DefinitionBase[] definitions, + IReadOnlyList definitions, int start, int count) { - Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); + Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Count); double measureSize = 0; int i = start + count - 1; @@ -1324,11 +1324,11 @@ namespace Avalonia.Controls /// Number of definitions included in the range. /// Length type for given range. private LayoutTimeSizeType GetLengthTypeForRange( - DefinitionBase[] definitions, + IReadOnlyList definitions, int start, int count) { - Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Length); + Debug.Assert(0 < count && 0 <= start && (start + count) <= definitions.Count); LayoutTimeSizeType lengthType = LayoutTimeSizeType.None; int i = start + count - 1; @@ -1350,13 +1350,13 @@ namespace Avalonia.Controls /// Definition array receiving distribution. /// Size used to resolve percentages. private void EnsureMinSizeInDefinitionRange( - DefinitionBase[] definitions, + IReadOnlyList definitions, int start, int count, double requestedSize, double percentReferenceSize) { - Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Length); + Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Count); // avoid processing when asked to distribute "0" if (!_IsZero(requestedSize)) @@ -1536,29 +1536,29 @@ namespace Avalonia.Controls /// Must initialize LayoutSize for all Star entries in given array of definitions. /// private void ResolveStar( - DefinitionBase[] definitions, + IReadOnlyList definitions, double availableSize) { - if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) - { - ResolveStarLegacy(definitions, availableSize); - } - else - { + // if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) + // { + // ResolveStarLegacy(definitions, availableSize); + // } + // else + // { ResolveStarMaxDiscrepancy(definitions, availableSize); - } + // } } // original implementation, used from 3.0 through 4.6.2 private void ResolveStarLegacy( - DefinitionBase[] definitions, + IReadOnlyList definitions, double availableSize) { DefinitionBase[] tempDefinitions = TempDefinitions; int starDefinitionsCount = 0; double takenSize = 0; - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { switch (definitions[i].SizeType) { @@ -1651,10 +1651,10 @@ namespace Avalonia.Controls // change in available space resulting in large change to one def's allocation. // 3. Correct handling of large *-values, including Infinity. private void ResolveStarMaxDiscrepancy( - DefinitionBase[] definitions, + IReadOnlyList definitions, double availableSize) { - int defCount = definitions.Length; + int defCount = definitions.Count; DefinitionBase[] tempDefinitions = TempDefinitions; int minCount = 0, maxCount = 0; double takenSize = 0; @@ -1948,11 +1948,11 @@ namespace Avalonia.Controls /// Array of definitions to use for calculations. /// Desired size. private double CalculateDesiredSize( - DefinitionBase[] definitions) + IReadOnlyList definitions) { double desiredSize = 0; - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { desiredSize += definitions[i].MinSize; } @@ -1967,28 +1967,28 @@ namespace Avalonia.Controls /// Final size to lay out to. /// True if sizing row definitions, false for columns private void SetFinalSize( - DefinitionBase[] definitions, + IReadOnlyList definitions, double finalSize, bool columns) { - if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) - { - SetFinalSizeLegacy(definitions, finalSize, columns); - } - else - { + // if (FrameworkAppContextSwitches.GridStarDefinitionsCanExceedAvailableSpace) + // { + // SetFinalSizeLegacy(definitions, finalSize, columns); + // } + // else + // { SetFinalSizeMaxDiscrepancy(definitions, finalSize, columns); - } + // } } // original implementation, used from 3.0 through 4.6.2 private void SetFinalSizeLegacy( - DefinitionBase[] definitions, + IReadOnlyList definitions, double finalSize, bool columns) { int starDefinitionsCount = 0; // traverses form the first entry up - int nonStarIndex = definitions.Length; // traverses from the last entry down + int nonStarIndex = definitions.Count; // traverses from the last entry down double allPreferredArrangeSize = 0; bool useLayoutRounding = this.UseLayoutRounding; int[] definitionIndices = DefinitionIndices; @@ -2004,7 +2004,7 @@ namespace Avalonia.Controls roundingErrors = RoundingErrors; } - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { // if definition is shared then is cannot be star Debug.Assert(!definitions[i].IsShared || !definitions[i].UserSize.IsStar); @@ -2134,13 +2134,13 @@ namespace Avalonia.Controls && !_AreClose(allPreferredArrangeSize, finalSize) ) { DistributionOrderIndexComparer distributionOrderIndexComparer = new DistributionOrderIndexComparer(definitions); - Array.Sort(definitionIndices, 0, definitions.Length, distributionOrderIndexComparer); + Array.Sort(definitionIndices, 0, definitions.Count, distributionOrderIndexComparer); double sizeToDistribute = finalSize - allPreferredArrangeSize; - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { int definitionIndex = definitionIndices[i]; - double final = definitions[definitionIndex].SizeCache + (sizeToDistribute / (definitions.Length - i)); + double final = definitions[definitionIndex].SizeCache + (sizeToDistribute / (definitions.Count - i)); double finalOld = final; final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); final = Math.Min(final, definitions[definitionIndex].SizeCache); @@ -2165,7 +2165,7 @@ namespace Avalonia.Controls if (!_AreClose(allPreferredArrangeSize, finalSize)) { // Compute deltas - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { roundingErrors[i] = roundingErrors[i] - definitions[i].SizeCache; definitionIndices[i] = i; @@ -2173,13 +2173,13 @@ namespace Avalonia.Controls // Sort rounding errors RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); - Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); + Array.Sort(definitionIndices, 0, definitions.Count, roundingErrorIndexComparer); double adjustedSize = allPreferredArrangeSize; double dpiIncrement = Control.RoundLayoutValue(1.0, dpi); if (allPreferredArrangeSize > finalSize) { - int i = definitions.Length - 1; + int i = definitions.Count - 1; while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) { DefinitionBase definition = definitions[definitionIndices[i]]; @@ -2196,7 +2196,7 @@ namespace Avalonia.Controls else if (allPreferredArrangeSize < finalSize) { int i = 0; - while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length) + while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Count) { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache + dpiIncrement; @@ -2213,9 +2213,9 @@ namespace Avalonia.Controls } definitions[0].FinalOffset = 0.0; - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { - definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; + definitions[(i + 1) % definitions.Count].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; } } @@ -2232,11 +2232,11 @@ namespace Avalonia.Controls // comes into play at high DPI - greater than 134. // 3. Applies rounding only to real pixel values (not to ratios) private void SetFinalSizeMaxDiscrepancy( - DefinitionBase[] definitions, + IReadOnlyList definitions, double finalSize, bool columns) { - int defCount = definitions.Length; + int defCount = definitions.Count; int[] definitionIndices = DefinitionIndices; int minCount = 0, maxCount = 0; double takenSize = 0.0; @@ -2571,7 +2571,7 @@ namespace Avalonia.Controls double roundedTakenSize = 0.0; // round each of the allocated sizes, keeping track of the deltas - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { DefinitionBase def = definitions[i]; double roundedSize = Control.RoundLayoutValue(def.SizeCache, dpi); @@ -2625,20 +2625,20 @@ namespace Avalonia.Controls if (!_AreClose(roundedTakenSize, finalSize)) { // Compute deltas - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { definitionIndices[i] = i; } // Sort rounding errors RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); - Array.Sort(definitionIndices, 0, definitions.Length, roundingErrorIndexComparer); + Array.Sort(definitionIndices, 0, definitions.Count, roundingErrorIndexComparer); double adjustedSize = roundedTakenSize; double dpiIncrement = 1.0/dpi; if (roundedTakenSize > finalSize) { - int i = definitions.Length - 1; + int i = definitions.Count - 1; while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) { DefinitionBase definition = definitions[definitionIndices[i]]; @@ -2655,7 +2655,7 @@ namespace Avalonia.Controls else if (roundedTakenSize < finalSize) { int i = 0; - while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Length) + while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Count) { DefinitionBase definition = definitions[definitionIndices[i]]; double final = definition.SizeCache + dpiIncrement; @@ -2673,9 +2673,9 @@ namespace Avalonia.Controls // Phase 6. Compute final offsets definitions[0].FinalOffset = 0.0; - for (int i = 0; i < definitions.Length; ++i) + for (int i = 0; i < definitions.Count; ++i) { - definitions[(i + 1) % definitions.Length].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; + definitions[(i + 1) % definitions.Count].FinalOffset = definitions[i].FinalOffset + definitions[i].SizeCache; } } @@ -2753,7 +2753,7 @@ namespace Avalonia.Controls /// Number of items in the range. /// Final size. private double GetFinalSizeForRange( - DefinitionBase[] definitions, + IReadOnlyList definitions, int start, int count) { @@ -2782,7 +2782,7 @@ namespace Avalonia.Controls if (extData.TempDefinitions != null) { // TempDefinitions has to be cleared to avoid "memory leaks" - Array.Clear(extData.TempDefinitions, 0, Math.Max(DefinitionsU.Length, DefinitionsV.Length)); + Array.Clear(extData.TempDefinitions, 0, Math.Max(DefinitionsU.Count, DefinitionsV.Count)); extData.TempDefinitions = null; } } @@ -2960,7 +2960,7 @@ namespace Avalonia.Controls /// /// Private version returning array of column definitions. /// - private DefinitionBase[] DefinitionsU + private IReadOnlyList DefinitionsU { get { return (ExtData.DefinitionsU); } } @@ -2968,7 +2968,7 @@ namespace Avalonia.Controls /// /// Private version returning array of row definitions. /// - private DefinitionBase[] DefinitionsV + private IReadOnlyList DefinitionsV { get { return (ExtData.DefinitionsV); } } @@ -2981,7 +2981,7 @@ namespace Avalonia.Controls get { ExtendedData extData = ExtData; - int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length) * 2; + int requiredLength = Math.Max(DefinitionsU.Count, DefinitionsV.Count) * 2; if ( extData.TempDefinitions == null || extData.TempDefinitions.Length < requiredLength ) @@ -3014,7 +3014,7 @@ namespace Avalonia.Controls { get { - int requiredLength = Math.Max(Math.Max(DefinitionsU.Length, DefinitionsV.Length), 1) * 2; + int requiredLength = Math.Max(Math.Max(DefinitionsU.Count, DefinitionsV.Count), 1) * 2; if (_definitionIndices == null || _definitionIndices.Length < requiredLength) { @@ -3032,7 +3032,7 @@ namespace Avalonia.Controls { get { - int requiredLength = Math.Max(DefinitionsU.Length, DefinitionsV.Length); + int requiredLength = Math.Max(DefinitionsU.Count, DefinitionsV.Count); if (_roundingErrors == null && requiredLength == 0) { @@ -3209,8 +3209,8 @@ namespace Avalonia.Controls { internal ColumnDefinitions ColumnDefinitions; // collection of column definitions (logical tree support) internal RowDefinitions RowDefinitions; // collection of row definitions (logical tree support) - internal DefinitionBase[] DefinitionsU; // collection of column definitions used during calc - internal DefinitionBase[] DefinitionsV; // collection of row definitions used during calc + internal IReadOnlyList DefinitionsU; // collection of column definitions used during calc + internal IReadOnlyList DefinitionsV; // collection of row definitions used during calc internal CellCache[] CellCachesCollection; // backing store for logical children internal int CellGroup1; // index of the first cell in first cell group internal int CellGroup2; // index of the first cell in second cell group @@ -3589,9 +3589,9 @@ namespace Avalonia.Controls /// private class StarDistributionOrderIndexComparer : IComparer { - private readonly DefinitionBase[] definitions; + private readonly IReadOnlyList definitions; - internal StarDistributionOrderIndexComparer(DefinitionBase[] definitions) + internal StarDistributionOrderIndexComparer(IReadOnlyList definitions) { Contract.Requires(definitions != null); this.definitions = definitions; @@ -3630,9 +3630,9 @@ namespace Avalonia.Controls /// private class DistributionOrderIndexComparer : IComparer { - private readonly DefinitionBase[] definitions; + private readonly IReadOnlyList definitions; - internal DistributionOrderIndexComparer(DefinitionBase[] definitions) + internal DistributionOrderIndexComparer(IReadOnlyList definitions) { Contract.Requires(definitions != null); this.definitions = definitions; @@ -3772,9 +3772,9 @@ namespace Avalonia.Controls /// private class MinRatioIndexComparer : IComparer { - private readonly DefinitionBase[] definitions; + private readonly IReadOnlyList definitions; - internal MinRatioIndexComparer(DefinitionBase[] definitions) + internal MinRatioIndexComparer(IReadOnlyList definitions) { Contract.Requires(definitions != null); this.definitions = definitions; @@ -3813,9 +3813,9 @@ namespace Avalonia.Controls /// private class MaxRatioIndexComparer : IComparer { - private readonly DefinitionBase[] definitions; + private readonly IReadOnlyList definitions; - internal MaxRatioIndexComparer(DefinitionBase[] definitions) + internal MaxRatioIndexComparer(IReadOnlyList definitions) { Contract.Requires(definitions != null); this.definitions = definitions; @@ -3854,9 +3854,9 @@ namespace Avalonia.Controls /// private class StarWeightIndexComparer : IComparer { - private readonly DefinitionBase[] definitions; + private readonly IReadOnlyList definitions; - internal StarWeightIndexComparer(DefinitionBase[] definitions) + internal StarWeightIndexComparer(IReadOnlyList definitions) { Contract.Requires(definitions != null); this.definitions = definitions; From 53423882bcf2cbb293e7194291d1d98d8bb10793 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 18:48:50 +0800 Subject: [PATCH 075/130] Refactor child references. --- src/Avalonia.Controls/Grid.cs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index d32534faa5..72e915f460 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -295,16 +295,16 @@ namespace Avalonia.Controls if (extData == null) { gridDesiredSize = new Size(); - Controls children = InternalChildren; + var children = this.Children; for (int i = 0, count = children.Count; i < count; ++i) { - Control child = children[i]; + var child = children[i]; if (child != null) { child.Measure(constraint); - gridDesiredSize.Width = Math.Max(gridDesiredSize.Width, child.DesiredSize.Width); - gridDesiredSize.Height = Math.Max(gridDesiredSize.Height, child.DesiredSize.Height); + gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), + Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); } } } @@ -593,11 +593,11 @@ namespace Avalonia.Controls if (_data == null) { - Controls children = InternalChildren; + var children = this.Children; for (int i = 0, count = children.Count; i < count; ++i) { - Control child = children[i]; + var child = children[i]; if (child != null) { child.Arrange(new Rect(arrangeSize)); @@ -615,11 +615,11 @@ namespace Avalonia.Controls - Controls children = InternalChildren; + var children = this.Children; for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) { - Control cell = children[currentCell]; + var cell = children[currentCell]; if (cell == null) { continue; @@ -798,7 +798,7 @@ namespace Avalonia.Controls /// private void ValidateCellsCore() { - Controls children = InternalChildren; + var children = this.Children; ExtendedData extData = ExtData; extData.CellCachesCollection = new CellCache[children.Count]; @@ -813,7 +813,7 @@ namespace Avalonia.Controls for (int i = PrivateCells.Length - 1; i >= 0; --i) { - Control child = children[i]; + var child = children[i]; if (child == null) { continue; @@ -828,19 +828,19 @@ namespace Avalonia.Controls // read indices from the corresponding properties // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Count - 1); + cell.ColumnIndex = Math.Min(GetColumn((Control)child), DefinitionsU.Count - 1); // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Count - 1); + cell.RowIndex = Math.Min(GetRow((Control)child), DefinitionsV.Count - 1); // read span properties // clamp to not exceed beyond right side of the grid // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Count - cell.ColumnIndex); + cell.ColumnSpan = Math.Min(GetColumnSpan((Control)child), DefinitionsU.Count - cell.ColumnIndex); // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Count - cell.RowIndex); + cell.RowSpan = Math.Min(GetRowSpan((Control)child), DefinitionsV.Count - cell.RowIndex); Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Count); Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Count); @@ -1076,7 +1076,7 @@ namespace Avalonia.Controls { for (int i=0; i Date: Thu, 30 May 2019 18:57:09 +0800 Subject: [PATCH 076/130] Add WPF's UIElement.RoundLayoutValue to MathUtilities. --- src/Avalonia.Base/Utilities/MathUtilities.cs | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index dc47584f32..ef7a2a37ea 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -114,6 +114,39 @@ namespace Avalonia.Utilities } } + /// + /// Calculates the value to be used for layout rounding at high DPI. + /// + /// Input value to be rounded. + /// Ratio of screen's DPI to layout DPI + /// Adjusted value that will produce layout rounding on screen at high dpi. + /// This is a layout helper method. It takes DPI into account and also does not return + /// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper associated with + /// UseLayoutRounding property and should not be used as a general rounding utility. + public static double RoundLayoutValue(double value, double dpiScale) + { + double newValue; + + // If DPI == 1, don't use DPI-aware rounding. + if (!MathUtilities.AreClose(dpiScale, 1.0)) + { + newValue = Math.Round(value * dpiScale) / dpiScale; + // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. + if (double.IsNaN(newValue) || + double.IsInfinity(newValue) || + MathUtilities.AreClose(newValue, double.MaxValue)) + { + newValue = value; + } + } + else + { + newValue = Math.Round(value); + } + + return newValue; + } + /// /// Clamps a value between a minimum and maximum value. /// From 4c0f3651185d3b237ebbd4194aae949fc1b902e8 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 18:57:52 +0800 Subject: [PATCH 077/130] Fix Grid dpi and roundedlayout stuff + add GridLinesRenderer as Grid's visual child. --- src/Avalonia.Controls/Grid.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 72e915f460..c3da8b7a34 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1999,8 +1999,9 @@ namespace Avalonia.Controls if (useLayoutRounding) { - DpiScale dpiScale = GetDpi(); - dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; + // DpiScale dpiScale = GetDpi(); + // dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; + dpi = (VisualRoot as Layout.ILayoutRoot)?.LayoutScaling ?? 1.0; roundingErrors = RoundingErrors; } @@ -2033,7 +2034,7 @@ namespace Avalonia.Controls if (useLayoutRounding) { roundingErrors[i] = definitions[i].SizeCache; - definitions[i].SizeCache = Control.RoundLayoutValue(definitions[i].SizeCache, dpi); + definitions[i].SizeCache = MathUtilities.RoundLayoutValue(definitions[i].SizeCache, dpi); } } definitionIndices[starDefinitionsCount++] = i; @@ -2072,7 +2073,7 @@ namespace Avalonia.Controls if (useLayoutRounding) { roundingErrors[i] = definitions[i].SizeCache; - definitions[i].SizeCache = Control.RoundLayoutValue(definitions[i].SizeCache, dpi); + definitions[i].SizeCache = MathUtilities.RoundLayoutValue(definitions[i].SizeCache, dpi); } allPreferredArrangeSize += definitions[i].SizeCache; @@ -2123,7 +2124,7 @@ namespace Avalonia.Controls if (useLayoutRounding) { roundingErrors[definitionIndices[i]] = definitions[definitionIndices[i]].SizeCache; - definitions[definitionIndices[i]].SizeCache = Control.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi); + definitions[definitionIndices[i]].SizeCache = MathUtilities.RoundLayoutValue(definitions[definitionIndices[i]].SizeCache, dpi); } allPreferredArrangeSize += definitions[definitionIndices[i]].SizeCache; @@ -2148,7 +2149,7 @@ namespace Avalonia.Controls if (useLayoutRounding) { roundingErrors[definitionIndex] = final; - final = Control.RoundLayoutValue(finalOld, dpi); + final = MathUtilities.RoundLayoutValue(finalOld, dpi); final = Math.Max(final, definitions[definitionIndex].MinSizeForArrange); final = Math.Min(final, definitions[definitionIndex].SizeCache); } @@ -2175,7 +2176,7 @@ namespace Avalonia.Controls RoundingErrorIndexComparer roundingErrorIndexComparer = new RoundingErrorIndexComparer(roundingErrors); Array.Sort(definitionIndices, 0, definitions.Count, roundingErrorIndexComparer); double adjustedSize = allPreferredArrangeSize; - double dpiIncrement = Control.RoundLayoutValue(1.0, dpi); + double dpiIncrement = MathUtilities.RoundLayoutValue(1.0, dpi); if (allPreferredArrangeSize > finalSize) { @@ -2565,8 +2566,9 @@ namespace Avalonia.Controls // unrounded sizes, to avoid breaking assumptions in the previous phases if (UseLayoutRounding) { - DpiScale dpiScale = GetDpi(); - double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; + // DpiScale dpiScale = GetDpi(); + // double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; + var dpi = (VisualRoot as Layout.ILayoutRoot)?.LayoutScaling ?? 1.0; double[] roundingErrors = RoundingErrors; double roundedTakenSize = 0.0; @@ -2574,7 +2576,7 @@ namespace Avalonia.Controls for (int i = 0; i < definitions.Count; ++i) { DefinitionBase def = definitions[i]; - double roundedSize = Control.RoundLayoutValue(def.SizeCache, dpi); + double roundedSize = MathUtilities.RoundLayoutValue(def.SizeCache, dpi); roundingErrors[i] = (roundedSize - def.SizeCache); def.SizeCache = roundedSize; roundedTakenSize += roundedSize; @@ -2823,12 +2825,12 @@ namespace Avalonia.Controls if (ShowGridLines && (_gridLinesRenderer == null)) { _gridLinesRenderer = new GridLinesRenderer(); - this.AddVisualChild(_gridLinesRenderer); + this.VisualChildren.Add(_gridLinesRenderer); } if ((!ShowGridLines) && (_gridLinesRenderer != null)) { - this.RemoveVisualChild(_gridLinesRenderer); + this.VisualChildren.Add(_gridLinesRenderer); _gridLinesRenderer = null; } From 136acf2d61d77d8cc806644cc86b46b1ba9269f4 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 19:02:59 +0800 Subject: [PATCH 078/130] Fix definitionbase attached properties. --- src/Avalonia.Controls/DefinitionBase.cs | 41 +++++++++---------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 7099befdcd..dcb375745d 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -529,13 +529,9 @@ namespace Avalonia.Controls /// b) contains only letters, digits and underscore ('_'). /// c) does not start with a digit. /// - private static bool SharedSizeGroupPropertyValueValid(object value) + private static string SharedSizeGroupPropertyValueValid(Control _, string value) { - // null is default value - if (value == null) - { - return (true); - } + Contract.Requires(value != null); string id = (string)value; @@ -557,11 +553,11 @@ namespace Avalonia.Controls if (i == id.Length) { - return (true); + return value; } } - return (false); + throw new ArgumentException("Invalid SharedSizeGroup string."); } /// @@ -931,14 +927,9 @@ namespace Avalonia.Controls /// Private shared size scope property holds a collection of shared state objects for the a given shared size scope. /// /// - internal static readonly AvaloniaProperty PrivateSharedSizeScopeProperty = - AvaloniaProperty.RegisterAttached( - "PrivateSharedSizeScope", - typeof(SharedSizeScope), - typeof(DefinitionBase), - new FrameworkPropertyMetadata( - null, - FrameworkPropertyMetadataOptions.Inherits)); + private static readonly AttachedProperty PrivateSharedSizeScopeProperty = + AvaloniaProperty.RegisterAttached( + "PrivateSharedSizeScope"); /// /// Shared size group property marks column / row definition as belonging to a group "Foo" or "Bar". @@ -956,23 +947,19 @@ namespace Avalonia.Controls /// String must not start with a digit. /// /// - /// - public static readonly AvaloniaProperty SharedSizeGroupProperty = - AvaloniaProperty.Register( - "SharedSizeGroup", - typeof(string), - typeof(DefinitionBase), - new FrameworkPropertyMetadata(new PropertyChangedCallback(OnSharedSizeGroupPropertyChanged)), - new ValidateValueCallback(SharedSizeGroupPropertyValueValid)); + /// + public static readonly AttachedProperty SharedSizeGroupProperty = + AvaloniaProperty.RegisterAttached( + "SharedSizeGroup", + validate:SharedSizeGroupPropertyValueValid); /// /// Static ctor. Used for static registration of properties. /// static DefinitionBase() { - PrivateSharedSizeScopeProperty.OverrideMetadata( - typeof(DefinitionBase), - new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPrivateSharedSizeScopePropertyChanged))); + SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); + PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); } #endregion Properties From c53297085f8a3ad21ebfe4bad4e07eadd1c1f41d Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 19:06:13 +0800 Subject: [PATCH 079/130] Fix OnCellAttachedPropertyChanged --- src/Avalonia.Controls/Grid.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index c3da8b7a34..9fb5098030 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -30,7 +30,10 @@ namespace Avalonia.Controls static Grid() { ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); - AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); + ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + RowProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + RowSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); } /// @@ -2893,7 +2896,7 @@ namespace Avalonia.Controls if (child != null) { - Grid grid = VisualTreeHelper.GetParent(child) as Grid; + Grid grid = child.GetVisualParent(); if ( grid != null && grid.ExtData != null && grid.ListenToNotifications ) From d1da8df74b214105f86abd789feabe94e00ed3c1 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 19:07:27 +0800 Subject: [PATCH 080/130] Restore removed GridSplitter code. --- src/Avalonia.Controls/GridSplitter.cs | 209 ++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 src/Avalonia.Controls/GridSplitter.cs diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs new file mode 100644 index 0000000000..304a760216 --- /dev/null +++ b/src/Avalonia.Controls/GridSplitter.cs @@ -0,0 +1,209 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.VisualTree; + +namespace Avalonia.Controls +{ + /// + /// Represents the control that redistributes space between columns or rows of a Grid control. + /// + /// + /// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext. + /// + public class GridSplitter : Thumb + { + private List _definitions; + + private Grid _grid; + + private DefinitionBase _nextDefinition; + + private Orientation _orientation; + + private DefinitionBase _prevDefinition; + + private void GetDeltaConstraints(out double min, out double max) + { + var prevDefinitionLen = GetActualLength(_prevDefinition); + var prevDefinitionMin = GetMinLength(_prevDefinition); + var prevDefinitionMax = GetMaxLength(_prevDefinition); + + var nextDefinitionLen = GetActualLength(_nextDefinition); + var nextDefinitionMin = GetMinLength(_nextDefinition); + var nextDefinitionMax = GetMaxLength(_nextDefinition); + // Determine the minimum and maximum the columns can be resized + min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen); + max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin); + } + + protected override void OnDragDelta(VectorEventArgs e) + { + // WPF doesn't change anything when spliter is in the last row/column + // but resizes the splitter row/column when it's the first one. + // this is different, but more internally consistent. + if (_prevDefinition == null || _nextDefinition == null) + return; + + var delta = _orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y; + double max; + double min; + GetDeltaConstraints(out min, out max); + delta = Math.Min(Math.Max(delta, min), max); + + var prevIsStar = IsStar(_prevDefinition); + var nextIsStar = IsStar(_nextDefinition); + + if (prevIsStar && nextIsStar) + { + foreach (var definition in _definitions) + { + if (definition == _prevDefinition) + { + SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta); + } + else if (definition == _nextDefinition) + { + SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta); + } + else if (IsStar(definition)) + { + SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars. + } + } + } + else if (prevIsStar) + { + SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta); + } + else if (nextIsStar) + { + SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta); + } + else + { + SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta); + SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta); + } + } + + private double GetActualLength(DefinitionBase definition) + { + if (definition == null) + return 0; + var columnDefinition = definition as ColumnDefinition; + return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; + } + + private double GetMinLength(DefinitionBase definition) + { + if (definition == null) + return 0; + var columnDefinition = definition as ColumnDefinition; + return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight; + } + + private double GetMaxLength(DefinitionBase definition) + { + if (definition == null) + return 0; + var columnDefinition = definition as ColumnDefinition; + return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight; + } + + private bool IsStar(DefinitionBase definition) + { + var columnDefinition = definition as ColumnDefinition; + return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar; + } + + private void SetLengthInStars(DefinitionBase definition, double value) + { + var columnDefinition = definition as ColumnDefinition; + if (columnDefinition != null) + { + columnDefinition.Width = new GridLength(value, GridUnitType.Star); + } + else + { + ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star); + } + } + + private void SetLength(DefinitionBase definition, double value) + { + var columnDefinition = definition as ColumnDefinition; + if (columnDefinition != null) + { + columnDefinition.Width = new GridLength(value); + } + else + { + ((RowDefinition)definition).Height = new GridLength(value); + } + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + _grid = this.GetVisualParent(); + + _orientation = DetectOrientation(); + + int definitionIndex; //row or col + if (_orientation == Orientation.Vertical) + { + Cursor = new Cursor(StandardCursorType.SizeWestEast); + _definitions = _grid.ColumnDefinitions.Cast().ToList(); + definitionIndex = GetValue(Grid.ColumnProperty); + PseudoClasses.Add(":vertical"); + } + else + { + Cursor = new Cursor(StandardCursorType.SizeNorthSouth); + definitionIndex = GetValue(Grid.RowProperty); + _definitions = _grid.RowDefinitions.Cast().ToList(); + PseudoClasses.Add(":horizontal"); + } + + if (definitionIndex > 0) + _prevDefinition = _definitions[definitionIndex - 1]; + + if (definitionIndex < _definitions.Count - 1) + _nextDefinition = _definitions[definitionIndex + 1]; + } + + private Orientation DetectOrientation() + { + if (!_grid.ColumnDefinitions.Any()) + return Orientation.Horizontal; + if (!_grid.RowDefinitions.Any()) + return Orientation.Vertical; + + var col = GetValue(Grid.ColumnProperty); + var row = GetValue(Grid.RowProperty); + var width = _grid.ColumnDefinitions[col].Width; + var height = _grid.RowDefinitions[row].Height; + if (width.IsAuto && !height.IsAuto) + { + return Orientation.Vertical; + } + if (!width.IsAuto && height.IsAuto) + { + return Orientation.Horizontal; + } + if (_grid.Children.OfType() // Decision based on other controls in the same column + .Where(c => Grid.GetColumn(c) == col) + .Any(c => c.GetType() != typeof(GridSplitter))) + { + return Orientation.Horizontal; + } + return Orientation.Vertical; + } + } +} From b387c38c8409436640b7a1ef9d94152b757b3948 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 30 May 2019 14:17:07 +0300 Subject: [PATCH 081/130] Implemented simple inertial scroll --- src/Avalonia.Controls/MenuItem.cs | 4 +- .../ScrollGestureRecognizer.cs | 62 +++++++++++++++++-- src/Avalonia.Input/MouseDevice.cs | 57 ++++++++--------- src/Avalonia.Input/PointerEventArgs.cs | 20 +++--- src/Avalonia.Input/PointerWheelEventArgs.cs | 5 +- src/Avalonia.Input/TouchDevice.cs | 6 +- .../MouseTestHelper.cs | 14 +++-- .../DefaultMenuInteractionHandlerTests.cs | 6 +- 8 files changed, 120 insertions(+), 54 deletions(-) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index d0ab0a0c8b..5f01c233b8 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -339,7 +339,7 @@ namespace Avalonia.Controls var point = e.GetPointerPoint(null); RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position, - point.Properties, e.InputModifiers)); + e.Timestamp, point.Properties, e.InputModifiers)); } /// @@ -349,7 +349,7 @@ namespace Avalonia.Controls var point = e.GetPointerPoint(null); RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position, - point.Properties, e.InputModifiers)); + e.Timestamp, point.Properties, e.InputModifiers)); } /// diff --git a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs index e560230bfe..4f3c7c0bba 100644 --- a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -1,5 +1,7 @@ using System; +using System.Diagnostics; using Avalonia.Interactivity; +using Avalonia.Threading; namespace Avalonia.Input.GestureRecognizers { @@ -16,6 +18,10 @@ namespace Avalonia.Input.GestureRecognizers private bool _canVerticallyScroll; private int _gestureId; + // Movement per second + private Vector _inertia; + private ulong? _lastMoveTimestamp; + /// /// Defines the property. /// @@ -63,14 +69,19 @@ namespace Avalonia.Input.GestureRecognizers { if (e.Pointer.IsPrimary && e.Pointer.Type == PointerType.Touch) { + EndGesture(); _tracking = e.Pointer; - _scrolling = false; + _gestureId = ScrollGestureEventArgs.GetNextFreeId();; _trackedRootPoint = e.GetPosition(null); } } // Arbitrary chosen value, probably need to move that to platform settings or something private const double ScrollStartDistance = 30; + + // Pixels per second speed that is considered to be the stop of inertiall scroll + private const double InertialScrollSpeedEnd = 5; + public void PointerMoved(PointerEventArgs e) { if (e.Pointer == _tracking) @@ -85,14 +96,20 @@ namespace Avalonia.Input.GestureRecognizers if (_scrolling) { _actions.Capture(e.Pointer, this); - _gestureId = ScrollGestureEventArgs.GetNextFreeId(); } } if (_scrolling) { var vector = _trackedRootPoint - rootPoint; + var elapsed = _lastMoveTimestamp.HasValue ? + TimeSpan.FromMilliseconds(e.Timestamp - _lastMoveTimestamp.Value) : + TimeSpan.Zero; + + _lastMoveTimestamp = e.Timestamp; _trackedRootPoint = rootPoint; + if (elapsed.TotalSeconds > 0) + _inertia = vector / elapsed.TotalSeconds; _target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); e.Handled = true; } @@ -109,8 +126,11 @@ namespace Avalonia.Input.GestureRecognizers _tracking = null; if (_scrolling) { + _inertia = default; _scrolling = false; _target.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); + _gestureId = 0; + _lastMoveTimestamp = null; } } @@ -118,11 +138,45 @@ namespace Avalonia.Input.GestureRecognizers public void PointerReleased(PointerReleasedEventArgs e) { - // TODO: handle inertia if (e.Pointer == _tracking && _scrolling) { e.Handled = true; - EndGesture(); + if (_inertia == default + || e.Timestamp == 0 + || _lastMoveTimestamp == 0 + || e.Timestamp - _lastMoveTimestamp > 200) + EndGesture(); + else + { + var savedGestureId = _gestureId; + var st = Stopwatch.StartNew(); + var lastTime = TimeSpan.Zero; + DispatcherTimer.Run(() => + { + // Another gesture has started, finish the current one + if (_gestureId != savedGestureId) + { + return false; + } + + var elapsedSinceLastTick = st.Elapsed - lastTime; + lastTime = st.Elapsed; + + var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds); + var distance = speed * elapsedSinceLastTick.TotalSeconds; + _target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); + + + + if (Math.Abs(speed.X) < InertialScrollSpeedEnd || Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + { + EndGesture(); + return false; + } + + return true; + }, TimeSpan.FromMilliseconds(16), DispatcherPriority.Background); + } } } } diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 05840660e2..a62a4dc62f 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -89,11 +89,11 @@ namespace Avalonia.Input { if (_pointer.Captured == null) { - SetPointerOver(this, root, clientPoint, InputModifiers.None); + SetPointerOver(this, 0 /* TODO: proper timestamp */, root, clientPoint, InputModifiers.None); } else { - SetPointerOver(this, root, _pointer.Captured, InputModifiers.None); + SetPointerOver(this, 0 /* TODO: proper timestamp */, root, _pointer.Captured, InputModifiers.None); } } } @@ -121,13 +121,13 @@ namespace Avalonia.Input switch (e.Type) { case RawPointerEventType.LeaveWindow: - LeaveWindow(mouse, e.Root, e.InputModifiers); + LeaveWindow(mouse, e.Timestamp, e.Root, e.InputModifiers); break; case RawPointerEventType.LeftButtonDown: case RawPointerEventType.RightButtonDown: case RawPointerEventType.MiddleButtonDown: if (ButtonCount(props) > 1) - e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers); + e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, e.InputModifiers); else e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position, props, e.InputModifiers); @@ -136,25 +136,25 @@ namespace Avalonia.Input case RawPointerEventType.RightButtonUp: case RawPointerEventType.MiddleButtonUp: if (ButtonCount(props) != 0) - e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers); + e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, e.InputModifiers); else - e.Handled = MouseUp(mouse, e.Root, e.Position, props, e.InputModifiers); + e.Handled = MouseUp(mouse, e.Timestamp, e.Root, e.Position, props, e.InputModifiers); break; case RawPointerEventType.Move: - e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers); + e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, e.InputModifiers); break; case RawPointerEventType.Wheel: - e.Handled = MouseWheel(mouse, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers); + e.Handled = MouseWheel(mouse, e.Timestamp, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers); break; } } - private void LeaveWindow(IMouseDevice device, IInputRoot root, InputModifiers inputModifiers) + private void LeaveWindow(IMouseDevice device, ulong timestamp, IInputRoot root, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); - ClearPointerOver(this, root, inputModifiers); + ClearPointerOver(this, timestamp, root, inputModifiers); } @@ -206,7 +206,7 @@ namespace Avalonia.Input _lastClickRect = new Rect(p, new Size()) .Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2)); _lastMouseDownButton = properties.GetObsoleteMouseButton(); - var e = new PointerPressedEventArgs(source, _pointer, root, p, properties, inputModifiers, _clickCount); + var e = new PointerPressedEventArgs(source, _pointer, root, p, timestamp, properties, inputModifiers, _clickCount); source.RaiseEvent(e); return e.Handled; } @@ -215,7 +215,7 @@ namespace Avalonia.Input return false; } - private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties properties, + private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties, InputModifiers inputModifiers) { Contract.Requires(device != null); @@ -225,22 +225,22 @@ namespace Avalonia.Input if (_pointer.Captured == null) { - source = SetPointerOver(this, root, p, inputModifiers); + source = SetPointerOver(this, timestamp, root, p, inputModifiers); } else { - SetPointerOver(this, root, _pointer.Captured, inputModifiers); + SetPointerOver(this, timestamp, root, _pointer.Captured, inputModifiers); source = _pointer.Captured; } var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root, - p, properties, inputModifiers); + p, timestamp, properties, inputModifiers); source?.RaiseEvent(e); return e.Handled; } - private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties props, + private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props, InputModifiers inputModifiers) { Contract.Requires(device != null); @@ -251,7 +251,8 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerReleasedEventArgs(source, _pointer, root, p, props, inputModifiers, _lastMouseDownButton); + var e = new PointerReleasedEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, + _lastMouseDownButton); source?.RaiseEvent(e); _pointer.Capture(null); @@ -261,7 +262,7 @@ namespace Avalonia.Input return false; } - private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p, + private bool MouseWheel(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props, Vector delta, InputModifiers inputModifiers) { @@ -273,7 +274,7 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerWheelEventArgs(source, _pointer, root, p, props, inputModifiers, delta); + var e = new PointerWheelEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta); source?.RaiseEvent(e); return e.Handled; @@ -298,19 +299,19 @@ namespace Avalonia.Input return _pointer.Captured ?? root.InputHitTest(p); } - PointerEventArgs CreateSimpleEvent(RoutedEvent ev, IInteractive source, InputModifiers inputModifiers) + PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive source, InputModifiers inputModifiers) { return new PointerEventArgs(ev, source, _pointer, null, default, - new PointerPointProperties(inputModifiers), inputModifiers); + timestamp, new PointerPointProperties(inputModifiers), inputModifiers); } - private void ClearPointerOver(IPointerDevice device, IInputRoot root, InputModifiers inputModifiers) + private void ClearPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); var element = root.PointerOverElement; - var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, element, inputModifiers); + var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, element, inputModifiers); if (element!=null && !element.IsAttachedToVisualTree) { @@ -347,7 +348,7 @@ namespace Avalonia.Input } } - private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p, InputModifiers inputModifiers) + private IInputElement SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, Point p, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); @@ -358,18 +359,18 @@ namespace Avalonia.Input { if (element != null) { - SetPointerOver(device, root, element, inputModifiers); + SetPointerOver(device, timestamp, root, element, inputModifiers); } else { - ClearPointerOver(device, root, inputModifiers); + ClearPointerOver(device, timestamp, root, inputModifiers); } } return element; } - private void SetPointerOver(IPointerDevice device, IInputRoot root, IInputElement element, InputModifiers inputModifiers) + private void SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, IInputElement element, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); @@ -391,7 +392,7 @@ namespace Avalonia.Input el = root.PointerOverElement; - var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, el, inputModifiers); + var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, el, inputModifiers); if (el!=null && branch!=null && !el.IsAttachedToVisualTree) { ClearChildrenPointerOver(e,branch,false); diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index 37d9ade839..c827822192 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/src/Avalonia.Input/PointerEventArgs.cs @@ -17,7 +17,9 @@ namespace Avalonia.Input public PointerEventArgs(RoutedEvent routedEvent, IInteractive source, IPointer pointer, - IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, + IVisual rootVisual, Point rootVisualPosition, + ulong timestamp, + PointerPointProperties properties, InputModifiers modifiers) : base(routedEvent) { @@ -26,6 +28,7 @@ namespace Avalonia.Input _rootVisualPosition = rootVisualPosition; _properties = properties; Pointer = pointer; + Timestamp = timestamp; InputModifiers = modifiers; } @@ -50,6 +53,7 @@ namespace Avalonia.Input } public IPointer Pointer { get; } + public ulong Timestamp { get; } private IPointerDevice _device; @@ -86,11 +90,13 @@ namespace Avalonia.Input public PointerPressedEventArgs( IInteractive source, IPointer pointer, - IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, + IVisual rootVisual, Point rootVisualPosition, + ulong timestamp, + PointerPointProperties properties, InputModifiers modifiers, int obsoleteClickCount = 1) - : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, properties, - modifiers) + : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, + timestamp, properties, modifiers) { _obsoleteClickCount = obsoleteClickCount; } @@ -105,10 +111,10 @@ namespace Avalonia.Input { public PointerReleasedEventArgs( IInteractive source, IPointer pointer, - IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, InputModifiers modifiers, - MouseButton obsoleteMouseButton) + IVisual rootVisual, Point rootVisualPosition, ulong timestamp, + PointerPointProperties properties, InputModifiers modifiers, MouseButton obsoleteMouseButton) : base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition, - properties, modifiers) + timestamp, properties, modifiers) { MouseButton = obsoleteMouseButton; } diff --git a/src/Avalonia.Input/PointerWheelEventArgs.cs b/src/Avalonia.Input/PointerWheelEventArgs.cs index b409cc81bd..de1badfe96 100644 --- a/src/Avalonia.Input/PointerWheelEventArgs.cs +++ b/src/Avalonia.Input/PointerWheelEventArgs.cs @@ -11,9 +11,10 @@ namespace Avalonia.Input public Vector Delta { get; set; } public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, - Point rootVisualPosition, + Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, InputModifiers modifiers, Vector delta) - : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, properties, modifiers) + : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, + timestamp, properties, modifiers) { Delta = delta; } diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index 8db2b125a6..7f473bb320 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -44,7 +44,7 @@ namespace Avalonia.Input if (args.Type == RawPointerEventType.TouchBegin) { target.RaiseEvent(new PointerPressedEventArgs(target, pointer, - args.Root, args.Position, + args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, pointer.IsPrimary)), GetModifiers(args.InputModifiers, false))); } @@ -55,7 +55,7 @@ namespace Avalonia.Input using (pointer) { target.RaiseEvent(new PointerReleasedEventArgs(target, pointer, - args.Root, args.Position, + args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, false)), GetModifiers(args.InputModifiers, pointer.IsPrimary), pointer.IsPrimary ? MouseButton.Left : MouseButton.None)); @@ -66,7 +66,7 @@ namespace Avalonia.Input { var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary); target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root, - args.Position, new PointerPointProperties(modifiers), modifiers)); + args.Position, ev.Timestamp, new PointerPointProperties(modifiers), modifiers)); } } diff --git a/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs b/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs index d6542d23f0..153473a8a0 100644 --- a/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs +++ b/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs @@ -1,3 +1,4 @@ +using System.Reactive; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.VisualTree; @@ -22,6 +23,8 @@ namespace Avalonia.Controls.UnitTests } TestPointer _pointer = new TestPointer(); + private ulong _nextStamp = 1; + private ulong Timestamp() => _nextStamp++; private InputModifiers _pressedButtons; public IInputElement Captured => _pointer.Captured; @@ -61,7 +64,7 @@ namespace Avalonia.Controls.UnitTests else { _pressedButton = mouseButton; - target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, props, + target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, Timestamp(), props, GetModifiers(modifiers), clickCount)); } } @@ -70,7 +73,7 @@ namespace Avalonia.Controls.UnitTests public void Move(IInteractive target, IInteractive source, in Point position, InputModifiers modifiers = default) { target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (IVisual)target, position, - new PointerPointProperties(_pressedButtons), GetModifiers(modifiers))); + Timestamp(), new PointerPointProperties(_pressedButtons), GetModifiers(modifiers))); } public void Up(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default, @@ -84,7 +87,8 @@ namespace Avalonia.Controls.UnitTests _pressedButtons = (_pressedButtons | conv) ^ conv; var props = new PointerPointProperties(_pressedButtons); if (ButtonCount(props) == 0) - target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, props, + target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, + Timestamp(), props, GetModifiers(modifiers), _pressedButton)); else Move(target, source, position); @@ -103,13 +107,13 @@ namespace Avalonia.Controls.UnitTests public void Enter(IInteractive target) { target.RaiseEvent(new PointerEventArgs(InputElement.PointerEnterEvent, target, _pointer, (IVisual)target, default, - new PointerPointProperties(_pressedButtons), _pressedButtons)); + Timestamp(), new PointerPointProperties(_pressedButtons), _pressedButtons)); } public void Leave(IInteractive target) { target.RaiseEvent(new PointerEventArgs(InputElement.PointerLeaveEvent, target, _pointer, (IVisual)target, default, - new PointerPointProperties(_pressedButtons), _pressedButtons)); + Timestamp(), new PointerPointProperties(_pressedButtons), _pressedButtons)); } } diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index fb3a5bfefb..ba4d6ca9c5 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -11,14 +11,14 @@ namespace Avalonia.Controls.UnitTests.Platform public class DefaultMenuInteractionHandlerTests { static PointerEventArgs CreateArgs(RoutedEvent ev, IInteractive source) - => new PointerEventArgs(ev, source, new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default); + => new PointerEventArgs(ev, source, new FakePointer(), (IVisual)source, default, 0, new PointerPointProperties(), default); static PointerPressedEventArgs CreatePressed(IInteractive source) => new PointerPressedEventArgs(source, - new FakePointer(), (IVisual)source, default, new PointerPointProperties {IsLeftButtonPressed = true}, + new FakePointer(), (IVisual)source, default,0, new PointerPointProperties {IsLeftButtonPressed = true}, default); static PointerReleasedEventArgs CreateReleased(IInteractive source) => new PointerReleasedEventArgs(source, - new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default, MouseButton.Left); + new FakePointer(), (IVisual)source, default,0, new PointerPointProperties(), default, MouseButton.Left); public class TopLevel { From cba7de5f06329efef197f8d11fcd637046c13ae9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 30 May 2019 14:18:23 +0300 Subject: [PATCH 082/130] Do not crash --- src/Avalonia.Controls/TabControl.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 88b9a84111..fc2c118132 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -179,8 +179,9 @@ namespace Avalonia.Controls if (e.MouseButton == MouseButton.Left && e.Pointer.Type != PointerType.Mouse) { var container = GetContainerFromEventSource(e.Source); - if (container.GetVisualsAt(e.GetPosition(container)) - .Any(c => container == c || container.IsVisualAncestorOf(c))) + if (container != null + && container.GetVisualsAt(e.GetPosition(container)) + .Any(c => container == c || container.IsVisualAncestorOf(c))) { e.Handled = UpdateSelectionFromEventSource(e.Source); } From 534e3bf4eca0045323d66fc9324c2e46a3d9441f Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 19:19:11 +0800 Subject: [PATCH 083/130] Make ValueCache as abstract props. Add setters to Row/ColumnDefinitions. --- src/Avalonia.Controls/ColumnDefinition.cs | 4 +++ src/Avalonia.Controls/DefinitionBase.cs | 35 +++-------------------- src/Avalonia.Controls/Grid.cs | 10 +++++++ src/Avalonia.Controls/RowDefinition.cs | 4 +++ 4 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs index d316881a05..d87a683cc8 100644 --- a/src/Avalonia.Controls/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -87,5 +87,9 @@ namespace Avalonia.Controls get { return GetValue(WidthProperty); } set { SetValue(WidthProperty, value); } } + + internal override GridLength UserSizeValueCache => this.Width; + internal override double UserMinSizeValueCache => this.MinWidth; + internal override double UserMaxSizeValueCache => this.MaxWidth; } } diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index dcb375745d..2e0afc7fe7 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -410,45 +410,18 @@ namespace Avalonia.Controls /// /// Internal helper to access up-to-date UserSize property value. /// - internal GridLength UserSizeValueCache - { - get - { - return (GridLength) GetValue( - _isColumnDefinition ? - ColumnDefinition.WidthProperty : - RowDefinition.HeightProperty); - } - } + internal abstract GridLength UserSizeValueCache { get; } /// /// Internal helper to access up-to-date UserMinSize property value. /// - internal double UserMinSizeValueCache - { - get - { - return (double) GetValue( - _isColumnDefinition ? - ColumnDefinition.MinWidthProperty : - RowDefinition.MinHeightProperty); - } - } + internal abstract double UserMinSizeValueCache { get; } /// /// Internal helper to access up-to-date UserMaxSize property value. /// - internal double UserMaxSizeValueCache - { - get - { - return (double) GetValue( - _isColumnDefinition ? - ColumnDefinition.MaxWidthProperty : - RowDefinition.MaxHeightProperty); - } - } - + internal abstract double UserMaxSizeValueCache { get; } + /// /// Protected. Returns true if this DefinitionBase instance is in parent's logical tree. /// diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 9fb5098030..eaa7cc11c3 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -216,6 +216,11 @@ namespace Avalonia.Controls return (_data.ColumnDefinitions); } + set + { + if (_data == null) { _data = new ExtendedData(); } + _data.ColumnDefinitions = value; + } } /// @@ -230,6 +235,11 @@ namespace Avalonia.Controls return (_data.RowDefinitions); } + set + { + if (_data == null) { _data = new ExtendedData(); } + _data.RowDefinitions = value; + } } //------------------------------------------------------ diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs index 8e6ab3ae36..f2f09f797c 100644 --- a/src/Avalonia.Controls/RowDefinition.cs +++ b/src/Avalonia.Controls/RowDefinition.cs @@ -87,5 +87,9 @@ namespace Avalonia.Controls get { return GetValue(HeightProperty); } set { SetValue(HeightProperty, value); } } + + internal override GridLength UserSizeValueCache => this.Height; + internal override double UserMinSizeValueCache => this.MinHeight; + internal override double UserMaxSizeValueCache => this.MaxHeight; } } \ No newline at end of file From adbd42ed2d12fb085950c91a29f90a3100cbd9c2 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 19:51:43 +0800 Subject: [PATCH 084/130] Trigger OnEnter/EnterParentTree on DefBase. --- src/Avalonia.Controls/DefinitionBase.cs | 10 +++-- src/Avalonia.Controls/Grid.cs | 37 +++++++++++++------ .../SharedSizeScopeTests.cs | 15 +++++--- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 2e0afc7fe7..0c696a1035 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -655,7 +655,7 @@ namespace Avalonia.Controls /// /// Collection of shared states objects for a single scope /// - private class SharedSizeScope + internal class SharedSizeScope { /// /// Returns SharedSizeState object for a given group. @@ -690,7 +690,7 @@ namespace Avalonia.Controls /// /// Implementation of per shared group state object /// - private class SharedSizeState + internal class SharedSizeState { /// /// Default ctor. @@ -900,9 +900,11 @@ namespace Avalonia.Controls /// Private shared size scope property holds a collection of shared state objects for the a given shared size scope. /// /// - private static readonly AttachedProperty PrivateSharedSizeScopeProperty = + internal static readonly AttachedProperty PrivateSharedSizeScopeProperty = AvaloniaProperty.RegisterAttached( - "PrivateSharedSizeScope"); + "PrivateSharedSizeScope", + defaultValue: null, + inherits: true); /// /// Shared size group property marks column / row definition as belonging to a group "Foo" or "Bar". diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index eaa7cc11c3..269d7a3093 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -7,6 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using Avalonia; using Avalonia.Collections; @@ -922,8 +923,6 @@ namespace Avalonia.Controls /// private void ValidateDefinitionsUStructure() { - - if (ColumnDefinitionsDirty) { ExtendedData extData = ExtData; @@ -937,8 +936,6 @@ namespace Avalonia.Controls } else { - // extData.ColumnDefinitions.InternalTrimToSize(); - if (extData.ColumnDefinitions.Count == 0) { // if column definitions collection is empty @@ -947,16 +944,26 @@ namespace Avalonia.Controls } else { + foreach(var definition in extData.DefinitionsU + ?? Enumerable.Empty()) + definition.OnExitParentTree(); + extData.DefinitionsU = extData.ColumnDefinitions; } } + // adds index information. + for(int i = 0; i < extData.DefinitionsU.Count;i++) + { + var definition = extData.DefinitionsU[i]; + definition.Index = i; + definition.OnEnterParentTree(); + } + ColumnDefinitionsDirty = false; } Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Count > 0); - - } /// @@ -969,8 +976,6 @@ namespace Avalonia.Controls /// private void ValidateDefinitionsVStructure() { - - if (RowDefinitionsDirty) { ExtendedData extData = ExtData; @@ -984,8 +989,6 @@ namespace Avalonia.Controls } else { - // extData.RowDefinitions.InternalTrimToSize(); - if (extData.RowDefinitions.Count == 0) { // if row definitions collection is empty @@ -994,16 +997,26 @@ namespace Avalonia.Controls } else { + foreach(var definition in extData.DefinitionsV + ?? Enumerable.Empty()) + definition.OnExitParentTree(); + extData.DefinitionsV = extData.RowDefinitions; } } + // adds index information. + for(int i = 0; i < extData.DefinitionsV.Count;i++) + { + var definition = extData.DefinitionsV[i]; + definition.Index = i; + definition.OnEnterParentTree(); + } + RowDefinitionsDirty = false; } Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Count > 0); - - } /// diff --git a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs b/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs index 467c25bfc6..12d4df32e5 100644 --- a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs @@ -12,6 +12,11 @@ namespace Avalonia.Controls.UnitTests { public class SharedSizeScopeTests { + public bool HasSharedSizeScope(Control control) + { + return control.GetValue(DefinitionBase.PrivateSharedSizeScopeProperty) != null; + } + [Fact] public void All_Descendant_Grids_Are_Registered_When_Added_After_Setting_Scope() { @@ -23,7 +28,7 @@ namespace Avalonia.Controls.UnitTests root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Child = scope; - Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); + Assert.All(grids, g => Assert.True(HasSharedSizeScope(g))); } [Fact] @@ -37,7 +42,7 @@ namespace Avalonia.Controls.UnitTests root.Child = scope; root.SetValue(Grid.IsSharedSizeScopeProperty, true); - Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); + Assert.All(grids, g => Assert.True(HasSharedSizeScope(g))); } [Fact] @@ -51,10 +56,10 @@ namespace Avalonia.Controls.UnitTests root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Child = scope; - Assert.All(grids, g => Assert.True(g.HasSharedSizeScope())); + Assert.All(grids, g => Assert.True(HasSharedSizeScope(g))); root.SetValue(Grid.IsSharedSizeScopeProperty, false); - Assert.All(grids, g => Assert.False(g.HasSharedSizeScope())); - Assert.Equal(null, root.GetValue(Grid.PrivateSharedSizeScopeProperty)); + Assert.All(grids, g => Assert.False(HasSharedSizeScope(g))); + Assert.Equal(null, root.GetValue(DefinitionBase.PrivateSharedSizeScopeProperty)); } [Fact] From 2858eba77d63d26c21964eb4b19a9c4f74d609e4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 30 May 2019 15:02:46 +0300 Subject: [PATCH 085/130] MouseTestHelper should also auto-captrure the target --- .../MouseTestHelper.cs | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs b/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs index 153473a8a0..373bbaed75 100644 --- a/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs +++ b/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs @@ -7,22 +7,7 @@ namespace Avalonia.Controls.UnitTests { public class MouseTestHelper { - - class TestPointer : IPointer - { - public int Id { get; } = Pointer.GetNextFreeId(); - - public void Capture(IInputElement control) - { - Captured = control; - } - - public IInputElement Captured { get; set; } - public PointerType Type => PointerType.Mouse; - public bool IsPrimary => true; - } - - TestPointer _pointer = new TestPointer(); + private Pointer _pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); private ulong _nextStamp = 1; private ulong Timestamp() => _nextStamp++; @@ -52,8 +37,10 @@ namespace Avalonia.Controls.UnitTests public void Down(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default, InputModifiers modifiers = default, int clickCount = 1) - => Down(target, target, mouseButton, position, modifiers, clickCount); - + { + Down(target, target, mouseButton, position, modifiers, clickCount); + } + public void Down(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left, Point position = default, InputModifiers modifiers = default, int clickCount = 1) { @@ -64,6 +51,7 @@ namespace Avalonia.Controls.UnitTests else { _pressedButton = mouseButton; + _pointer.Capture((IInputElement)target); target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, Timestamp(), props, GetModifiers(modifiers), clickCount)); } @@ -87,9 +75,12 @@ namespace Avalonia.Controls.UnitTests _pressedButtons = (_pressedButtons | conv) ^ conv; var props = new PointerPointProperties(_pressedButtons); if (ButtonCount(props) == 0) + { + _pointer.Capture(null); target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, Timestamp(), props, GetModifiers(modifiers), _pressedButton)); + } else Move(target, source, position); } From 551ce9b058afc572f112252dd8ea8bb44cdd8301 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 30 May 2019 15:16:34 +0300 Subject: [PATCH 086/130] Fixed capture transfer for removed controls --- src/Avalonia.Input/Pointer.cs | 2 +- tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs index 14703986e2..890ad57024 100644 --- a/src/Avalonia.Input/Pointer.cs +++ b/src/Avalonia.Input/Pointer.cs @@ -40,7 +40,7 @@ namespace Avalonia.Input foreach (var notifyTarget in oldCapture.GetSelfAndVisualAncestors().OfType()) { if (notifyTarget == commonParent) - return; + break; notifyTarget.RaiseEvent(new PointerCaptureLostEventArgs(notifyTarget, this)); } } diff --git a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs index 8f1c071695..983f541c2a 100644 --- a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs @@ -15,7 +15,7 @@ namespace Avalonia.Input.UnitTests public class MouseDeviceTests { [Fact] - public void Capture_Is_Cleared_When_Control_Removed() + public void Capture_Is_Transferred_To_Parent_When_Control_Removed() { Canvas control; var root = new TestRoot @@ -29,7 +29,7 @@ namespace Avalonia.Input.UnitTests root.Child = null; - Assert.Null(target.Captured); + Assert.Same(root, target.Captured); } [Fact] From c7a372d0b67d34bb21467437d9cc2946c0bfa456 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 20:42:02 +0800 Subject: [PATCH 087/130] Invalidate parent grid when property changes on DefBase. Link ActualWidth/Height setters to their parent GetFinalColumnDefinitionWidth/GetFinalRowDefinitionWidth --- src/Avalonia.Controls/ColumnDefinition.cs | 6 +---- src/Avalonia.Controls/DefinitionBase.cs | 33 +++++++++++++---------- src/Avalonia.Controls/Grid.cs | 10 +++---- src/Avalonia.Controls/RowDefinition.cs | 6 +---- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs index d87a683cc8..e3d2489241 100644 --- a/src/Avalonia.Controls/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -55,11 +55,7 @@ namespace Avalonia.Controls /// /// Gets the actual calculated width of the column. /// - public double ActualWidth - { - get; - internal set; - } + public double ActualWidth => Parent?.GetFinalColumnDefinitionWidth(Index) ?? 0d; /// /// Gets or sets the maximum width of the column in DIPs. diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 0c696a1035..a0b68a25a6 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -85,6 +85,15 @@ namespace Avalonia.Controls } } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) + { + if(e.Property.PropertyType == typeof(GridLength) + || e.Property.PropertyType == typeof(double)) + OnUserSizePropertyChanged(e); + + base.OnPropertyChanged(e); + } + /// /// Callback to notify about exitting model tree. /// @@ -108,7 +117,7 @@ namespace Avalonia.Controls LayoutWasUpdated = true; // defer verification for shared definitions - if (_sharedState != null) { _sharedState.EnsureDeferredValidation(grid); } + if (_sharedState != null) { _sharedState.EnsureDeferredValidation(grid); } } /// @@ -135,32 +144,28 @@ namespace Avalonia.Controls /// /// This method needs to be internal to be accessable from derived classes. /// - internal static void OnUserSizePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) + internal void OnUserSizePropertyChanged(AvaloniaPropertyChangedEventArgs e) { - DefinitionBase definition = (DefinitionBase) d; - - if (definition.InParentLogicalTree) + if (InParentLogicalTree) { - if (definition._sharedState != null) + if (_sharedState != null) { - definition._sharedState.Invalidate(); + _sharedState.Invalidate(); } else { - Grid parentGrid = (Grid) definition.Parent; - - if (((GridLength) e.OldValue).GridUnitType != ((GridLength) e.NewValue).GridUnitType) + if (((GridLength)e.OldValue).GridUnitType != ((GridLength)e.NewValue).GridUnitType) { - parentGrid.Invalidate(); + Parent.Invalidate(); } else - { - parentGrid.InvalidateMeasure(); + { + Parent.InvalidateMeasure(); } } } } - + /// /// /// diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 269d7a3093..1d9251076d 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -656,11 +656,8 @@ namespace Avalonia.Controls } // update render bound on grid lines renderer visual - GridLinesRenderer gridLinesRenderer = EnsureGridLinesRenderer(); - if (gridLinesRenderer != null) - { - gridLinesRenderer.UpdateRenderBounds(arrangeSize); - } + var gridLinesRenderer = EnsureGridLinesRenderer(); + gridLinesRenderer?.UpdateRenderBounds(arrangeSize); } } finally @@ -956,6 +953,7 @@ namespace Avalonia.Controls for(int i = 0; i < extData.DefinitionsU.Count;i++) { var definition = extData.DefinitionsU[i]; + definition.Parent = this; definition.Index = i; definition.OnEnterParentTree(); } @@ -1009,6 +1007,7 @@ namespace Avalonia.Controls for(int i = 0; i < extData.DefinitionsV.Count;i++) { var definition = extData.DefinitionsV[i]; + definition.Parent = this; definition.Index = i; definition.OnEnterParentTree(); } @@ -4075,6 +4074,7 @@ namespace Avalonia.Controls internal void UpdateRenderBounds(Size arrangeSize) { _lastArrangeSize = arrangeSize; + this.InvalidateMeasure(); this.InvalidateVisual(); } diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs index f2f09f797c..ad7312d515 100644 --- a/src/Avalonia.Controls/RowDefinition.cs +++ b/src/Avalonia.Controls/RowDefinition.cs @@ -55,11 +55,7 @@ namespace Avalonia.Controls /// /// Gets the actual calculated height of the row. /// - public double ActualHeight - { - get; - internal set; - } + public double ActualHeight => Parent?.GetFinalRowDefinitionHeight(Index) ?? 0d; /// /// Gets or sets the maximum height of the row in DIPs. From 7e16a9032a75e03cc32ae16f1fa8944d87132c6a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 21:39:04 +0800 Subject: [PATCH 088/130] Add inheritance parent to DefBase. --- src/Avalonia.Controls/DefinitionBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index a0b68a25a6..3d1a5f0e79 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -68,6 +68,7 @@ namespace Avalonia.Controls /// internal void OnEnterParentTree() { + this.InheritanceParent = Parent; if (_sharedState == null) { // start with getting SharedSizeGroup value. @@ -87,7 +88,7 @@ namespace Avalonia.Controls protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) { - if(e.Property.PropertyType == typeof(GridLength) + if (e.Property.PropertyType == typeof(GridLength) || e.Property.PropertyType == typeof(double)) OnUserSizePropertyChanged(e); @@ -165,7 +166,6 @@ namespace Avalonia.Controls } } } - /// /// /// @@ -939,7 +939,7 @@ namespace Avalonia.Controls static DefinitionBase() { SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); + PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); } #endregion Properties From ff0ae960808fe52b422fc0fc0bcb89526a68a327 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 30 May 2019 21:56:08 +0800 Subject: [PATCH 089/130] Fix shared size scope handlers. 5/9 unit tests on SharedSizeScopeTests now works. --- src/Avalonia.Controls/DefinitionBase.cs | 2 +- src/Avalonia.Controls/Grid.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 3d1a5f0e79..e0ed8aa7d7 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -939,7 +939,7 @@ namespace Avalonia.Controls static DefinitionBase() { SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); + PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); } #endregion Properties diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 1d9251076d..891e36da08 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -30,6 +30,7 @@ namespace Avalonia.Controls static Grid() { + IsSharedSizeScopeProperty.Changed.AddClassHandler(DefinitionBase.OnIsSharedSizeScopePropertyChanged); ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); From 5fbfdb4c181d99b74a71f5fc12dfbb47078d524f Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 31 May 2019 01:07:34 +0800 Subject: [PATCH 090/130] Pass all of GridTests. --- src/Avalonia.Controls/Grid.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 891e36da08..55298abb3a 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -32,10 +32,12 @@ namespace Avalonia.Controls { IsSharedSizeScopeProperty.Changed.AddClassHandler(DefinitionBase.OnIsSharedSizeScopePropertyChanged); ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); + ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); RowProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); RowSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); } /// @@ -2925,7 +2927,6 @@ namespace Avalonia.Controls && grid.ListenToNotifications ) { grid.CellsStructureDirty = true; - grid.InvalidateMeasure(); } } } From 2b6fdc153148c2687e41cb1fd4256d5011db3b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 30 May 2019 20:49:39 +0200 Subject: [PATCH 091/130] Initial port of WPF WrapPanel --- src/Avalonia.Controls/WrapPanel.cs | 195 ++++++++++++++--------------- 1 file changed, 93 insertions(+), 102 deletions(-) diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index 597734d400..b6215a28cc 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -92,109 +92,127 @@ namespace Avalonia.Controls } } - private UVSize CreateUVSize(Size size) => new UVSize(Orientation, size); - - private UVSize CreateUVSize() => new UVSize(Orientation); - /// - protected override Size MeasureOverride(Size availableSize) + protected override Size MeasureOverride(Size constraint) { - var desiredSize = CreateUVSize(); - var lineSize = CreateUVSize(); - var uvAvailableSize = CreateUVSize(availableSize); + var curLineSize = new UVSize(Orientation); + var panelSize = new UVSize(Orientation); + var uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height); - foreach (var child in Children) + var childConstraint = new Size(constraint.Width, constraint.Height); + + for (int i = 0, count = Children.Count; i < count; i++) { - child.Measure(availableSize); - var childSize = CreateUVSize(child.DesiredSize); - if (lineSize.U + childSize.U <= uvAvailableSize.U) // same line + var child = Children[i]; + if (child == null) continue; + + //Flow passes its own constrint to children + child.Measure(childConstraint); + + //this is the size of the child in UV space + var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); + + if ((curLineSize.U + sz.U) > uvConstraint.U) //need to switch to another line { - lineSize.U += childSize.U; - lineSize.V = Max(lineSize.V, childSize.V); + panelSize.U = Max(curLineSize.U, panelSize.U); + panelSize.V += curLineSize.V; + curLineSize = sz; + + if (sz.U > uvConstraint.U) //the element is wider then the constrint - give it a separate line + { + panelSize.U = Max(sz.U, panelSize.U); + panelSize.V += sz.V; + curLineSize = new UVSize(Orientation); + } } - else // moving to next line + else //continue to accumulate a line { - desiredSize.U = Max(lineSize.U, uvAvailableSize.U); - desiredSize.V += lineSize.V; - lineSize = childSize; + curLineSize.U += sz.U; + curLineSize.V = Max(sz.V, curLineSize.V); } } - // last element - desiredSize.U = Max(lineSize.U, desiredSize.U); - desiredSize.V += lineSize.V; - return desiredSize.ToSize(); + //the last line size, if any should be added + panelSize.U = Max(curLineSize.U, panelSize.U); + panelSize.V += curLineSize.V; + + //go from UV space to W/H space + return new Size(panelSize.Width, panelSize.Height); } /// protected override Size ArrangeOverride(Size finalSize) { + int firstInLine = 0; double accumulatedV = 0; - var uvFinalSize = CreateUVSize(finalSize); - var lineSize = CreateUVSize(); - int firstChildInLineIndex = 0; - for (int index = 0; index < Children.Count; index++) + UVSize curLineSize = new UVSize(Orientation); + UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height); + + for (int i = 0; i < Children.Count; i++) { - var child = Children[index]; - var childSize = CreateUVSize(child.DesiredSize); - if (lineSize.U + childSize.U <= uvFinalSize.U) // same line + var child = Children[i]; + if (child == null) continue; + + var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); + + if ((curLineSize.U + sz.U) > uvFinalSize.U) //need to switch to another line { - lineSize.U += childSize.U; - lineSize.V = Max(lineSize.V, childSize.V); + arrangeLine(accumulatedV, curLineSize.V, firstInLine, i); + + accumulatedV += curLineSize.V; + curLineSize = sz; + + if (sz.U > uvFinalSize.U) //the element is wider then the constraint - give it a separate line + { + //switch to next line which only contain one element + arrangeLine(accumulatedV, sz.V, i, ++i); + + accumulatedV += sz.V; + curLineSize = new UVSize(Orientation); + } + firstInLine = i; } - else // moving to next line + else //continue to accumulate a line { - var controlsInLine = GetControlsBetween(firstChildInLineIndex, index); - ArrangeLine(accumulatedV, lineSize.V, controlsInLine); - accumulatedV += lineSize.V; - lineSize = childSize; - firstChildInLineIndex = index; + curLineSize.U += sz.U; + curLineSize.V = Max(sz.V, curLineSize.V); } } - if (firstChildInLineIndex < Children.Count) + //arrange the last line, if any + if (firstInLine < Children.Count) { - var controlsInLine = GetControlsBetween(firstChildInLineIndex, Children.Count); - ArrangeLine(accumulatedV, lineSize.V, controlsInLine); + arrangeLine(accumulatedV, curLineSize.V, firstInLine, Children.Count); } - return finalSize; - } - private IEnumerable GetControlsBetween(int first, int last) - { - return Children.Skip(first).Take(last - first); + return finalSize; } - private void ArrangeLine(double v, double lineV, IEnumerable controls) + private void arrangeLine(double v, double lineV, int start, int end) { double u = 0; bool isHorizontal = (Orientation == Orientation.Horizontal); - foreach (var child in controls) + + for (int i = 0, count = Children.Count; i < count; i++) { - var childSize = CreateUVSize(child.DesiredSize); - var x = isHorizontal ? u : v; - var y = isHorizontal ? v : u; - var width = isHorizontal ? childSize.U : lineV; - var height = isHorizontal ? lineV : childSize.U; - child.Arrange(new Rect(x, y, width, height)); - u += childSize.U; + var child = Children[i]; + if (child != null) + { + UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); + double layoutSlotU = childSize.U; + child.Arrange(new Rect( + (isHorizontal ? u : v), + (isHorizontal ? v : u), + (isHorizontal ? layoutSlotU : lineV), + (isHorizontal ? lineV : layoutSlotU))); + u += layoutSlotU; + } } } - /// - /// Used to not not write separate code for horizontal and vertical orientation. - /// U is direction in line. (x if orientation is horizontal) - /// V is direction of lines. (y if orientation is horizontal) - /// - [DebuggerDisplay("U = {U} V = {V}")] + private struct UVSize { - private readonly Orientation _orientation; - - internal double U; - - internal double V; - - private UVSize(Orientation orientation, double width, double height) + internal UVSize(Orientation orientation, double width, double height) { U = V = 0d; _orientation = orientation; @@ -202,52 +220,25 @@ namespace Avalonia.Controls Height = height; } - internal UVSize(Orientation orientation, Size size) - : this(orientation, size.Width, size.Height) - { - } - internal UVSize(Orientation orientation) { U = V = 0d; _orientation = orientation; } - private double Width + internal double U; + internal double V; + private Orientation _orientation; + + internal double Width { get { return (_orientation == Orientation.Horizontal ? U : V); } - set - { - if (_orientation == Orientation.Horizontal) - { - U = value; - } - else - { - V = value; - } - } + set { if (_orientation == Orientation.Horizontal) U = value; else V = value; } } - - private double Height + internal double Height { get { return (_orientation == Orientation.Horizontal ? V : U); } - set - { - if (_orientation == Orientation.Horizontal) - { - V = value; - } - else - { - U = value; - } - } - } - - public Size ToSize() - { - return new Size(Width, Height); + set { if (_orientation == Orientation.Horizontal) V = value; else U = value; } } } } From f0ada1c82ff27fa72c512624bb840dbee83c6b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 30 May 2019 21:18:34 +0200 Subject: [PATCH 092/130] Update MathUtilities.cs --- src/Avalonia.Base/Utilities/MathUtilities.cs | 118 ++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index dcb3ef4a2b..546133bb03 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -1,6 +1,9 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using System.Runtime.InteropServices; + namespace Avalonia.Utilities { /// @@ -8,6 +11,86 @@ namespace Avalonia.Utilities /// public static class MathUtilities { + /// + /// AreClose - Returns whether or not two doubles are "close". That is, whether or + /// not they are within epsilon of each other. + /// + /// The first double to compare. + /// The second double to compare. + public static bool AreClose(double value1, double value2) + { + //in case they are Infinities (then epsilon check does not work) + if (value1 == value2) return true; + double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * double.Epsilon; + double delta = value1 - value2; + return (-eps < delta) && (eps > delta); + } + + /// + /// LessThan - Returns whether or not the first double is less than the second double. + /// That is, whether or not the first is strictly less than *and* not within epsilon of + /// the other number. + /// The first double to compare. + /// The second double to compare. + public static bool LessThan(double value1, double value2) + { + return (value1 < value2) && !AreClose(value1, value2); + } + + /// + /// GreaterThan - Returns whether or not the first double is greater than the second double. + /// That is, whether or not the first is strictly greater than *and* not within epsilon of + /// the other number. + /// The first double to compare. + /// The second double to compare. + public static bool GreaterThan(double value1, double value2) + { + return (value1 > value2) && !AreClose(value1, value2); + } + + /// + /// LessThanOrClose - Returns whether or not the first double is less than or close to + /// the second double. That is, whether or not the first is strictly less than or within + /// epsilon of the other number. + /// The first double to compare. + /// The second double to compare. + public static bool LessThanOrClose(double value1, double value2) + { + return (value1 < value2) || AreClose(value1, value2); + } + + /// + /// GreaterThanOrClose - Returns whether or not the first double is greater than or close to + /// the second double. That is, whether or not the first is strictly greater than or within + /// epsilon of the other number. + /// + /// The first double to compare. + /// The second double to compare. + public static bool GreaterThanOrClose(double value1, double value2) + { + return (value1 > value2) || AreClose(value1, value2); + } + + /// + /// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), + /// but this is faster. + /// + /// The double to compare to 1. + public static bool IsOne(double value) + { + return Math.Abs(value - 1.0) < 10.0 * double.Epsilon; + } + + /// + /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), + /// but this is faster. + /// + /// The double to compare to 0. + public static bool IsZero(double value) + { + return Math.Abs(value) < 10.0 * double.Epsilon; + } + /// /// Clamps a value between a minimum and maximum value. /// @@ -31,6 +114,39 @@ namespace Avalonia.Utilities } } + /// + /// Calculates the value to be used for layout rounding at high DPI. + /// + /// Input value to be rounded. + /// Ratio of screen's DPI to layout DPI + /// Adjusted value that will produce layout rounding on screen at high dpi. + /// This is a layout helper method. It takes DPI into account and also does not return + /// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper associated with + /// UseLayoutRounding property and should not be used as a general rounding utility. + public static double RoundLayoutValue(double value, double dpiScale) + { + double newValue; + + // If DPI == 1, don't use DPI-aware rounding. + if (!MathUtilities.AreClose(dpiScale, 1.0)) + { + newValue = Math.Round(value * dpiScale) / dpiScale; + // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. + if (double.IsNaN(newValue) || + double.IsInfinity(newValue) || + MathUtilities.AreClose(newValue, double.MaxValue)) + { + newValue = value; + } + } + else + { + newValue = Math.Round(value); + } + + return newValue; + } + /// /// Clamps a value between a minimum and maximum value. /// @@ -54,4 +170,4 @@ namespace Avalonia.Utilities } } } -} +} \ No newline at end of file From 10d295decf53bec90de96c7663fdfcfc72adcaec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 30 May 2019 21:18:42 +0200 Subject: [PATCH 093/130] Use MathUtilities --- src/Avalonia.Controls/WrapPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index b6215a28cc..86f1691e70 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -112,13 +112,13 @@ namespace Avalonia.Controls //this is the size of the child in UV space var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); - if ((curLineSize.U + sz.U) > uvConstraint.U) //need to switch to another line + if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U) //need to switch to another line { panelSize.U = Max(curLineSize.U, panelSize.U); panelSize.V += curLineSize.V; curLineSize = sz; - if (sz.U > uvConstraint.U) //the element is wider then the constrint - give it a separate line + if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constrint - give it a separate line { panelSize.U = Max(sz.U, panelSize.U); panelSize.V += sz.V; From 6f652867ff047631c452d685178645759eccc528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 30 May 2019 21:25:30 +0200 Subject: [PATCH 094/130] Fix --- src/Avalonia.Controls/WrapPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index 86f1691e70..fb6eecbef1 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -112,7 +112,7 @@ namespace Avalonia.Controls //this is the size of the child in UV space var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); - if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U) //need to switch to another line + if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line { panelSize.U = Max(curLineSize.U, panelSize.U); panelSize.V += curLineSize.V; From 9844b05bfdd23e90c4fc46ef960a14d7e977e7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 30 May 2019 21:27:23 +0200 Subject: [PATCH 095/130] Add missing using --- src/Avalonia.Controls/WrapPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index fb6eecbef1..2382e689ad 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using Avalonia.Input; +using Avalonia.Utilities; using static System.Math; From 62b8c679ea1913c2eae2c3c2c8f534ff2ac76d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 30 May 2019 21:48:48 +0200 Subject: [PATCH 096/130] Update WrapPanel.cs --- src/Avalonia.Controls/WrapPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index 2382e689ad..fcc21a7b41 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -156,14 +156,14 @@ namespace Avalonia.Controls var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height); - if ((curLineSize.U + sz.U) > uvFinalSize.U) //need to switch to another line + if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line { arrangeLine(accumulatedV, curLineSize.V, firstInLine, i); accumulatedV += curLineSize.V; curLineSize = sz; - if (sz.U > uvFinalSize.U) //the element is wider then the constraint - give it a separate line + if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line { //switch to next line which only contain one element arrangeLine(accumulatedV, sz.V, i, ++i); From ec891d8e8b1b6ac36be153afde70890b35e52636 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 31 May 2019 18:27:46 +0800 Subject: [PATCH 097/130] =?UTF-8?q?Added=20tests=20from=20WpfGridTests,=20?= =?UTF-8?q?Special=20thanks=20to=20@wieslawsoltes=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GridMultiTests.cs | 1067 +++++++++++++++++ .../SharedSizeScopeTests.cs | 284 ----- 2 files changed, 1067 insertions(+), 284 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/GridMultiTests.cs delete mode 100644 tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs diff --git a/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs b/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs new file mode 100644 index 0000000000..a37f925039 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs @@ -0,0 +1,1067 @@ +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Platform; +using Avalonia.UnitTests; + +using Moq; +using Xunit; +using Xunit.Abstractions; + +namespace Avalonia.Controls.UnitTests +{ + public class GridMultiTests + { + private readonly ITestOutputHelper output; + + public GridMultiTests(ITestOutputHelper output) + { + this.output = output; + } + + private void PrintColumnDefinitions(Grid grid) + { + output.WriteLine($"[Grid] ActualWidth: {grid.Bounds.Width} ActualHeight: {grid.Bounds.Width}"); + output.WriteLine($"[ColumnDefinitions]"); + for (int i = 0; i < grid.ColumnDefinitions.Count; i++) + { + var cd = grid.ColumnDefinitions[i]; + output.WriteLine($"[{i}] ActualWidth: {cd.ActualWidth} SharedSizeGroup: {cd.SharedSizeGroup}"); + } + } + + [Fact] + public void Grid_GridLength_Same_Size_Pixel_0() + { + var grid = CreateGrid( + (null, new GridLength()), + (null, new GridLength()), + (null, new GridLength()), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, false); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == null), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void Grid_GridLength_Same_Size_Pixel_50() + { + var grid = CreateGrid( + (null, new GridLength(50)), + (null, new GridLength(50)), + (null, new GridLength(50)), + (null, new GridLength(50))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, false); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == null), cd => Assert.Equal(50, cd.ActualWidth)); + } + + [Fact] + public void Grid_GridLength_Same_Size_Auto() + { + var grid = CreateGrid( + (null, new GridLength(0, GridUnitType.Auto)), + (null, new GridLength(0, GridUnitType.Auto)), + (null, new GridLength(0, GridUnitType.Auto)), + (null, new GridLength(0, GridUnitType.Auto))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, false); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == null), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void Grid_GridLength_Same_Size_Star() + { + var grid = CreateGrid( + (null, new GridLength(1, GridUnitType.Star)), + (null, new GridLength(1, GridUnitType.Star)), + (null, new GridLength(1, GridUnitType.Star)), + (null, new GridLength(1, GridUnitType.Star))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, false); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == null), cd => Assert.Equal(50, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_0() + { + var grid = CreateGrid( + ("A", new GridLength()), + ("A", new GridLength()), + ("A", new GridLength()), + ("A", new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_50() + { + var grid = CreateGrid( + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Auto() + { + var grid = CreateGrid( + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Star() + { + var grid = CreateGrid( + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star))); // Star sizing is treated as Auto, 1 is ignored + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_0_First_Column_0() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength()), + ("A", new GridLength()), + ("A", new GridLength()), + ("A", new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_50_First_Column_0() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Auto_First_Column_0() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Star_First_Column_0() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star))); // Star sizing is treated as Auto, 1 is ignored + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_0_Last_Column_0() + { + var grid = CreateGrid( + ("A", new GridLength()), + ("A", new GridLength()), + ("A", new GridLength()), + ("A", new GridLength()), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_50_Last_Column_0() + { + var grid = CreateGrid( + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50)), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Auto_Last_Column_0() + { + var grid = CreateGrid( + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Star_Last_Column_0() + { + var grid = CreateGrid( + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_0_First_And_Last_Column_0() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength()), + ("A", new GridLength()), + ("A", new GridLength()), + ("A", new GridLength()), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_50_First_And_Last_Column_0() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50)), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Auto_First_And_Last_Column_0() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Star_First_And_Last_Column_0() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_0_Two_Groups() + { + var grid = CreateGrid( + ("A", new GridLength()), + ("B", new GridLength()), + ("B", new GridLength()), + ("A", new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_50_Two_Groups() + { + var grid = CreateGrid( + ("A", new GridLength(25)), + ("B", new GridLength(75)), + ("B", new GridLength(75)), + ("A", new GridLength(25))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(25, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(75, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Auto_Two_Groups() + { + var grid = CreateGrid( + ("A", new GridLength(0, GridUnitType.Auto)), + ("B", new GridLength(0, GridUnitType.Auto)), + ("B", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Star_Two_Groups() + { + var grid = CreateGrid( + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("B", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("B", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star))); // Star sizing is treated as Auto, 1 is ignored + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_0_First_Column_0_Two_Groups() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength()), + ("B", new GridLength()), + ("B", new GridLength()), + ("A", new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_50_First_Column_0_Two_Groups() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(25)), + ("B", new GridLength(75)), + ("B", new GridLength(75)), + ("A", new GridLength(25))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(25, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(75, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Auto_First_Column_0_Two_Groups() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(0, GridUnitType.Auto)), + ("B", new GridLength(0, GridUnitType.Auto)), + ("B", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Star_First_Column_0_Two_Groups() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("B", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("B", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star))); // Star sizing is treated as Auto, 1 is ignored + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_0_Last_Column_0_Two_Groups() + { + var grid = CreateGrid( + ("A", new GridLength()), + ("B", new GridLength()), + ("B", new GridLength()), + ("A", new GridLength()), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_50_Last_Column_0_Two_Groups() + { + var grid = CreateGrid( + ("A", new GridLength(25)), + ("B", new GridLength(75)), + ("B", new GridLength(75)), + ("A", new GridLength(25)), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(25, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(75, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Auto_Last_Column_0_Two_Groups() + { + var grid = CreateGrid( + ("A", new GridLength(0, GridUnitType.Auto)), + ("B", new GridLength(0, GridUnitType.Auto)), + ("B", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Star_Last_Column_0_Two_Groups() + { + var grid = CreateGrid( + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("B", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("B", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_0_First_And_Last_Column_0_Two_Groups() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength()), + ("B", new GridLength()), + ("B", new GridLength()), + ("A", new GridLength()), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Pixel_50_First_And_Last_Column_0_Two_Groups() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(25)), + ("B", new GridLength(75)), + ("B", new GridLength(75)), + ("A", new GridLength(25)), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(25, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(75, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Auto_First_And_Last_Column_0_Two_Groups() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(0, GridUnitType.Auto)), + ("B", new GridLength(0, GridUnitType.Auto)), + ("B", new GridLength(0, GridUnitType.Auto)), + ("A", new GridLength(0, GridUnitType.Auto)), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void SharedSize_Grid_GridLength_Same_Size_Star_First_And_Last_Column_0_Two_Groups() + { + var grid = CreateGrid( + (null, new GridLength()), + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("B", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("B", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + ("A", new GridLength(1, GridUnitType.Star)), // Star sizing is treated as Auto, 1 is ignored + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + // [Fact] + // public void Size_Propagation_Is_Constrained_To_Innermost_Scope() + // { + // var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + // var innerScope = new Grid(); + // foreach(var xgrids in grids) + // innerScope.Children.Add(xgrids); + // innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); + + // var outerGrid = CreateGrid(("A", new GridLength(0))); + // var outerScope = new Grid(); + // outerScope.Children.Add(outerGrid); + // outerScope.Children.Add(innerScope); + + // var root = new Grid(); + // root.SetValue(Grid.IsSharedSizeScopeProperty, true); + // root.Children.Add(outerScope); + + // root.Measure(new Size(50, 50)); + // root.Arrange(new Rect(new Point(), new Point(50, 50))); + // Assert.Equal(1, outerGrid.ColumnDefinitions[0].ActualWidth); + // } + + [Fact] + public void Size_Group_Changes_Are_Tracked() + { + var grids = new[] { + CreateGrid((null, new GridLength(0, GridUnitType.Auto)), (null, new GridLength())), + CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + var scope = new Grid(); + foreach (var xgrids in grids) + scope.Children.Add(xgrids); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + PrintColumnDefinitions(grids[0]); + Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); + + grids[0].ColumnDefinitions[0].SharedSizeGroup = "A"; + + root.Measure(new Size(51, 51)); + root.Arrange(new Rect(new Point(), new Point(51, 51))); + PrintColumnDefinitions(grids[0]); + Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); + + grids[0].ColumnDefinitions[0].SharedSizeGroup = null; + + root.Measure(new Size(52, 52)); + root.Arrange(new Rect(new Point(), new Point(52, 52))); + PrintColumnDefinitions(grids[0]); + Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); + } + + [Fact] + public void Collection_Changes_Are_Tracked() + { + var grid = CreateGrid( + ("A", new GridLength(20)), + ("A", new GridLength(30)), + ("A", new GridLength(40)), + (null, new GridLength())); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(40, cd.ActualWidth)); + + grid.ColumnDefinitions.RemoveAt(2); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); + + grid.ColumnDefinitions.Insert(1, new ColumnDefinition { Width = new GridLength(30), SharedSizeGroup = "A" }); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); + + grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(10), SharedSizeGroup = "A" }; + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); + + grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(50), SharedSizeGroup = "A" }; + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + [Fact] + public void Size_Priorities_Are_Maintained() + { + var sizers = new List(); + var grid = CreateGrid( + ("A", new GridLength(20)), + ("A", new GridLength(20, GridUnitType.Auto)), + ("A", new GridLength(1, GridUnitType.Star)), + ("A", new GridLength(1, GridUnitType.Star)), + (null, new GridLength())); + for (int i = 0; i < 3; i++) + sizers.Add(AddSizer(grid, i, 6 + i * 6)); + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(100, 100)); + grid.Arrange(new Rect(new Point(), new Point(100, 100))); + PrintColumnDefinitions(grid); + // all in group are equal to the first fixed column + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(19, cd.ActualWidth - 1)); + + grid.ColumnDefinitions[0].SharedSizeGroup = null; + + grid.Measure(new Size(100, 100)); + grid.Arrange(new Rect(new Point(), new Point(100, 100))); + PrintColumnDefinitions(grid); + // all in group are equal to width (MinWidth) of the sizer in the second column + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(20, cd.ActualWidth)); + + grid.ColumnDefinitions[1].SharedSizeGroup = null; + + grid.Measure(new Size(double.PositiveInfinity, 100)); + grid.Arrange(new Rect(new Point(), new Point(100, 100))); + PrintColumnDefinitions(grid); + // with no constraint star columns default to the MinWidth of the sizer in the column + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); + } + + // grid creators + // private Grid CreateGrid(params string[] columnGroups) + // { + // return CreateGrid(columnGroups.Select(s => (s, (double)ColumnDefinition.WidthProperty.DefaultMetadata.DefaultValue)).ToArray()); + // } + + private Grid CreateGrid(params (string name, GridLength width)[] columns) + { + return CreateGrid(columns.Select(c => + (c.name, c.width, ColumnDefinition.MinWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + } + + private Grid CreateGrid(params (string name, GridLength width, double minWidth)[] columns) + { + return CreateGrid(columns.Select(c => + (c.name, c.width, c.minWidth, ColumnDefinition.MaxWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); + } + + private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns) + { + + var grid = new Grid(); + foreach (var k in columns.Select(c => new ColumnDefinition + { + SharedSizeGroup = c.name, + Width = c.width, + MinWidth = c.minWidth, + MaxWidth = c.maxWidth + })) + grid.ColumnDefinitions.Add(k); + + return grid; + } + + private Control AddSizer(Grid grid, int column, double size = 30) + { + var ctrl = new Control { MinWidth = size, MinHeight = size }; + ctrl.SetValue(Grid.ColumnProperty, column); + grid.Children.Add(ctrl); + return ctrl; + } + } + + +} \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs b/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs deleted file mode 100644 index 12d4df32e5..0000000000 --- a/tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Avalonia.Controls.Primitives; -using Avalonia.Input; -using Avalonia.Platform; -using Avalonia.UnitTests; - -using Moq; -using Xunit; - -namespace Avalonia.Controls.UnitTests -{ - public class SharedSizeScopeTests - { - public bool HasSharedSizeScope(Control control) - { - return control.GetValue(DefinitionBase.PrivateSharedSizeScopeProperty) != null; - } - - [Fact] - public void All_Descendant_Grids_Are_Registered_When_Added_After_Setting_Scope() - { - var grids = new[] { new Grid(), new Grid(), new Grid() }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - Assert.All(grids, g => Assert.True(HasSharedSizeScope(g))); - } - - [Fact] - public void All_Descendant_Grids_Are_Registered_When_Setting_Scope() - { - var grids = new[] { new Grid(), new Grid(), new Grid() }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.Child = scope; - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - - Assert.All(grids, g => Assert.True(HasSharedSizeScope(g))); - } - - [Fact] - public void All_Descendant_Grids_Are_Unregistered_When_Resetting_Scope() - { - var grids = new[] { new Grid(), new Grid(), new Grid() }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - Assert.All(grids, g => Assert.True(HasSharedSizeScope(g))); - root.SetValue(Grid.IsSharedSizeScopeProperty, false); - Assert.All(grids, g => Assert.False(HasSharedSizeScope(g))); - Assert.Equal(null, root.GetValue(DefinitionBase.PrivateSharedSizeScopeProperty)); - } - - [Fact] - public void Size_Is_Propagated_Between_Grids() - { - var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); - } - - [Fact] - public void Size_Propagation_Is_Constrained_To_Innermost_Scope() - { - var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - var innerScope = new Panel(); - innerScope.Children.AddRange(grids); - innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); - - var outerGrid = CreateGrid(("A", new GridLength(0))); - var outerScope = new Panel(); - outerScope.Children.AddRange(new[] { outerGrid, innerScope }); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = outerScope; - - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(0, outerGrid.ColumnDefinitions[0].ActualWidth); - } - - [Fact] - public void Size_Is_Propagated_Between_Rows_And_Columns() - { - var grid = new Grid - { - ColumnDefinitions = new ColumnDefinitions("*,30"), - RowDefinitions = new RowDefinitions("*,10") - }; - - grid.ColumnDefinitions[1].SharedSizeGroup = "A"; - grid.RowDefinitions[1].SharedSizeGroup = "A"; - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = grid; - - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(30, grid.RowDefinitions[1].ActualHeight); - } - - [Fact] - public void Size_Group_Changes_Are_Tracked() - { - var grids = new[] { - CreateGrid((null, new GridLength(0, GridUnitType.Auto)), (null, new GridLength())), - CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - var scope = new Panel(); - scope.Children.AddRange(grids); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - root.Measure(new Size(50, 50)); - root.Arrange(new Rect(new Point(), new Point(50, 50))); - Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); - - grids[0].ColumnDefinitions[0].SharedSizeGroup = "A"; - - root.Measure(new Size(51, 51)); - root.Arrange(new Rect(new Point(), new Point(51, 51))); - Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth); - - grids[0].ColumnDefinitions[0].SharedSizeGroup = null; - - root.Measure(new Size(52, 52)); - root.Arrange(new Rect(new Point(), new Point(52, 52))); - Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth); - } - - [Fact] - public void Collection_Changes_Are_Tracked() - { - var grid = CreateGrid( - ("A", new GridLength(20)), - ("A", new GridLength(30)), - ("A", new GridLength(40)), - (null, new GridLength())); - - var scope = new Panel(); - scope.Children.Add(grid); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(40, cd.ActualWidth)); - - grid.ColumnDefinitions.RemoveAt(2); - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); - - grid.ColumnDefinitions.Insert(1, new ColumnDefinition { Width = new GridLength(35), SharedSizeGroup = "A" }); - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(35, cd.ActualWidth)); - - grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(10), SharedSizeGroup = "A" }; - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth)); - - grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(50), SharedSizeGroup = "A" }; - - grid.Measure(new Size(200, 200)); - grid.Arrange(new Rect(new Point(), new Point(200, 200))); - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); - } - - [Fact] - public void Size_Priorities_Are_Maintained() - { - var sizers = new List(); - var grid = CreateGrid( - ("A", new GridLength(20)), - ("A", new GridLength(20, GridUnitType.Auto)), - ("A", new GridLength(1, GridUnitType.Star)), - ("A", new GridLength(1, GridUnitType.Star)), - (null, new GridLength())); - for (int i = 0; i < 3; i++) - sizers.Add(AddSizer(grid, i, 6 + i * 6)); - var scope = new Panel(); - scope.Children.Add(grid); - - var root = new TestRoot(); - root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Child = scope; - - grid.Measure(new Size(100, 100)); - grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // all in group are equal to the first fixed column - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(20, cd.ActualWidth)); - - grid.ColumnDefinitions[0].SharedSizeGroup = null; - - grid.Measure(new Size(100, 100)); - grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // all in group are equal to width (MinWidth) of the sizer in the second column - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth)); - - grid.ColumnDefinitions[1].SharedSizeGroup = null; - - grid.Measure(new Size(double.PositiveInfinity, 100)); - grid.Arrange(new Rect(new Point(), new Point(100, 100))); - // with no constraint star columns default to the MinWidth of the sizer in the column - Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 2 * 6, cd.ActualWidth)); - } - - // grid creators - private Grid CreateGrid(params string[] columnGroups) - { - return CreateGrid(columnGroups.Select(s => (s, ColumnDefinition.WidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - } - - private Grid CreateGrid(params (string name, GridLength width)[] columns) - { - return CreateGrid(columns.Select(c => - (c.name, c.width, ColumnDefinition.MinWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - } - - private Grid CreateGrid(params (string name, GridLength width, double minWidth)[] columns) - { - return CreateGrid(columns.Select(c => - (c.name, c.width, c.minWidth, ColumnDefinition.MaxWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray()); - } - - private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns) - { - var columnDefinitions = new ColumnDefinitions(); - - columnDefinitions.AddRange( - columns.Select(c => new ColumnDefinition - { - SharedSizeGroup = c.name, - Width = c.width, - MinWidth = c.minWidth, - MaxWidth = c.maxWidth - }) - ); - var grid = new Grid - { - ColumnDefinitions = columnDefinitions - }; - - return grid; - } - - private Control AddSizer(Grid grid, int column, double size = 30) - { - var ctrl = new Control { MinWidth = size, MinHeight = size }; - ctrl.SetValue(Grid.ColumnProperty, column); - grid.Children.Add(ctrl); - return ctrl; - } - } -} \ No newline at end of file From 15f06a09a7d9447dc0bf3c55eeeb2bb8e2a38cde Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 31 May 2019 18:37:07 +0800 Subject: [PATCH 098/130] Add some tests --- .../GridMultiTests.cs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs b/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs index a37f925039..b91f98e735 100644 --- a/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs @@ -1020,6 +1020,77 @@ namespace Avalonia.Controls.UnitTests Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); } + [Fact] + public void ColumnDefinitions_Collection_Is_ReadOnly() + { + var grid = CreateGrid( + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50)), + ("A", new GridLength(50))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth)); + + grid.ColumnDefinitions[0] = new ColumnDefinition { Width = new GridLength(25), SharedSizeGroup = "A" }; + grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(75), SharedSizeGroup = "B" }; + grid.ColumnDefinitions[2] = new ColumnDefinition { Width = new GridLength(75), SharedSizeGroup = "B" }; + grid.ColumnDefinitions[3] = new ColumnDefinition { Width = new GridLength(25), SharedSizeGroup = "A" }; + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(25, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(75, cd.ActualWidth)); + } + + [Fact] + public void ColumnDefinitions_Collection_Reset_SharedSizeGroup() + { + var grid = CreateGrid( + ("A", new GridLength(25)), + ("B", new GridLength(75)), + ("B", new GridLength(75)), + ("A", new GridLength(25))); + + var scope = new Grid(); + scope.Children.Add(grid); + + var root = new Grid(); + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(scope); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(25, cd.ActualWidth)); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(75, cd.ActualWidth)); + + grid.ColumnDefinitions[0].SharedSizeGroup = null; + grid.ColumnDefinitions[0].Width = new GridLength(50); + grid.ColumnDefinitions[1].SharedSizeGroup = null; + grid.ColumnDefinitions[1].Width = new GridLength(50); + grid.ColumnDefinitions[2].SharedSizeGroup = null; + grid.ColumnDefinitions[2].Width = new GridLength(50); + grid.ColumnDefinitions[3].SharedSizeGroup = null; + grid.ColumnDefinitions[3].Width = new GridLength(50); + + grid.Measure(new Size(200, 200)); + grid.Arrange(new Rect(new Point(), new Point(200, 200))); + PrintColumnDefinitions(grid); + Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == null), cd => Assert.Equal(50, cd.ActualWidth)); + } + + // grid creators // private Grid CreateGrid(params string[] columnGroups) // { From 4337e22c7c282584f6d3d10842e0f7a5271297a5 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 31 May 2019 20:50:29 +0800 Subject: [PATCH 099/130] Update GridMultiTests --- .../GridMultiTests.cs | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs b/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs index b91f98e735..66f55e38c0 100644 --- a/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs @@ -44,6 +44,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, false); root.Children.Add(scope); @@ -66,6 +67,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, false); root.Children.Add(scope); @@ -88,6 +90,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, false); root.Children.Add(scope); @@ -110,6 +113,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, false); root.Children.Add(scope); @@ -132,6 +136,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -154,6 +159,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -176,6 +182,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -198,6 +205,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -221,6 +229,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -244,6 +253,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -267,6 +277,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -290,6 +301,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -313,6 +325,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -336,6 +349,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -359,6 +373,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -382,6 +397,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -406,6 +422,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -430,6 +447,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -454,6 +472,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -478,6 +497,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -500,6 +520,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -523,6 +544,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -546,6 +568,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -569,6 +592,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -593,6 +617,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -617,6 +642,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -641,6 +667,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -665,6 +692,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -689,6 +717,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -713,6 +742,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -737,6 +767,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -761,6 +792,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -786,6 +818,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -811,6 +844,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -836,6 +870,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -861,6 +896,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -885,7 +921,8 @@ namespace Avalonia.Controls.UnitTests // outerScope.Children.Add(outerGrid); // outerScope.Children.Add(innerScope); - // var root = new Grid(); + // var root = new Grid(); + // root.UseLayoutRounding = false; // root.SetValue(Grid.IsSharedSizeScopeProperty, true); // root.Children.Add(outerScope); @@ -905,6 +942,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(xgrids); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -941,6 +979,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -994,6 +1033,7 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); root.Children.Add(scope); @@ -1020,7 +1060,7 @@ namespace Avalonia.Controls.UnitTests Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(0, cd.ActualWidth)); } - [Fact] + [Fact] public void ColumnDefinitions_Collection_Is_ReadOnly() { var grid = CreateGrid( @@ -1033,8 +1073,9 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Children.Add(scope); + root.Children.Add(scope); grid.Measure(new Size(200, 200)); grid.Arrange(new Rect(new Point(), new Point(200, 200))); @@ -1066,8 +1107,9 @@ namespace Avalonia.Controls.UnitTests scope.Children.Add(grid); var root = new Grid(); + root.UseLayoutRounding = false; root.SetValue(Grid.IsSharedSizeScopeProperty, true); - root.Children.Add(scope); + root.Children.Add(scope); grid.Measure(new Size(200, 200)); grid.Arrange(new Rect(new Point(), new Point(200, 200))); @@ -1078,11 +1120,11 @@ namespace Avalonia.Controls.UnitTests grid.ColumnDefinitions[0].SharedSizeGroup = null; grid.ColumnDefinitions[0].Width = new GridLength(50); grid.ColumnDefinitions[1].SharedSizeGroup = null; - grid.ColumnDefinitions[1].Width = new GridLength(50); + grid.ColumnDefinitions[1].Width = new GridLength(50); grid.ColumnDefinitions[2].SharedSizeGroup = null; - grid.ColumnDefinitions[2].Width = new GridLength(50); + grid.ColumnDefinitions[2].Width = new GridLength(50); grid.ColumnDefinitions[3].SharedSizeGroup = null; - grid.ColumnDefinitions[3].Width = new GridLength(50); + grid.ColumnDefinitions[3].Width = new GridLength(50); grid.Measure(new Size(200, 200)); grid.Arrange(new Rect(new Point(), new Point(200, 200))); From 78e6ef6e53418fc7bc3728cd4b8cc157e12643ef Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 31 May 2019 22:21:59 +0800 Subject: [PATCH 100/130] Fixes defBase behavior when removed/added from collection. --- src/Avalonia.Controls/ColumnDefinitions.cs | 29 ++++++++++++++ src/Avalonia.Controls/DefinitionBase.cs | 2 +- src/Avalonia.Controls/Grid.cs | 46 ++++++---------------- src/Avalonia.Controls/RowDefinitions.cs | 28 +++++++++++++ 4 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/Avalonia.Controls/ColumnDefinitions.cs b/src/Avalonia.Controls/ColumnDefinitions.cs index ecfe6027ac..ae7756e7d1 100644 --- a/src/Avalonia.Controls/ColumnDefinitions.cs +++ b/src/Avalonia.Controls/ColumnDefinitions.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; @@ -17,6 +19,33 @@ namespace Avalonia.Controls public ColumnDefinitions() { ResetBehavior = ResetBehavior.Remove; + CollectionChanged += OnCollectionChanged; + this.TrackItemPropertyChanged(delegate { IsDirty = true; }); + } + + internal bool IsDirty { get; set; } = true; + internal Grid Parent { get; set; } + + private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + foreach (var nI in this.Select((d, i) => (d, i))) + nI.d._parentIndex = nI.i; + + foreach (var nD in e.NewItems?.Cast() + ?? Enumerable.Empty()) + { + nD.Parent = this.Parent; + nD.OnEnterParentTree(); + } + + foreach (var oD in e.OldItems?.Cast() + ?? Enumerable.Empty()) + { + oD.Parent = null; + oD.OnExitParentTree(); + } + + IsDirty = true; } /// diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index e0ed8aa7d7..36dcb714c4 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -623,7 +623,7 @@ namespace Avalonia.Controls #region Private Fields private readonly bool _isColumnDefinition; // when "true", this is a ColumnDefinition; when "false" this is a RowDefinition (faster than a type check) private Flags _flags; // flags reflecting various aspects of internal state - private int _parentIndex = -1; // this instance's index in parent's children collection + internal int _parentIndex = -1; // this instance's index in parent's children collection private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content" diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 55298abb3a..240ebd4091 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -216,7 +216,7 @@ namespace Avalonia.Controls get { if (_data == null) { _data = new ExtendedData(); } - if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions(); } + if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; } return (_data.ColumnDefinitions); } @@ -224,6 +224,7 @@ namespace Avalonia.Controls { if (_data == null) { _data = new ExtendedData(); } _data.ColumnDefinitions = value; + _data.ColumnDefinitions.Parent = this; } } @@ -235,7 +236,7 @@ namespace Avalonia.Controls get { if (_data == null) { _data = new ExtendedData(); } - if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions(); } + if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions() { Parent = this }; } return (_data.RowDefinitions); } @@ -243,6 +244,7 @@ namespace Avalonia.Controls { if (_data == null) { _data = new ExtendedData(); } _data.RowDefinitions = value; + _data.RowDefinitions.Parent = this; } } @@ -769,20 +771,20 @@ namespace Avalonia.Controls /// /// Convenience accessor to ValidDefinitionsUStructure bit flag. - /// + /// internal bool ColumnDefinitionsDirty { - get { return (!CheckFlagsAnd(Flags.ValidDefinitionsUStructure)); } - set { SetFlags(!value, Flags.ValidDefinitionsUStructure); } + get => ColumnDefinitions?.IsDirty ?? false; + set => ColumnDefinitions.IsDirty = value; } /// /// Convenience accessor to ValidDefinitionsVStructure bit flag. /// - internal bool RowDefinitionsDirty + internal bool RowDefinitionsDirty { - get { return (!CheckFlagsAnd(Flags.ValidDefinitionsVStructure)); } - set { SetFlags(!value, Flags.ValidDefinitionsVStructure); } + get => RowDefinitions?.IsDirty ?? false; + set => RowDefinitions.IsDirty = value; } //------------------------------------------------------ @@ -944,23 +946,10 @@ namespace Avalonia.Controls } else { - foreach(var definition in extData.DefinitionsU - ?? Enumerable.Empty()) - definition.OnExitParentTree(); - extData.DefinitionsU = extData.ColumnDefinitions; } } - // adds index information. - for(int i = 0; i < extData.DefinitionsU.Count;i++) - { - var definition = extData.DefinitionsU[i]; - definition.Parent = this; - definition.Index = i; - definition.OnEnterParentTree(); - } - ColumnDefinitionsDirty = false; } @@ -997,24 +986,11 @@ namespace Avalonia.Controls extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() }; } else - { - foreach(var definition in extData.DefinitionsV - ?? Enumerable.Empty()) - definition.OnExitParentTree(); - + { extData.DefinitionsV = extData.RowDefinitions; } } - // adds index information. - for(int i = 0; i < extData.DefinitionsV.Count;i++) - { - var definition = extData.DefinitionsV[i]; - definition.Parent = this; - definition.Index = i; - definition.OnEnterParentTree(); - } - RowDefinitionsDirty = false; } diff --git a/src/Avalonia.Controls/RowDefinitions.cs b/src/Avalonia.Controls/RowDefinitions.cs index 1a14cc78f3..c12d284977 100644 --- a/src/Avalonia.Controls/RowDefinitions.cs +++ b/src/Avalonia.Controls/RowDefinitions.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; @@ -17,6 +18,33 @@ namespace Avalonia.Controls public RowDefinitions() { ResetBehavior = ResetBehavior.Remove; + CollectionChanged += OnCollectionChanged; + this.TrackItemPropertyChanged(delegate { IsDirty = true; }); + } + + internal bool IsDirty { get; set; } = true; + internal Grid Parent { get; set; } + + private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + foreach (var nI in this.Select((d, i) => (d, i))) + nI.d._parentIndex = nI.i; + + foreach (var nD in e.NewItems?.Cast() + ?? Enumerable.Empty()) + { + nD.Parent = this.Parent; + nD.OnEnterParentTree(); + } + + foreach (var oD in e.OldItems?.Cast() + ?? Enumerable.Empty()) + { + oD.Parent = null; + oD.OnExitParentTree(); + } + + IsDirty = true; } /// From 7c222a4f698bbb0fab2c3742a1914165672421b6 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 31 May 2019 22:26:44 +0800 Subject: [PATCH 101/130] Uncomment a disabled unit-test. --- .../GridMultiTests.cs | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs b/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs index 66f55e38c0..50acad0cde 100644 --- a/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridMultiTests.cs @@ -907,29 +907,31 @@ namespace Avalonia.Controls.UnitTests Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "B"), cd => Assert.Equal(0, cd.ActualWidth)); } - // [Fact] - // public void Size_Propagation_Is_Constrained_To_Innermost_Scope() - // { - // var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; - // var innerScope = new Grid(); - // foreach(var xgrids in grids) - // innerScope.Children.Add(xgrids); - // innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); - - // var outerGrid = CreateGrid(("A", new GridLength(0))); - // var outerScope = new Grid(); - // outerScope.Children.Add(outerGrid); - // outerScope.Children.Add(innerScope); - - // var root = new Grid(); - // root.UseLayoutRounding = false; - // root.SetValue(Grid.IsSharedSizeScopeProperty, true); - // root.Children.Add(outerScope); - - // root.Measure(new Size(50, 50)); - // root.Arrange(new Rect(new Point(), new Point(50, 50))); - // Assert.Equal(1, outerGrid.ColumnDefinitions[0].ActualWidth); - // } + [Fact] + public void Size_Propagation_Is_Constrained_To_Innermost_Scope() + { + var grids = new[] { CreateGrid(("A", new GridLength())), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) }; + var innerScope = new Grid(); + + foreach (var grid in grids) + innerScope.Children.Add(grid); + + innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true); + + var outerGrid = CreateGrid(("A", new GridLength(0))); + var outerScope = new Grid(); + outerScope.Children.Add(outerGrid); + outerScope.Children.Add(innerScope); + + var root = new Grid(); + root.UseLayoutRounding = false; + root.SetValue(Grid.IsSharedSizeScopeProperty, true); + root.Children.Add(outerScope); + + root.Measure(new Size(50, 50)); + root.Arrange(new Rect(new Point(), new Point(50, 50))); + Assert.Equal(0, outerGrid.ColumnDefinitions[0].ActualWidth); + } [Fact] public void Size_Group_Changes_Are_Tracked() From 2b0382481123d70f48b8224b00c6d8bc3e7bc606 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 31 May 2019 23:37:51 +0800 Subject: [PATCH 102/130] Make a new base class for both Row/ColumnDefinitions. --- src/Avalonia.Controls/ColumnDefinitions.cs | 27 +---------- src/Avalonia.Controls/DefinitionBase.cs | 2 +- src/Avalonia.Controls/DefinitionList.cs | 56 ++++++++++++++++++++++ src/Avalonia.Controls/Grid.cs | 8 ++-- src/Avalonia.Controls/RowDefinitions.cs | 28 +---------- 5 files changed, 63 insertions(+), 58 deletions(-) create mode 100644 src/Avalonia.Controls/DefinitionList.cs diff --git a/src/Avalonia.Controls/ColumnDefinitions.cs b/src/Avalonia.Controls/ColumnDefinitions.cs index ae7756e7d1..7fb763c6fd 100644 --- a/src/Avalonia.Controls/ColumnDefinitions.cs +++ b/src/Avalonia.Controls/ColumnDefinitions.cs @@ -11,7 +11,7 @@ namespace Avalonia.Controls /// /// A collection of s. /// - public class ColumnDefinitions : AvaloniaList + public class ColumnDefinitions : DefinitionList { /// /// Initializes a new instance of the class. @@ -23,31 +23,6 @@ namespace Avalonia.Controls this.TrackItemPropertyChanged(delegate { IsDirty = true; }); } - internal bool IsDirty { get; set; } = true; - internal Grid Parent { get; set; } - - private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - foreach (var nI in this.Select((d, i) => (d, i))) - nI.d._parentIndex = nI.i; - - foreach (var nD in e.NewItems?.Cast() - ?? Enumerable.Empty()) - { - nD.Parent = this.Parent; - nD.OnEnterParentTree(); - } - - foreach (var oD in e.OldItems?.Cast() - ?? Enumerable.Empty()) - { - oD.Parent = null; - oD.OnExitParentTree(); - } - - IsDirty = true; - } - /// /// Initializes a new instance of the class. /// diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 36dcb714c4..8899c38bf9 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -315,7 +315,7 @@ namespace Avalonia.Controls } set { - Debug.Assert(value >= -1 && _parentIndex != value); + Debug.Assert(value >= -1); _parentIndex = value; } } diff --git a/src/Avalonia.Controls/DefinitionList.cs b/src/Avalonia.Controls/DefinitionList.cs new file mode 100644 index 0000000000..97c8b7f7ec --- /dev/null +++ b/src/Avalonia.Controls/DefinitionList.cs @@ -0,0 +1,56 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Specialized; +using System.Linq; +using Avalonia.Collections; + +namespace Avalonia.Controls +{ + public abstract class DefinitionList : AvaloniaList where T : DefinitionBase + { + internal bool IsDirty = true; + private Grid _parent; + + internal Grid Parent + { + get => _parent; + set => SetParent(value); + } + + + private void SetParent(Grid value) + { + _parent = value; + + foreach (var pair in this.Select((definitions, index) => (definitions, index))) + { + pair.definitions.Parent = value; + pair.definitions.Index = pair.index; + } + } + + internal void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + foreach (var nI in this.Select((d, i) => (d, i))) + nI.d._parentIndex = nI.i; + + foreach (var nD in e.NewItems?.Cast() + ?? Enumerable.Empty()) + { + nD.Parent = this.Parent; + nD.OnEnterParentTree(); + } + + foreach (var oD in e.OldItems?.Cast() + ?? Enumerable.Empty()) + { + oD.Parent = null; + oD.OnExitParentTree(); + } + + IsDirty = true; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 240ebd4091..fc61c409f0 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -933,7 +933,7 @@ namespace Avalonia.Controls { if (extData.DefinitionsU == null) { - extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() }; + extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() { Parent = this } }; } } else @@ -942,7 +942,7 @@ namespace Avalonia.Controls { // if column definitions collection is empty // mockup array with one column - extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() }; + extData.DefinitionsU = new DefinitionBase[] { new ColumnDefinition() { Parent = this } }; } else { @@ -974,7 +974,7 @@ namespace Avalonia.Controls { if (extData.DefinitionsV == null) { - extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() }; + extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() { Parent = this } }; } } else @@ -983,7 +983,7 @@ namespace Avalonia.Controls { // if row definitions collection is empty // mockup array with one row - extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() }; + extData.DefinitionsV = new DefinitionBase[] { new RowDefinition() { Parent = this } }; } else { diff --git a/src/Avalonia.Controls/RowDefinitions.cs b/src/Avalonia.Controls/RowDefinitions.cs index c12d284977..c5c8c75173 100644 --- a/src/Avalonia.Controls/RowDefinitions.cs +++ b/src/Avalonia.Controls/RowDefinitions.cs @@ -1,7 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; @@ -10,7 +9,7 @@ namespace Avalonia.Controls /// /// A collection of s. /// - public class RowDefinitions : AvaloniaList + public class RowDefinitions : DefinitionList { /// /// Initializes a new instance of the class. @@ -22,31 +21,6 @@ namespace Avalonia.Controls this.TrackItemPropertyChanged(delegate { IsDirty = true; }); } - internal bool IsDirty { get; set; } = true; - internal Grid Parent { get; set; } - - private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - foreach (var nI in this.Select((d, i) => (d, i))) - nI.d._parentIndex = nI.i; - - foreach (var nD in e.NewItems?.Cast() - ?? Enumerable.Empty()) - { - nD.Parent = this.Parent; - nD.OnEnterParentTree(); - } - - foreach (var oD in e.OldItems?.Cast() - ?? Enumerable.Empty()) - { - oD.Parent = null; - oD.OnExitParentTree(); - } - - IsDirty = true; - } - /// /// Initializes a new instance of the class. /// From c3c7e8d286fc157d059bb7744ed9c7aa6fedc066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Fri, 31 May 2019 17:53:08 +0200 Subject: [PATCH 103/130] Fix copy and paste error --- src/Avalonia.Controls/WrapPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index fcc21a7b41..4df1b39400 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -194,7 +194,7 @@ namespace Avalonia.Controls double u = 0; bool isHorizontal = (Orientation == Orientation.Horizontal); - for (int i = 0, count = Children.Count; i < count; i++) + for (int i = start; i < end; i++) { var child = Children[i]; if (child != null) From 36d24bcea207254b1323670dd333ab3ba64a4218 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 31 May 2019 17:15:25 +0100 Subject: [PATCH 104/130] improved notification colours. --- src/Avalonia.Themes.Default/Accents/BaseDark.xaml | 10 +++++----- src/Avalonia.Themes.Default/Accents/BaseLight.xaml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index f84e09510b..8f7d56dbc6 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -46,11 +46,11 @@ - - - - - + + + + + 1,1,1,1 0.5 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index 18c32b02bc..666596d710 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -46,11 +46,11 @@ - - - - - + + + + + 1 0.5 From bf8e23457172aeb73d4d54aea8ed711ddc200cd9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 31 May 2019 17:36:04 +0100 Subject: [PATCH 105/130] allow notification card size to be controlled with styles. --- src/Avalonia.Themes.Default/NotificationCard.xaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/NotificationCard.xaml b/src/Avalonia.Themes.Default/NotificationCard.xaml index e94cb33d1e..bcc7bb9a29 100644 --- a/src/Avalonia.Themes.Default/NotificationCard.xaml +++ b/src/Avalonia.Themes.Default/NotificationCard.xaml @@ -13,7 +13,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Margin="8,8,0,0"> - + @@ -40,6 +40,10 @@ + + - + DataGrid A control for displaying and interacting with a data source. @@ -52,4 +52,4 @@ - \ No newline at end of file + From 85a5288fdd835481764ffc6c10dfb77c90708c3a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 6 Jun 2019 12:47:05 +0300 Subject: [PATCH 124/130] Propagate mouse pointer capture to Win32 API --- src/Avalonia.Input/MouseDevice.cs | 9 ++++-- src/Avalonia.Input/Pointer.cs | 6 ++++ .../Wpf/WpfMouseDevice.cs | 28 +++++++++++++------ .../Input/WindowsMouseDevice.cs | 26 ++++++++++++----- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 ++ 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 4c4d679087..ee7d0c9501 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -20,8 +20,13 @@ namespace Avalonia.Input private Rect _lastClickRect; private ulong _lastClickTime; - private readonly Pointer _pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); + private readonly Pointer _pointer; + public MouseDevice(Pointer pointer = null) + { + _pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); + } + /// /// Gets the control that is currently capturing by the mouse, if any. /// @@ -51,7 +56,7 @@ namespace Avalonia.Input /// within the control's bounds or not. The current mouse capture control is exposed /// by the property. /// - public virtual void Capture(IInputElement control) + public void Capture(IInputElement control) { _pointer.Capture(control); } diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs index 890ad57024..80d803abb1 100644 --- a/src/Avalonia.Input/Pointer.cs +++ b/src/Avalonia.Input/Pointer.cs @@ -27,6 +27,11 @@ namespace Avalonia.Input var seen = new HashSet(control1.GetSelfAndVisualAncestors().OfType()); return control2.GetSelfAndVisualAncestors().OfType().FirstOrDefault(seen.Contains); } + + protected virtual void PlatformCapture(IInputElement element) + { + + } public void Capture(IInputElement control) { @@ -34,6 +39,7 @@ namespace Avalonia.Input Captured.DetachedFromVisualTree -= OnCaptureDetached; var oldCapture = control; Captured = control; + PlatformCapture(control); if (oldCapture != null) { var commonParent = FindCommonParent(control, oldCapture); diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs index 0d93115714..ebfe8cde47 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs @@ -9,22 +9,32 @@ namespace Avalonia.Win32.Interop.Wpf { private readonly WpfTopLevelImpl _impl; - public WpfMouseDevice(WpfTopLevelImpl impl) + public WpfMouseDevice(WpfTopLevelImpl impl) : base(new WpfMousePointer(impl)) { _impl = impl; } - public override void Capture(IInputElement control) + class WpfMousePointer : Pointer { - if (control == null) + private readonly WpfTopLevelImpl _impl; + + public WpfMousePointer(WpfTopLevelImpl impl) : base(Pointer.GetNextFreeId(), PointerType.Mouse, true) + { + _impl = impl; + } + + protected override void PlatformCapture(IInputElement control) { - System.Windows.Input.Mouse.Capture(null); + if (control == null) + { + System.Windows.Input.Mouse.Capture(null); + } + else if ((control.GetVisualRoot() as EmbeddableControlRoot)?.PlatformImpl != _impl) + throw new ArgumentException("Visual belongs to unknown toplevel"); + else + System.Windows.Input.Mouse.Capture(_impl); } - else if ((control.GetVisualRoot() as EmbeddableControlRoot)?.PlatformImpl != _impl) - throw new ArgumentException("Visual belongs to unknown toplevel"); - else - System.Windows.Input.Mouse.Capture(_impl); - base.Capture(control); } + } } diff --git a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs index 2b4105efee..e7c379ad89 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs @@ -1,7 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using Avalonia.Controls; using Avalonia.Input; +using Avalonia.VisualTree; using Avalonia.Win32.Interop; namespace Avalonia.Win32.Input @@ -10,23 +13,32 @@ namespace Avalonia.Win32.Input { public static WindowsMouseDevice Instance { get; } = new WindowsMouseDevice(); + public WindowsMouseDevice() : base(new WindowsMousePointer()) + { + + } + public WindowImpl CurrentWindow { get; set; } - public override void Capture(IInputElement control) + class WindowsMousePointer : Pointer { - base.Capture(control); - - if (control != null) + public WindowsMousePointer() : base(Pointer.GetNextFreeId(),PointerType.Mouse, true) { - UnmanagedMethods.SetCapture(CurrentWindow.Handle.Handle); } - else + + protected override void PlatformCapture(IInputElement element) { - UnmanagedMethods.ReleaseCapture(); + var hwnd = ((element?.GetVisualRoot() as TopLevel)?.PlatformImpl as WindowImpl) + ?.Handle.Handle; + + if (hwnd.HasValue && hwnd != IntPtr.Zero) + UnmanagedMethods.SetCapture(hwnd.Value); + else + UnmanagedMethods.ReleaseCapture(); } } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9e8b2d58a6..5cc148fa0d 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -336,6 +336,7 @@ namespace Avalonia.Win32 public void BeginMoveDrag() { + WindowsMouseDevice.Instance.Capture(null); UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, new IntPtr((int)UnmanagedMethods.HitTestValues.HTCAPTION), IntPtr.Zero); } @@ -357,6 +358,7 @@ namespace Avalonia.Win32 #if USE_MANAGED_DRAG _managedDrag.BeginResizeDrag(edge, ScreenToClient(MouseDevice.Position)); #else + WindowsMouseDevice.Instance.Capture(null); UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, new IntPtr((int)EdgeDic[edge]), IntPtr.Zero); #endif From 39b19e8ff30a9154535fbd207961de9e6d838332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Fri, 7 Jun 2019 23:41:06 +0100 Subject: [PATCH 125/130] XML comment fixes. --- src/Avalonia.Base/Utilities/MathUtilities.cs | 5 ++++- src/Avalonia.Controls/Grid.cs | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 546133bb03..41b57b6e70 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -30,6 +30,7 @@ namespace Avalonia.Utilities /// LessThan - Returns whether or not the first double is less than the second double. /// That is, whether or not the first is strictly less than *and* not within epsilon of /// the other number. + /// /// The first double to compare. /// The second double to compare. public static bool LessThan(double value1, double value2) @@ -41,6 +42,7 @@ namespace Avalonia.Utilities /// GreaterThan - Returns whether or not the first double is greater than the second double. /// That is, whether or not the first is strictly greater than *and* not within epsilon of /// the other number. + /// /// The first double to compare. /// The second double to compare. public static bool GreaterThan(double value1, double value2) @@ -52,6 +54,7 @@ namespace Avalonia.Utilities /// LessThanOrClose - Returns whether or not the first double is less than or close to /// the second double. That is, whether or not the first is strictly less than or within /// epsilon of the other number. + /// /// The first double to compare. /// The second double to compare. public static bool LessThanOrClose(double value1, double value2) @@ -170,4 +173,4 @@ namespace Avalonia.Utilities } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index fc61c409f0..6d3b9f37cf 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -1969,7 +1969,7 @@ namespace Avalonia.Controls /// /// Array of definitions to process. /// Final size to lay out to. - /// True if sizing row definitions, false for columns + /// True if sizing column definitions, false for rows private void SetFinalSize( IReadOnlyList definitions, double finalSize, @@ -2694,9 +2694,9 @@ namespace Avalonia.Controls /// if the max constraint has higher discrepancy /// null if proportion doesn't fail a min or max constraint /// The discrepancy is the ratio of the proportion to the max- or min-ratio. - /// When both ratios hit the constraint, minRatio < proportion < maxRatio, + /// When both ratios hit the constraint, minRatio < proportion > maxRatio, /// and the minRatio has higher discrepancy if - /// (proportion / minRatio) > (maxRatio / proportion) + /// (proportion / minRatio) > (maxRatio / proportion) /// private static bool? Choose(double minRatio, double maxRatio, double proportion) { @@ -4063,4 +4063,4 @@ namespace Avalonia.Controls private static readonly Pen _evenDashPen; // second pen to draw dash } } -} \ No newline at end of file +} From fedbe118be3ef7b1c8cf59172c0ec84fbfe2a99a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 8 Jun 2019 17:48:12 +0300 Subject: [PATCH 126/130] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 12f683bd55..cf995f10fb 100644 --- a/readme.md +++ b/readme.md @@ -8,9 +8,9 @@ ## About -Avalonia is a WPF-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Linux (GTK), MacOS, Android and iOS. +Avalonia is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Linux (libX11), MacOS, Android (experimental) and iOS (exprerimental). -**Avalonia is currently in beta** which means that the framework is generally usable for writing applications, but there may be some bugs and breaking changes as we continue development. +**Avalonia is currently in beta** which means that the framework is generally usable for writing applications, but there may be some bugs and breaking changes as we continue development, for more details about the status see https://github.com/AvaloniaUI/Avalonia/issues/2239 | Control catalog | Desktop platforms | Mobile platforms | |---|---|---| From 86a48cf7d583539744c7f82611e5b02f4e636fae Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 11 Jun 2019 19:08:25 +0300 Subject: [PATCH 127/130] Fixed #2561 --- src/Avalonia.Build.Tasks/Program.cs | 11 ++++- .../XamlIlAvaloniaPropertyHelper.cs | 2 +- .../Xaml/XamlIlTests.cs | 42 +++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs index c2d0950264..d356b15408 100644 --- a/src/Avalonia.Build.Tasks/Program.cs +++ b/src/Avalonia.Build.Tasks/Program.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.IO; +using System.Linq; using Microsoft.Build.Framework; namespace Avalonia.Build.Tasks @@ -11,8 +12,14 @@ namespace Avalonia.Build.Tasks { if (args.Length != 3) { - Console.Error.WriteLine("input references output"); - return 1; + if (args.Length == 1) + args = new[] {"original.dll", "references", "out.dll"} + .Select(x => Path.Combine(args[0], x)).ToArray(); + else + { + Console.Error.WriteLine("input references output"); + return 1; + } } return new CompileAvaloniaXamlTask() diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 452bb05132..34a79a4cdc 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -64,7 +64,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions var tref = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true); forgedReference = new XamlIlAstNamePropertyReference(lineInfo, - tref, parsedPropertyName.name, tref); + tref, parsedPropertyName.name, selectorTypeReference); } var clrProperty = diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index 5e346e5289..b2cc44291d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -207,6 +207,33 @@ namespace Avalonia.Markup.Xaml.UnitTests xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'/>", typeof(XamlIlTests).Assembly); Assert.Equal(Design.GetDataContext(loaded), SomeStaticProperty); } + + [Fact] + public void Attached_Properties_From_Static_Types_Should_Work_In_Style_Setters_Bug_2561() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + + var parsed = (Window)AvaloniaXamlLoader.Parse(@" + + + + + + + +"); + var tb = ((TextBox)parsed.Content); + parsed.Show(); + tb.ApplyTemplate(); + Assert.Equal(100, XamlIlBugTestsStaticClassWithAttachedProperty.GetTestInt(tb)); + } + } } public class XamlIlBugTestsEventHandlerCodeBehind : Window @@ -272,4 +299,19 @@ namespace Avalonia.Markup.Xaml.UnitTests { } + public static class XamlIlBugTestsStaticClassWithAttachedProperty + { + public static readonly AvaloniaProperty TestIntProperty = AvaloniaProperty + .RegisterAttached("TestInt", typeof(XamlIlBugTestsStaticClassWithAttachedProperty)); + + public static void SetTestInt(Control control, int value) + { + control.SetValue(TestIntProperty, value); + } + + public static int GetTestInt(Control control) + { + return (int)control.GetValue(TestIntProperty); + } + } } From 143b6a3476537e27ed61424258d11947f94c219c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 11 Jun 2019 21:10:41 +0300 Subject: [PATCH 128/130] Explicitly ignore Setter's property type for animations --- .../AvaloniaXamlIlLanguage.cs | 2 +- .../AvaloniaXamlIlSetterTransformer.cs | 27 ++++++-- .../AvaloniaXamlIlWellKnownTypes.cs | 2 + .../XamlIlAvaloniaPropertyHelper.cs | 66 ++++++++++++++++++- 4 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs index 581dbcdac7..d5cc93ef66 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs @@ -165,7 +165,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions throw new XamlIlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node); if (!(node is XamlIlAstTextNode text)) throw new XamlIlLoadException("Property should be a text node", node); - result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text); + result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text, false); return true; } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index 0e7ea34bb7..d548d3a188 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -16,8 +16,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (!(node is XamlIlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Setter")) return node; - var parent = context.ParentNodes().OfType() - .FirstOrDefault(x => x.Type.GetClrType().FullName == "Avalonia.Styling.Style"); + + // This is a hack required to get complex animations (which are also a hack) to work + var inAnimation = false; + + XamlIlAstObjectNode parent = null; + + foreach (var p in context.ParentNodes().OfType()) + { + if (p.Type.GetClrType().FullName == "Avalonia.Styling.Style") + { + parent = p; + break; + } + + if (p.Type.GetClrType().FullName == "Avalonia.Animation.Animation") + inAnimation = true; + } + if (parent == null) throw new XamlIlParseException( "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node); @@ -43,7 +59,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, - new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]); + new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0], + // Hack to allow passing any property to an animation + inAnimation); property.Values = new List { avaloniaPropertyNode @@ -53,8 +71,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers .OfType().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value"); if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode) { - var propType = avaloniaPropertyNode.Property.Getter?.ReturnType - ?? avaloniaPropertyNode.Property.Setters.First().Parameters[0]; + var propType = avaloniaPropertyNode.AvaloniaPropertyType; if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0], propType, out var converted)) throw new XamlIlParseException( diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index b57c26c241..c054e57380 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -10,6 +10,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlIlType BindingPriority { get; } public IXamlIlType AvaloniaObjectExtensions { get; } public IXamlIlType AvaloniaProperty { get; } + public IXamlIlType AvaloniaPropertyT { get; } public IXamlIlType IBinding { get; } public IXamlIlMethod AvaloniaObjectBindMethod { get; } public IXamlIlMethod AvaloniaObjectSetValueMethod { get; } @@ -26,6 +27,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject"); AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions"); AvaloniaProperty = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"); + AvaloniaPropertyT = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty`1"); BindingPriority = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.BindingPriority"); IBinding = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.IBinding"); IDisposable = ctx.Configuration.TypeSystem.GetType("System.IDisposable"); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 34a79a4cdc..bf3f8958b3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -44,8 +44,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return true; } - public static XamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context, - string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo) + public static IXamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context, + string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo, + bool ignoreAttachedTargetType) { XamlIlAstNamePropertyReference forgedReference; @@ -63,6 +64,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions xmlOwner += parsedPropertyName.owner; var tref = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true); + + if (ignoreAttachedTargetType) + { + var propertyFieldName = parsedPropertyName.name + "Property"; + var found = tref.Type.GetAllFields() + .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName); + if (found == null) + throw new XamlIlParseException( + $"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo); + return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found); + } + forgedReference = new XamlIlAstNamePropertyReference(lineInfo, tref, parsedPropertyName.name, selectorTypeReference); } @@ -75,13 +88,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions clrProperty); } } + + interface IXamlIlAvaloniaPropertyNode : IXamlIlAstValueNode + { + IXamlIlType AvaloniaPropertyType { get; } + } - class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode + class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode { public XamlIlAvaloniaPropertyNode(IXamlIlLineInfo lineInfo, IXamlIlType type, XamlIlAstClrProperty property) : base(lineInfo) { Type = new XamlIlAstClrTypeReference(this, type, false); Property = property; + AvaloniaPropertyType = Property.Getter?.ReturnType + ?? Property.Setters.First().Parameters[0]; } public XamlIlAstClrProperty Property { get; } @@ -93,6 +113,46 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this); return XamlIlNodeEmitResult.Type(0, Type.GetClrType()); } + + public IXamlIlType AvaloniaPropertyType { get; } + } + + class XamlIlAvaloniaPropertyFieldNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode + { + private readonly IXamlIlField _field; + + public XamlIlAvaloniaPropertyFieldNode(AvaloniaXamlIlWellKnownTypes types, + IXamlIlLineInfo lineInfo, IXamlIlField field) : base(lineInfo) + { + _field = field; + var avaloniaPropertyType = field.FieldType; + while (avaloniaPropertyType != null) + { + if (avaloniaPropertyType.GenericTypeDefinition?.Equals(types.AvaloniaPropertyT) == true) + { + AvaloniaPropertyType = avaloniaPropertyType.GenericArguments[0]; + return; + } + + avaloniaPropertyType = avaloniaPropertyType.BaseType; + } + + throw new XamlIlParseException( + $"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty, make sure to use typed properties", + lineInfo); + + } + + + + public IXamlIlAstTypeReference Type => new XamlIlAstClrTypeReference(this, _field.FieldType, false); + public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) + { + codeGen.Ldsfld(_field); + return XamlIlNodeEmitResult.Type(0, _field.FieldType); + } + + public IXamlIlType AvaloniaPropertyType { get; } } interface IXamlIlAvaloniaProperty From 7a99e3118e8d2d027fe9d34197e09153b0c9fdaa Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 11 Jun 2019 21:27:57 +0300 Subject: [PATCH 129/130] Always ignore target type for fully-qualified avalonia properties --- .../AvaloniaXamlIlLanguage.cs | 2 +- .../AvaloniaXamlIlSetterTransformer.cs | 24 +++--------------- .../XamlIlAvaloniaPropertyHelper.cs | 25 +++++++------------ 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs index d5cc93ef66..581dbcdac7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs @@ -165,7 +165,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions throw new XamlIlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node); if (!(node is XamlIlAstTextNode text)) throw new XamlIlLoadException("Property should be a text node", node); - result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text, false); + result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text); return true; } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index d548d3a188..629e2562d3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -16,24 +16,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (!(node is XamlIlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Setter")) return node; - - // This is a hack required to get complex animations (which are also a hack) to work - var inAnimation = false; - - XamlIlAstObjectNode parent = null; - - foreach (var p in context.ParentNodes().OfType()) - { - if (p.Type.GetClrType().FullName == "Avalonia.Styling.Style") - { - parent = p; - break; - } - - if (p.Type.GetClrType().FullName == "Avalonia.Animation.Animation") - inAnimation = true; - } + var parent = context.ParentNodes().OfType() + .FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style"); + if (parent == null) throw new XamlIlParseException( "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node); @@ -59,9 +45,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, - new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0], - // Hack to allow passing any property to an animation - inAnimation); + new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]); property.Values = new List { avaloniaPropertyNode diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index bf3f8958b3..6fc83cb5a5 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -45,8 +45,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } public static IXamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context, - string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo, - bool ignoreAttachedTargetType) + string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo) { XamlIlAstNamePropertyReference forgedReference; @@ -64,20 +63,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions xmlOwner += parsedPropertyName.owner; var tref = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true); - - if (ignoreAttachedTargetType) - { - var propertyFieldName = parsedPropertyName.name + "Property"; - var found = tref.Type.GetAllFields() - .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName); - if (found == null) - throw new XamlIlParseException( - $"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo); - return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found); - } - - forgedReference = new XamlIlAstNamePropertyReference(lineInfo, - tref, parsedPropertyName.name, selectorTypeReference); + + var propertyFieldName = parsedPropertyName.name + "Property"; + var found = tref.Type.GetAllFields() + .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName); + if (found == null) + throw new XamlIlParseException( + $"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo); + return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found); } var clrProperty = From 57e061366ed353fef31f9b429d06b4f08f1196a0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 11 Jun 2019 22:00:01 +0300 Subject: [PATCH 130/130] Use AvaloniaProperty in test class --- tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index b2cc44291d..5398e76f63 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -301,7 +301,7 @@ namespace Avalonia.Markup.Xaml.UnitTests public static class XamlIlBugTestsStaticClassWithAttachedProperty { - public static readonly AvaloniaProperty TestIntProperty = AvaloniaProperty + public static readonly AvaloniaProperty TestIntProperty = AvaloniaProperty .RegisterAttached("TestInt", typeof(XamlIlBugTestsStaticClassWithAttachedProperty)); public static void SetTestInt(Control control, int value)