From f545fb2a4dff4e4eefc037aaa20d343ae8d5479e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Jul 2020 23:22:42 +0200 Subject: [PATCH 01/85] Ported IElementFactory from UWP/WinUI. --- .../Repeater/IElementFactory.cs | 66 +++++++++++++++++++ .../Repeater/ItemTemplateWrapper.cs | 20 +++++- .../Repeater/ItemsRepeater.cs | 4 +- src/Avalonia.Controls/Repeater/ViewManager.cs | 31 +++++++-- 4 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 src/Avalonia.Controls/Repeater/IElementFactory.cs diff --git a/src/Avalonia.Controls/Repeater/IElementFactory.cs b/src/Avalonia.Controls/Repeater/IElementFactory.cs new file mode 100644 index 0000000000..6a899a6f26 --- /dev/null +++ b/src/Avalonia.Controls/Repeater/IElementFactory.cs @@ -0,0 +1,66 @@ +using Avalonia.Controls.Templates; + +namespace Avalonia.Controls +{ + /// + /// Represents the optional arguments to use when calling an implementation of the + /// 's method. + /// + public class ElementFactoryGetArgs + { + /// + /// Gets or sets the data item for which an appropriate element tree should be realized + /// when calling . + /// + public object Data { get; set; } + + /// + /// Gets or sets the that is expected to be the parent of the + /// realized element from . + /// + public IControl Parent { get; set; } + + /// + /// Gets or sets the index of the item that should be realized. + /// + public int Index { get; set; } + } + + /// + /// Represents the optional arguments to use when calling an implementation of the + /// 's method. + /// + public class ElementFactoryRecycleArgs + { + /// + /// Gets or sets the to recycle when calling + /// . + /// + public IControl Element { get; set; } + + /// + /// Gets or sets the that is expected to be the parent of the + /// realized element from . + /// + public IControl Parent { get; set; } + } + + /// + /// A data template that supports creating and recyling elements for an . + /// + public interface IElementFactory : IDataTemplate + { + /// + /// Gets an . + /// + /// The element args. + public IControl GetElement(ElementFactoryGetArgs args); + + /// + /// Recycles an that was previously retrieved using + /// . + /// + /// The recycle args. + public void RecycleElement(ElementFactoryRecycleArgs args); + } +} diff --git a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs index 04d859c742..4b784375a9 100644 --- a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs +++ b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs @@ -7,13 +7,27 @@ using Avalonia.Controls.Templates; namespace Avalonia.Controls { - internal class ItemTemplateWrapper + internal class ItemTemplateWrapper : IElementFactory { private readonly IDataTemplate _dataTemplate; public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate; - public IControl GetElement(IControl parent, object data) + public bool SupportsRecycling => false; + public IControl Build(object param) => GetElement(null, param); + public bool Match(object data) => _dataTemplate.Match(data); + + public IControl GetElement(ElementFactoryGetArgs args) + { + return GetElement(args.Parent, args.Data); + } + + public void RecycleElement(ElementFactoryRecycleArgs args) + { + RecycleElement(args.Parent, args.Element); + } + + private IControl GetElement(IControl parent, object data) { var selectedTemplate = _dataTemplate; var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); @@ -37,7 +51,7 @@ namespace Avalonia.Controls return element; } - public void RecycleElement(IControl parent, IControl element) + private void RecycleElement(IControl parent, IControl element) { var selectedTemplate = _dataTemplate; var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 87f4760156..8bc356bdec 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -141,7 +141,7 @@ namespace Avalonia.Controls /// public ItemsSourceView ItemsSourceView { get; private set; } - internal ItemTemplateWrapper ItemTemplateShim { get; set; } + internal IElementFactory ItemTemplateShim { get; set; } internal Point LayoutOrigin { get; set; } internal object LayoutState { get; set; } internal IControl MadeAnchor => _viewportManager.MadeAnchor; @@ -664,7 +664,7 @@ namespace Avalonia.Controls } } - ItemTemplateShim = new ItemTemplateWrapper(newValue); + ItemTemplateShim = newValue as IElementFactory ?? new ItemTemplateWrapper(newValue); InvalidateMeasure(); } diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs index eff51804b9..416b1e2824 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewManager.cs @@ -6,11 +6,9 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Layout; using Avalonia.Logging; using Avalonia.VisualTree; @@ -26,6 +24,8 @@ namespace Avalonia.Controls private readonly UniqueIdElementPool _resetPool; private IControl _lastFocusedElement; private bool _isDataSourceStableResetPending; + private ElementFactoryGetArgs _elementFactoryGetArgs; + private ElementFactoryRecycleArgs _elementFactoryRecycleArgs; private int _firstRealizedElementIndexHeldByLayout = FirstRealizedElementIndexDefault; private int _lastRealizedElementIndexHeldByLayout = LastRealizedElementIndexDefault; private bool _eventsSubscribed; @@ -134,7 +134,14 @@ namespace Avalonia.Controls if (_owner.ItemTemplateShim != null) { - _owner.ItemTemplateShim.RecycleElement(_owner, element); + var context = _elementFactoryRecycleArgs ??= new ElementFactoryRecycleArgs(); + context.Element = element; + context.Parent = _owner; + + _owner.ItemTemplateShim.RecycleElement(context); + + context.Element = null; + context.Parent = null; } else { @@ -579,7 +586,7 @@ namespace Avalonia.Controls var data = _owner.ItemsSourceView.GetAt(index); var providedElementFactory = _owner.ItemTemplateShim; - ItemTemplateWrapper GetElementFactory() + IElementFactory GetElementFactory() { if (providedElementFactory == null) { @@ -602,7 +609,20 @@ namespace Avalonia.Controls } var elementFactory = GetElementFactory(); - return elementFactory.GetElement(_owner, data); + var args = _elementFactoryGetArgs ??= new ElementFactoryGetArgs(); + + try + { + args.Data = data; + args.Parent = _owner; + args.Index = index; + return elementFactory.GetElement(args); + } + finally + { + args.Data = null; + args.Parent = null; + } } var element = GetElement(); @@ -732,6 +752,7 @@ namespace Avalonia.Controls { _owner.GotFocus += OnFocusChanged; _owner.LostFocus += OnFocusChanged; + _eventsSubscribed = true; } } From 87d1964e9fc4a62ce4acad8c5b3f6a88562546bf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jul 2020 09:42:47 +0200 Subject: [PATCH 02/85] Ported RecyclingElementFactory from WinUI. --- .../Repeater/ElementFactory.cs | 29 +++++ src/Avalonia.Controls/Repeater/RecyclePool.cs | 12 +- .../Repeater/RecyclingElementFactory.cs | 119 ++++++++++++++++++ 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 src/Avalonia.Controls/Repeater/ElementFactory.cs create mode 100644 src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs diff --git a/src/Avalonia.Controls/Repeater/ElementFactory.cs b/src/Avalonia.Controls/Repeater/ElementFactory.cs new file mode 100644 index 0000000000..1c1b71af88 --- /dev/null +++ b/src/Avalonia.Controls/Repeater/ElementFactory.cs @@ -0,0 +1,29 @@ +using Avalonia.Controls.Templates; + +namespace Avalonia.Controls +{ + public abstract class ElementFactory : IElementFactory + { + bool IDataTemplate.SupportsRecycling => false; + + public IControl Build(object data) + { + return GetElementCore(new ElementFactoryGetArgs { Data = data }); + } + + public IControl GetElement(ElementFactoryGetArgs args) + { + return GetElementCore(args); + } + + public bool Match(object data) => true; + + public void RecycleElement(ElementFactoryRecycleArgs args) + { + RecycleElementCore(args); + } + + protected abstract IControl GetElementCore(ElementFactoryGetArgs args); + protected abstract void RecycleElementCore(ElementFactoryRecycleArgs args); + } +} diff --git a/src/Avalonia.Controls/Repeater/RecyclePool.cs b/src/Avalonia.Controls/Repeater/RecyclePool.cs index 4e5950bdc5..28f299043c 100644 --- a/src/Avalonia.Controls/Repeater/RecyclePool.cs +++ b/src/Avalonia.Controls/Repeater/RecyclePool.cs @@ -11,10 +11,13 @@ using Avalonia.Controls.Templates; namespace Avalonia.Controls { - internal class RecyclePool + public class RecyclePool { - public static readonly AttachedProperty OriginTemplateProperty = - AvaloniaProperty.RegisterAttached("OriginTemplate", typeof(RecyclePool)); + internal static readonly AttachedProperty OriginTemplateProperty = + AvaloniaProperty.RegisterAttached("OriginTemplate"); + + internal static readonly AttachedProperty ReuseKeyProperty = + AvaloniaProperty.RegisterAttached("ReuseKey", string.Empty); private static ConditionalWeakTable s_pools = new ConditionalWeakTable(); private readonly Dictionary> _elements = new Dictionary>(); @@ -77,6 +80,9 @@ namespace Avalonia.Controls return null; } + internal string GetReuseKey(IControl element) => element.GetValue(ReuseKeyProperty); + internal void SetReuseKey(IControl element, string value) => element.SetValue(ReuseKeyProperty, value); + private IPanel EnsureOwnerIsPanelOrNull(IControl owner) { if (owner is IPanel panel) diff --git a/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs new file mode 100644 index 0000000000..9503239e34 --- /dev/null +++ b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls.Templates; + +#nullable enable + +namespace Avalonia.Controls +{ + public class SelectTemplateEventArgs : EventArgs + { + public string? TemplateKey { get; set; } + public object? DataContext { get; internal set; } + public IControl? Owner { get; internal set; } + } + + public class RecyclingElementFactory : ElementFactory + { + private RecyclePool? _recyclePool; + private IDictionary? _templates; + private SelectTemplateEventArgs? _args; + + public RecyclingElementFactory() + { + Templates = new Dictionary(); + } + + public RecyclePool RecyclePool + { + get => _recyclePool ??= new RecyclePool(); + set => _recyclePool = value ?? throw new ArgumentNullException(nameof(value)); + } + + public IDictionary Templates + { + get => _templates ??= new Dictionary(); + set => _templates = value ?? throw new ArgumentNullException(nameof(value)); + } + + public event EventHandler? SelectTemplateKey; + + protected override IControl GetElementCore(ElementFactoryGetArgs args) + { + if (_templates == null || _templates.Count == 0) + { + throw new InvalidOperationException("Templates cannot be empty."); + } + + var templateKey = Templates.Count == 1 ? + Templates.First().Key : + OnSelectTemplateKeyCore(args.Data, args.Parent); + + if (string.IsNullOrEmpty(templateKey)) + { + // Note: We could allow null/whitespace, which would work as long as + // the recycle pool is not shared. in order to make this work in all cases + // currently we validate that a valid template key is provided. + throw new InvalidOperationException("Template key cannot be null or empty."); + } + + // Get an element from the Recycle Pool or create one + var element = RecyclePool.TryGetElement(templateKey, args.Parent); + + if (element is null) + { + // No need to call HasKey if there is only one template. + if (Templates.Count > 1 && !Templates.ContainsKey(templateKey)) + { + var message = $"No templates of key '{templateKey}' were found in the templates collection."; + throw new InvalidOperationException(message); + } + + var dataTemplate = Templates[templateKey]; + element = dataTemplate.Build(args.Data); + + // Associate ReuseKey with element + RecyclePool.SetReuseKey(element, templateKey); + } + + return element; + } + + protected override void RecycleElementCore(ElementFactoryRecycleArgs args) + { + var element = args.Element; + var key = RecyclePool.GetReuseKey(element); + RecyclePool.PutElement(element, key, args.Parent); + } + + protected virtual string OnSelectTemplateKeyCore(object dataContext, IControl owner) + { + if (SelectTemplateKey is object) + { + _args ??= new SelectTemplateEventArgs(); + _args.TemplateKey = null; + _args.DataContext = dataContext; + _args.Owner = owner; + + try + { + SelectTemplateKey(this, _args); + } + finally + { + _args.DataContext = null; + _args.Owner = null; + } + } + + if (string.IsNullOrEmpty(_args?.TemplateKey)) + { + throw new InvalidOperationException( + "Please provide a valid template identifier in the handler for the SelectTemplateKey event."); + } + + return _args!.TemplateKey!; + } + } +} From 4fbc43e785e1609be3d2975568597e3d022b952b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jul 2020 09:58:42 +0200 Subject: [PATCH 03/85] Use RecyclingElementFactory in ItemsRepeaterPage. --- .../Pages/ItemsRepeaterPage.xaml | 36 +++++++++++++------ .../Pages/ItemsRepeaterPage.xaml.cs | 6 ++++ .../ViewModels/ItemsRepeaterPageViewModel.cs | 12 ++----- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml index e600e644af..304782dbf9 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml @@ -1,6 +1,30 @@ + + + + + + + + + + + + + + + + ItemsRepeater @@ -23,16 +47,8 @@ - - - - - - - + diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs index cce80a2d3c..82c44508b8 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs @@ -38,6 +38,12 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } + public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e) + { + var item = (ItemsRepeaterPageViewModel.Item)e.DataContext; + e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd"; + } + private void LayoutChanged(object sender, SelectionChangedEventArgs e) { if (_repeater == null) diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs index 73aaeff994..b859862f1f 100644 --- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs @@ -55,20 +55,16 @@ namespace ControlCatalog.ViewModels return new ObservableCollection( Enumerable.Range(1, 100000).Select(i => new Item(i) { - Text = $"Item {i.ToString()} {suffix}" + Text = $"Item {i} {suffix}" })); } public class Item : ReactiveObject { private double _height = double.NaN; - private int _index; - - public Item(int index) - { - _index = index; - } + public Item(int index) => Index = index; + public int Index { get; } public string Text { get; set; } public double Height @@ -76,8 +72,6 @@ namespace ControlCatalog.ViewModels get => _height; set => this.RaiseAndSetIfChanged(ref _height, value); } - - public IBrush Background => ((_index % 2) == 0) ? Brushes.Yellow : Brushes.Wheat; } } } From 547d3228ea248a253d0e50e2a3d891980990fe7a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Jul 2020 16:08:15 +0200 Subject: [PATCH 04/85] Undo perf regression. --- samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs index b859862f1f..f893a6e28e 100644 --- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs @@ -55,7 +55,7 @@ namespace ControlCatalog.ViewModels return new ObservableCollection( Enumerable.Range(1, 100000).Select(i => new Item(i) { - Text = $"Item {i} {suffix}" + Text = $"Item {i.ToString()} {suffix}" })); } From edcced230ccef6ebdecc44050d5fe27289e80259 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 7 Jul 2020 21:29:53 +0300 Subject: [PATCH 05/85] Use custom WaitForMultipleObjectsEx to prevent STA/COM fuckery --- .../AvaloniaSynchronizationContext.cs | 23 ++++++++++++++++++- src/Avalonia.FreeDesktop/DBusHelper.cs | 2 +- .../Interop/UnmanagedMethods.cs | 17 ++++++++++++++ .../Avalonia.Win32/NonPumpingWaitProvider.cs | 15 ++++++++++++ src/Windows/Avalonia.Win32/Win32Platform.cs | 1 + 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs index 38a23f918f..166832398f 100644 --- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs +++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; namespace Avalonia.Threading @@ -7,6 +8,18 @@ namespace Avalonia.Threading /// public class AvaloniaSynchronizationContext : SynchronizationContext { + public interface INonPumpingPlatformWaitProvider + { + int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout); + } + + private readonly INonPumpingPlatformWaitProvider _waitProvider; + + public AvaloniaSynchronizationContext(INonPumpingPlatformWaitProvider waitProvider) + { + _waitProvider = waitProvider; + } + /// /// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer. /// @@ -22,7 +35,8 @@ namespace Avalonia.Threading return; } - SetSynchronizationContext(new AvaloniaSynchronizationContext()); + SetSynchronizationContext(new AvaloniaSynchronizationContext(AvaloniaLocator.Current + .GetService())); } /// @@ -39,5 +53,12 @@ namespace Avalonia.Threading else Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait(); } + + public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) + { + if (_waitProvider != null) + return _waitProvider.Wait(waitHandles, waitAll, millisecondsTimeout); + return base.Wait(waitHandles, waitAll, millisecondsTimeout); + } } } diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index b445f86613..91c4c28995 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -43,7 +43,7 @@ namespace Avalonia.FreeDesktop public void Initialized() { lock (_lock) - _ctx = new AvaloniaSynchronizationContext(); + _ctx = new AvaloniaSynchronizationContext(null); } } public static Connection Connection { get; private set; } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 1aec4f0016..392ca31282 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -1382,6 +1383,22 @@ namespace Avalonia.Win32.Interop throw new Exception("RtlGetVersion failed!"); } } + + [DllImport("kernel32", EntryPoint="WaitForMultipleObjectsEx", SetLastError = true, CharSet = CharSet.Auto)] + private static extern int IntWaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable); + + public const int WAIT_FAILED = unchecked((int)0xFFFFFFFF); + + internal static int WaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable) + { + int result = IntWaitForMultipleObjectsEx(nCount, pHandles, bWaitAll, dwMilliseconds, bAlertable); + if(result == WAIT_FAILED) + { + throw new Win32Exception(); + } + + return result; + } [DllImport("user32.dll")] internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); diff --git a/src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs b/src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs new file mode 100644 index 0000000000..a0160fcfbd --- /dev/null +++ b/src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs @@ -0,0 +1,15 @@ +using System; +using Avalonia.Threading; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + internal class NonPumpingWaitProvider : AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider + { + public int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) + { + return UnmanagedMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll, + millisecondsTimeout, false); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index b7bb0e19ba..af6058d197 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -93,6 +93,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToSingleton() .Bind().ToConstant(s_instance) + .Bind().ToConstant(new NonPumpingWaitProvider()) .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()); if (options.AllowEglInitialization) From 09a58d0c146a45a14ebf7acfa4e0bf7e4699b91a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 9 Jul 2020 12:58:39 -0300 Subject: [PATCH 06/85] realtive panel no longer cares about the declaration order. and measures to the largest child when not stretched. --- src/Avalonia.Controls/RelativePanel.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 033a5559f5..07137e3d1a 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -16,12 +16,16 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - foreach (var child in Children) + var maxSize = new Size(); + + foreach (var child in Children.OfType()) { - child?.Measure(availableSize); + child.Measure(availableSize); + maxSize = maxSize.WithWidth(Math.Max(maxSize.Width, child.DesiredSize.Width)); + maxSize = maxSize.WithHeight(Math.Max(maxSize.Height, child.DesiredSize.Height)); } - return availableSize; + return maxSize; } protected override Size ArrangeOverride(Size arrangeSize) @@ -183,11 +187,14 @@ namespace Avalonia.Controls _nodeDic.Clear(); } - public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null); + public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null, null); - private bool CheckCyclic(IEnumerable nodes, HashSet? set) + private bool CheckCyclic(IEnumerable nodes, GraphNode? waitNode, HashSet? set) { - set ??= new HashSet(); + if (set == null) + { + set = new HashSet(); + } foreach (var node in nodes) { @@ -206,9 +213,13 @@ namespace Avalonia.Controls if (!set.Add(node.Element)) return true; - return CheckCyclic(node.OutgoingNodes, set); + return CheckCyclic(node.OutgoingNodes, node.Arranged ? null : node, set); } + if (waitNode != null) + { + ArrangeChild(waitNode); + } return false; } From 9309d779c74b2ea8747c4b719be395e91e0f201f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 10 Jul 2020 16:00:17 +0300 Subject: [PATCH 07/85] Call SetWaitNotificationRequired --- src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs index 166832398f..f920fec82e 100644 --- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs +++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs @@ -18,6 +18,8 @@ namespace Avalonia.Threading public AvaloniaSynchronizationContext(INonPumpingPlatformWaitProvider waitProvider) { _waitProvider = waitProvider; + if (_waitProvider != null) + SetWaitNotificationRequired(); } /// From efd5bbeaa9a7104c15cab284288b4730c3b3ea56 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 10 Jul 2020 16:21:53 +0300 Subject: [PATCH 08/85] Added an incantation nobody probably even remembers about anymore --- src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs index f920fec82e..40cf81358f 100644 --- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs +++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.ConstrainedExecution; using System.Threading; namespace Avalonia.Threading @@ -56,6 +57,7 @@ namespace Avalonia.Threading Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait(); } + [PrePrepareMethod] public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) { if (_waitProvider != null) From a8b7e879387a1d83cd724877195c27ac3e320be3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 12 Jul 2020 15:22:00 +0200 Subject: [PATCH 09/85] Added IRecyclingDataTemplate. In #4218 we imported `IElementFactory` from WinUI which is broadly analogous to a recycling datatemplate for lists. In Avalonia this implement `IDataTemplate` in order to have a common base class for all types of data templates. The problem with this is that `IDataTemplate` already had a `SupportsRecycling` property which is incompatible with the way recycling is implemented in `IElementFactory`. Instead, introduce an `IRecyclingDataTemplate` to signal data templates that support recycling. --- .../Generators/TreeItemContainerGenerator.cs | 1 - .../Presenters/ContentPresenter.cs | 21 +++++++------- .../Repeater/ElementFactory.cs | 2 -- .../Repeater/ItemTemplateWrapper.cs | 1 - .../Templates/FuncDataTemplate.cs | 29 ++++++++++++++----- .../Templates/FuncTemplate`2.cs | 6 ++-- .../Templates/IDataTemplate.cs | 12 ++++---- .../Templates/IRecyclingDataTemplate.cs | 25 ++++++++++++++++ .../Diagnostics/ViewLocator.cs | 2 -- .../Templates/DataTemplate.cs | 11 ++++--- .../Templates/TreeDataTemplate.cs | 2 -- .../TreeViewTests.cs | 2 -- 12 files changed, 71 insertions(+), 43 deletions(-) create mode 100644 src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index cd1ce3deae..9e65ef5f81 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -142,7 +142,6 @@ namespace Avalonia.Controls.Generators private readonly IDataTemplate _inner; public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner; public IControl Build(object param) => _inner.Build(param); - public bool SupportsRecycling => _inner.SupportsRecycling; public bool Match(object data) => _inner.Match(data); public InstancedBinding ItemsSelector(object item) => null; } diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index c4571505ba..8837901816 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -86,7 +86,7 @@ namespace Avalonia.Controls.Presenters private IControl _child; private bool _createdChild; - private IDataTemplate _dataTemplate; + private IRecyclingDataTemplate _recyclingDataTemplate; private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper(); /// @@ -281,7 +281,7 @@ namespace Avalonia.Controls.Presenters protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); - _dataTemplate = null; + _recyclingDataTemplate = null; _createdChild = false; InvalidateMeasure(); } @@ -307,22 +307,21 @@ namespace Avalonia.Controls.Presenters { var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default; - // We have content and it isn't a control, so if the new data template is the same - // as the old data template, try to recycle the existing child control to display - // the new data. - if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling) + if (dataTemplate is IRecyclingDataTemplate rdt) { - newChild = oldChild; + var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null; + newChild = rdt.Build(content, toRecycle); + _recyclingDataTemplate = rdt; } else { - _dataTemplate = dataTemplate; - newChild = _dataTemplate.Build(content); + newChild = dataTemplate.Build(content); + _recyclingDataTemplate = null; } } else { - _dataTemplate = null; + _recyclingDataTemplate = null; } return newChild; @@ -422,7 +421,7 @@ namespace Avalonia.Controls.Presenters LogicalChildren.Remove(Child); ((ISetInheritanceParent)Child).SetParent(Child.Parent); Child = null; - _dataTemplate = null; + _recyclingDataTemplate = null; } InvalidateMeasure(); diff --git a/src/Avalonia.Controls/Repeater/ElementFactory.cs b/src/Avalonia.Controls/Repeater/ElementFactory.cs index 1c1b71af88..644e077221 100644 --- a/src/Avalonia.Controls/Repeater/ElementFactory.cs +++ b/src/Avalonia.Controls/Repeater/ElementFactory.cs @@ -4,8 +4,6 @@ namespace Avalonia.Controls { public abstract class ElementFactory : IElementFactory { - bool IDataTemplate.SupportsRecycling => false; - public IControl Build(object data) { return GetElementCore(new ElementFactoryGetArgs { Data = data }); diff --git a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs index 4b784375a9..dd97cde218 100644 --- a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs +++ b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs @@ -13,7 +13,6 @@ namespace Avalonia.Controls public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate; - public bool SupportsRecycling => false; public IControl Build(object param) => GetElement(null, param); public bool Match(object data) => _dataTemplate.Match(data); diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs index d454a29021..1afd86a11e 100644 --- a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs @@ -6,7 +6,7 @@ namespace Avalonia.Controls.Templates /// /// Builds a control for a piece of data. /// - public class FuncDataTemplate : FuncTemplate, IDataTemplate + public class FuncDataTemplate : FuncTemplate, IRecyclingDataTemplate { /// /// The default data template used in the case where no matching data template is found. @@ -30,10 +30,8 @@ namespace Avalonia.Controls.Templates }, true); - /// - /// The implementation of the method. - /// private readonly Func _match; + private readonly bool _supportsRecycling; /// /// Initializes a new instance of the class. @@ -70,12 +68,9 @@ namespace Avalonia.Controls.Templates Contract.Requires(match != null); _match = match; - SupportsRecycling = supportsRecycling; + _supportsRecycling = supportsRecycling; } - /// - public bool SupportsRecycling { get; } - /// /// Checks to see if this data template matches the specified data. /// @@ -88,6 +83,24 @@ namespace Avalonia.Controls.Templates return _match(data); } + /// + /// Creates or recycles a control to display the specified data. + /// + /// The data to display. + /// An optional control to recycle. + /// + /// The control if supplied and applicable to + /// , otherwise a new control. + /// + /// + /// The caller should ensure that any control passed to + /// originated from the same data template. + /// + public IControl Build(object data, IControl existing) + { + return _supportsRecycling && existing is object ? existing : Build(data); + } + /// /// Determines of an object is of the specified type. /// diff --git a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs index d08616b968..cd0e3ad603 100644 --- a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs +++ b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Avalonia.Controls.Templates { /// @@ -18,9 +20,7 @@ namespace Avalonia.Controls.Templates /// The function used to create the control. public FuncTemplate(Func func) { - Contract.Requires(func != null); - - _func = func; + _func = func ?? throw new ArgumentNullException(nameof(func)); } /// diff --git a/src/Avalonia.Controls/Templates/IDataTemplate.cs b/src/Avalonia.Controls/Templates/IDataTemplate.cs index cfde029eb8..0368748a0b 100644 --- a/src/Avalonia.Controls/Templates/IDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/IDataTemplate.cs @@ -1,3 +1,7 @@ +using System; + +#nullable enable + namespace Avalonia.Controls.Templates { /// @@ -5,12 +9,6 @@ namespace Avalonia.Controls.Templates /// public interface IDataTemplate : ITemplate { - /// - /// Gets a value indicating whether the data template supports recycling of the generated - /// control. - /// - bool SupportsRecycling { get; } - /// /// Checks to see if this data template matches the specified data. /// @@ -20,4 +18,4 @@ namespace Avalonia.Controls.Templates /// bool Match(object data); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs b/src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs new file mode 100644 index 0000000000..25956a9c9a --- /dev/null +++ b/src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs @@ -0,0 +1,25 @@ +#nullable enable + +namespace Avalonia.Controls.Templates +{ + /// + /// An that supports recycling existing elements. + /// + public interface IRecyclingDataTemplate : IDataTemplate + { + /// + /// Creates or recycles a control to display the specified data. + /// + /// The data to display. + /// An optional control to recycle. + /// + /// The control if supplied and applicable to + /// , otherwise a new control. + /// + /// + /// The caller should ensure that any control passed to + /// originated from the same data template. + /// + IControl Build(object data, IControl? existing); + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs index c06fbec801..be3564e781 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs @@ -7,8 +7,6 @@ namespace Avalonia.Diagnostics { internal class ViewLocator : IDataTemplate { - public bool SupportsRecycling => false; - public IControl Build(object data) { var name = data.GetType().FullName.Replace("ViewModel", "View"); diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs index 5663d08412..07c5451135 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs @@ -5,7 +5,7 @@ using Avalonia.Metadata; namespace Avalonia.Markup.Xaml.Templates { - public class DataTemplate : IDataTemplate + public class DataTemplate : IRecyclingDataTemplate { public Type DataType { get; set; } @@ -14,8 +14,6 @@ namespace Avalonia.Markup.Xaml.Templates [TemplateContent] public object Content { get; set; } - public bool SupportsRecycling { get; set; } = true; - public bool Match(object data) { if (DataType == null) @@ -28,6 +26,11 @@ namespace Avalonia.Markup.Xaml.Templates } } - public IControl Build(object data) => TemplateContent.Load(Content).Control; + public IControl Build(object data) => Build(data, null); + + public IControl Build(object data, IControl existing) + { + return existing ?? TemplateContent.Load(Content).Control; + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs index b96486235a..b8e1c2df80 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -18,8 +18,6 @@ namespace Avalonia.Markup.Xaml.Templates [AssignBinding] public Binding ItemsSource { get; set; } - public bool SupportsRecycling { get; set; } = true; - public bool Match(object data) { if (DataType == null) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index c1bd45bcad..c25ad19027 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1273,8 +1273,6 @@ namespace Avalonia.Controls.UnitTests return new TextBlock { Text = node.Value }; } - public bool SupportsRecycling => false; - public InstancedBinding ItemsSelector(object item) { var obs = ExpressionObserver.Create(item, o => (o as Node).Children); From 8176ef66481256e11dead37e0163e590e0971121 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 19:11:19 +0200 Subject: [PATCH 10/85] Added failing test. Items are being materialized twice when not using virtualization. --- .../Presenters/ItemsPresenterTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs index fddc02f19c..fab57cec49 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs @@ -60,6 +60,25 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.IsType(target.Panel.Children[1]); } + [Fact] + public void Should_Create_Containers_Only_Once() + { + var parent = new TestItemsControl(); + var target = new ItemsPresenter + { + Items = new[] { "foo", "bar" }, + [StyledElement.TemplatedParentProperty] = parent, + }; + var raised = 0; + + parent.ItemContainerGenerator.Materialized += (s, e) => ++raised; + + target.ApplyTemplate(); + + Assert.Equal(2, target.Panel.Children.Count); + Assert.Equal(2, raised); + } + [Fact] public void ItemContainerGenerator_Should_Be_Picked_Up_From_TemplatedControl() { From 68792668ada4ad6cc2ecb10ada5fc6451d0779ac Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 19:23:00 +0200 Subject: [PATCH 11/85] Make items be materialized only once. When virtualization was turned off in an `ItemsControl`, items were virtualized twice. on control creation. Fix that. --- src/Avalonia.Controls/Presenters/CarouselPresenter.cs | 5 +++++ src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index 70a7583daf..7888249bdd 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -155,6 +155,11 @@ namespace Avalonia.Controls.Presenters } } + protected override void PanelCreated(IPanel panel) + { + ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + /// /// Moves to the selected page, animating if a is set. /// diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index 23846bcd2e..52f173fc71 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -229,8 +229,6 @@ namespace Avalonia.Controls.Presenters } PanelCreated(Panel); - - ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// From fa72f9f209ea9cf2474e30a7c8f7943e63f403bd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 19:25:38 +0200 Subject: [PATCH 12/85] Make ncrunch work again. --- Avalonia.v3.ncrunchsolution | 1 + 1 file changed, 1 insertion(+) diff --git a/Avalonia.v3.ncrunchsolution b/Avalonia.v3.ncrunchsolution index a2208a9a91..bef7e45524 100644 --- a/Avalonia.v3.ncrunchsolution +++ b/Avalonia.v3.ncrunchsolution @@ -3,6 +3,7 @@ tests\TestFiles\**.* src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll + src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Mono.Cecil.dll True .ncrunch From fca777bf91794d253165c07d554698a5e6629854 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 13 Jul 2020 18:21:45 -0300 Subject: [PATCH 13/85] fixes for relativepanel --- src/Avalonia.Controls/RelativePanel.cs | 541 +++++++++++++++++-------- 1 file changed, 374 insertions(+), 167 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 07137e3d1a..a5743f4153 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -8,35 +8,61 @@ using Avalonia.Layout; namespace Avalonia.Controls { + public static partial class Extensions + { + /// + /// Returns a value that indicates whether the specified value is not a number (). + /// + /// A double-precision floating-point number. + /// true if evaluates to ; otherwise, false. + public static bool IsNaN(this double d) + { + return double.IsNaN(d); + } + + public static IEnumerable Do(this IEnumerable source, Action predicate) + { + var enumerable = source as IList ?? source.ToList(); + foreach (var item in enumerable) + { + predicate.Invoke(item); + } + + return enumerable; + } + } + public partial class RelativePanel : Panel { private readonly Graph _childGraph; public RelativePanel() => _childGraph = new Graph(); + - protected override Size MeasureOverride(Size availableSize) + private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { - var maxSize = new Size(); + var dependency = child.GetValue(property); - foreach (var child in Children.OfType()) + if (dependency is Layoutable layoutable) { - child.Measure(availableSize); - maxSize = maxSize.WithWidth(Math.Max(maxSize.Width, child.DesiredSize.Width)); - maxSize = maxSize.WithHeight(Math.Max(maxSize.Height, child.DesiredSize.Height)); + if (Children.Contains((ILayoutable)layoutable)) + return layoutable; + + throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}"); } - return maxSize; + return null; } - protected override Size ArrangeOverride(Size arrangeSize) + protected override Size MeasureOverride(Size availableSize) { - _childGraph.Reset(arrangeSize); + #region Calc DesiredSize - foreach (var child in Children.OfType()) + _childGraph.Clear(); + foreach (Layoutable child in Children) { if (child == null) continue; - var node = _childGraph.AddNode(child); node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child)); @@ -50,106 +76,175 @@ namespace Avalonia.Controls node.BelowNode = _childGraph.AddLink(node, GetDependencyElement(BelowProperty, child)); node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child)); - node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); - } + node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); - if (_childGraph.CheckCyclic()) - { - throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); } + _childGraph.Measure(availableSize); - var size = new Size(); + #endregion - foreach (var child in Children) - { - if (child.Bounds.Bottom > size.Height) - { - size = size.WithHeight(child.Bounds.Bottom); - } + #region Calc AvailableSize - if (child.Bounds.Right > size.Width) - { - size = size.WithWidth(child.Bounds.Right); - } - } + _childGraph.Reset(); + var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN()); + _childGraph.Reset(); + _childGraph.Measure(boundingSize); + return boundingSize; - if (VerticalAlignment == VerticalAlignment.Stretch) - { - size = size.WithHeight(arrangeSize.Height); - } - - if (HorizontalAlignment == HorizontalAlignment.Stretch) - { - size = size.WithWidth(arrangeSize.Width); - } + #endregion + } - return size; + protected override Size ArrangeOverride(Size arrangeSize) + { + _childGraph.GetNodes().Do(node => node.Arrange(arrangeSize)); + return arrangeSize; } - private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) + private class GraphNode { - var dependency = child.GetValue(property); + public bool Measured { get; set; } - if (dependency is Layoutable layoutable) - { - if (Children.Contains((ILayoutable)layoutable)) - return layoutable; + public Layoutable Element { get; } - throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}"); - } + private bool HorizontalOffsetFlag { get; set; } - return null; - } + private bool VerticalOffsetFlag { get; set; } - private class GraphNode - { - public Point Position { get; set; } + private Size BoundingSize { get; set; } - public bool Arranged { get; set; } + public Size OriginDesiredSize { get; set; } - public Layoutable Element { get; } + public double Left { get; set; } = double.NaN; + + public double Top { get; set; } = double.NaN; + + public double Right { get; set; } = double.NaN; + + public double Bottom { get; set; } = double.NaN; public HashSet OutgoingNodes { get; } - public GraphNode? AlignLeftWithNode { get; set; } + public GraphNode AlignLeftWithNode { get; set; } - public GraphNode? AlignTopWithNode { get; set; } + public GraphNode AlignTopWithNode { get; set; } - public GraphNode? AlignRightWithNode { get; set; } + public GraphNode AlignRightWithNode { get; set; } - public GraphNode? AlignBottomWithNode { get; set; } + public GraphNode AlignBottomWithNode { get; set; } - public GraphNode? LeftOfNode { get; set; } + public GraphNode LeftOfNode { get; set; } - public GraphNode? AboveNode { get; set; } + public GraphNode AboveNode { get; set; } - public GraphNode? RightOfNode { get; set; } + public GraphNode RightOfNode { get; set; } - public GraphNode? BelowNode { get; set; } + public GraphNode BelowNode { get; set; } - public GraphNode? AlignHorizontalCenterWith { get; set; } + public GraphNode AlignHorizontalCenterWith { get; set; } - public GraphNode? AlignVerticalCenterWith { get; set; } + public GraphNode AlignVerticalCenterWith { get; set; } public GraphNode(Layoutable element) { OutgoingNodes = new HashSet(); Element = element; } + + public void Arrange(Size arrangeSize) => Element.Arrange(new Rect(Left, Top, Math.Max(arrangeSize.Width - Left - Right, 0), Math.Max(arrangeSize.Height - Top - Bottom, 0))); + + public void Reset() + { + Left = double.NaN; + Top = double.NaN; + Right = double.NaN; + Bottom = double.NaN; + Measured = false; + } + + public Size GetBoundingSize() + { + if (Measured) + return BoundingSize; + + if (!OutgoingNodes.Any()) + { + BoundingSize = Element.DesiredSize; + Measured = true; + } + else + { + BoundingSize = GetBoundingSize(this, Element.DesiredSize, OutgoingNodes); + Measured = true; + } + + return BoundingSize; + } + + private static Size GetBoundingSize(GraphNode prevNode, Size prevSize, IEnumerable nodes) + { + foreach (var node in nodes) + { + if (node.Measured || !node.OutgoingNodes.Any()) + { + if (prevNode.LeftOfNode != null && prevNode.LeftOfNode == node || + prevNode.RightOfNode != null && prevNode.RightOfNode == node) + { + prevSize = prevSize.WithWidth(prevSize.Width + node.BoundingSize.Width); + if (GetAlignHorizontalCenterWithPanel(node.Element) || node.HorizontalOffsetFlag) + { + prevSize = prevSize.WithWidth(prevSize.Width + prevNode.OriginDesiredSize.Width); + prevNode.HorizontalOffsetFlag = true; + } + if (node.VerticalOffsetFlag) + { + prevNode.VerticalOffsetFlag = true; + } + } + + if (prevNode.AboveNode != null && prevNode.AboveNode == node || + prevNode.BelowNode != null && prevNode.BelowNode == node) + { + prevSize = prevSize.WithHeight(prevSize.Height + node.BoundingSize.Height); + if (GetAlignVerticalCenterWithPanel(node.Element) || node.VerticalOffsetFlag) + { + prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height); + prevNode.VerticalOffsetFlag = true; + } + if (node.HorizontalOffsetFlag) + { + prevNode.HorizontalOffsetFlag = true; + } + } + } + else + { + return GetBoundingSize(node, prevSize, node.OutgoingNodes); + } + } + + return prevSize; + } } private class Graph { private readonly Dictionary _nodeDic; - private Size _arrangeSize; + private Size AvailableSize { get; set; } - public Graph() + public Graph() => _nodeDic = new Dictionary(); + + public IEnumerable GetNodes() => _nodeDic.Values; + + public void Clear() { - _nodeDic = new Dictionary(); + AvailableSize = new Size(); + _nodeDic.Clear(); } - public GraphNode? AddLink(GraphNode from, Layoutable? to) + public void Reset() => _nodeDic.Values.Do(node => node.Reset()); + + public GraphNode AddLink(GraphNode from, Layoutable to) { if (to == null) return null; @@ -181,183 +276,295 @@ namespace Avalonia.Controls return _nodeDic[value]; } - public void Reset(Size arrangeSize) + public void Measure(Size availableSize) { - _arrangeSize = arrangeSize; - _nodeDic.Clear(); + AvailableSize = availableSize; + Measure(_nodeDic.Values, null); } - public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null, null); - - private bool CheckCyclic(IEnumerable nodes, GraphNode? waitNode, HashSet? set) + private void Measure(IEnumerable nodes, HashSet set) { - if (set == null) - { - set = new HashSet(); - } + set ??= new HashSet(); foreach (var node in nodes) { - if (!node.Arranged && node.OutgoingNodes.Count == 0) + /* + * 该节点无任何依赖,所以从这里开始计算元素位置。 + * 因为无任何依赖,所以忽略同级元素 + */ + if (!node.Measured && !node.OutgoingNodes.Any()) { - ArrangeChild(node, true); + MeasureChild(node); continue; } - if (node.OutgoingNodes.All(item => item.Arranged)) + // 判断依赖元素是否全部排列完毕 + if (node.OutgoingNodes.All(item => item.Measured)) { - ArrangeChild(node); + MeasureChild(node); continue; } + // 判断是否有循环 if (!set.Add(node.Element)) - return true; + throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); - return CheckCyclic(node.OutgoingNodes, node.Arranged ? null : node, set); - } + // 没有循环,且有依赖,则继续往下 + Measure(node.OutgoingNodes, set); - if (waitNode != null) - { - ArrangeChild(waitNode); + if (!node.Measured) + { + MeasureChild(node); + } } - return false; } - private void ArrangeChild(GraphNode node, bool ignoneSibling = false) + private void MeasureChild(GraphNode node) { var child = node.Element; - var childSize = child.DesiredSize; - var childPos = new Point(); + child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + node.OriginDesiredSize = child.DesiredSize; - if (GetAlignHorizontalCenterWithPanel(child)) + var alignLeftWithPanel = GetAlignLeftWithPanel(child); + var alignTopWithPanel = GetAlignTopWithPanel(child); + var alignRightWithPanel = GetAlignRightWithPanel(child); + var alignBottomWithPanel = GetAlignBottomWithPanel(child); + + #region Panel alignment + + if (alignLeftWithPanel) + node.Left = 0; + if (alignTopWithPanel) + node.Top = 0; + if (alignRightWithPanel) + node.Right = 0; + if (alignBottomWithPanel) + node.Bottom = 0; + + #endregion + + #region Sibling alignment + + if (node.AlignLeftWithNode != null) { - childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2); + node.Left = node.Left.IsNaN() ? node.AlignLeftWithNode.Left : node.AlignLeftWithNode.Left * 0.5; } - if (GetAlignVerticalCenterWithPanel(child)) + if (node.AlignTopWithNode != null) { - childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2); + node.Top = node.Top.IsNaN() ? node.AlignTopWithNode.Top : node.AlignTopWithNode.Top * 0.5; } - var alignLeftWithPanel = GetAlignLeftWithPanel(child); - var alignTopWithPanel = GetAlignTopWithPanel(child); - var alignRightWithPanel = GetAlignRightWithPanel(child); - var alignBottomWithPanel = GetAlignBottomWithPanel(child); + if (node.AlignRightWithNode != null) + { + node.Right = node.Right.IsNaN() + ? node.AlignRightWithNode.Right + : node.AlignRightWithNode.Right * 0.5; + } - if (!ignoneSibling) + if (node.AlignBottomWithNode != null) { - if (node.LeftOfNode != null) - { - childPos = childPos.WithX(node.LeftOfNode.Position.X - childSize.Width); - } + node.Bottom = node.Bottom.IsNaN() + ? node.AlignBottomWithNode.Bottom + : node.AlignBottomWithNode.Bottom * 0.5; + } - if (node.AboveNode != null) - { - childPos = childPos.WithY(node.AboveNode.Position.Y - childSize.Height); - } + #endregion + + #region Measure + + var availableHeight = AvailableSize.Height - node.Top - node.Bottom; + if (availableHeight.IsNaN()) + { + availableHeight = AvailableSize.Height; - if (node.RightOfNode != null) + if (!node.Top.IsNaN() && node.Bottom.IsNaN()) { - childPos = childPos.WithX(node.RightOfNode.Position.X + node.RightOfNode.Element.DesiredSize.Width); + availableHeight -= node.Top; } - - if (node.BelowNode != null) + else if (node.Top.IsNaN() && !node.Bottom.IsNaN()) { - childPos = childPos.WithY(node.BelowNode.Position.Y + node.BelowNode.Element.DesiredSize.Height); + availableHeight -= node.Bottom; } + } - if (node.AlignHorizontalCenterWith != null) + var availableWidth = AvailableSize.Width - node.Left - node.Right; + if (availableWidth.IsNaN()) + { + availableWidth = AvailableSize.Width; + + if (!node.Left.IsNaN() && node.Right.IsNaN()) { - childPos = childPos.WithX(node.AlignHorizontalCenterWith.Position.X + - (node.AlignHorizontalCenterWith.Element.DesiredSize.Width - childSize.Width) / 2); + availableWidth -= node.Left; } - - if (node.AlignVerticalCenterWith != null) + else if (node.Left.IsNaN() && !node.Right.IsNaN()) { - childPos = childPos.WithY(node.AlignVerticalCenterWith.Position.Y + - (node.AlignVerticalCenterWith.Element.DesiredSize.Height - childSize.Height) / 2); + availableWidth -= node.Right; } + } + + child.Measure(new Size(Math.Max(availableWidth, 0), Math.Max(availableHeight, 0))); + var childSize = child.DesiredSize; + + #endregion - if (node.AlignLeftWithNode != null) + #region Sibling positional + + if (node.LeftOfNode != null && node.Left.IsNaN()) + { + node.Left = node.LeftOfNode.Left - childSize.Width; + } + + if (node.AboveNode != null && node.Top.IsNaN()) + { + node.Top = node.AboveNode.Top - childSize.Height; + } + + if (node.RightOfNode != null) + { + if (node.Right.IsNaN()) { - childPos = childPos.WithX(node.AlignLeftWithNode.Position.X); + node.Right = node.RightOfNode.Right - childSize.Width; } - if (node.AlignTopWithNode != null) + if (node.Left.IsNaN()) { - childPos = childPos.WithY(node.AlignTopWithNode.Position.Y); + node.Left = AvailableSize.Width - node.RightOfNode.Right; } + } - if (node.AlignRightWithNode != null) + if (node.BelowNode != null) + { + if (node.Bottom.IsNaN()) { - childPos = childPos.WithX(node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width); + node.Bottom = node.BelowNode.Bottom - childSize.Height; } - if (node.AlignBottomWithNode != null) + if (node.Top.IsNaN()) { - childPos = childPos.WithY(node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height); + node.Top = AvailableSize.Height - node.BelowNode.Bottom; } } - if (alignLeftWithPanel) + #endregion + + #region Sibling-center alignment + + if (node.AlignHorizontalCenterWith != null) { - if (node.AlignRightWithNode != null) - { - childPos = childPos.WithX((node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width) / 2); - } + var halfWidthLeft = (AvailableSize.Width + node.AlignHorizontalCenterWith.Left - node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5; + var halfWidthRight = (AvailableSize.Width - node.AlignHorizontalCenterWith.Left + node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5; + + if (node.Left.IsNaN()) + node.Left = halfWidthLeft; else - { - childPos = childPos.WithX(0); - } + node.Left = (node.Left + halfWidthLeft) * 0.5; + + if (node.Right.IsNaN()) + node.Right = halfWidthRight; + else + node.Right = (node.Right + halfWidthRight) * 0.5; } - if (alignTopWithPanel) + if (node.AlignVerticalCenterWith != null) { - if (node.AlignBottomWithNode != null) - { - childPos = childPos.WithY((node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height) / 2); - } + var halfHeightTop = (AvailableSize.Height + node.AlignVerticalCenterWith.Top - node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5; + var halfHeightBottom = (AvailableSize.Height - node.AlignVerticalCenterWith.Top + node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5; + + if (node.Top.IsNaN()) + node.Top = halfHeightTop; else - { - childPos = childPos.WithY(0); - } + node.Top = (node.Top + halfHeightTop) * 0.5; + + if (node.Bottom.IsNaN()) + node.Bottom = halfHeightBottom; + else + node.Bottom = (node.Bottom + halfHeightBottom) * 0.5; } - if (alignRightWithPanel) + #endregion + + #region Panel-center alignment + + if (GetAlignHorizontalCenterWithPanel(child)) { - if (alignLeftWithPanel) - { - childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2); - } - else if (node.AlignLeftWithNode == null) - { - childPos = childPos.WithX(_arrangeSize.Width - childSize.Width); - } + var halfSubWidth = (AvailableSize.Width - childSize.Width) * 0.5; + + if (node.Left.IsNaN()) + node.Left = halfSubWidth; else - { - childPos = childPos.WithX((_arrangeSize.Width + node.AlignLeftWithNode.Position.X - childSize.Width) / 2); - } + node.Left = (node.Left + halfSubWidth) * 0.5; + + if (node.Right.IsNaN()) + node.Right = halfSubWidth; + else + node.Right = (node.Right + halfSubWidth) * 0.5; } - if (alignBottomWithPanel) + if (GetAlignVerticalCenterWithPanel(child)) { - if (alignTopWithPanel) - { - childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2); - } - else if (node.AlignTopWithNode == null) + var halfSubHeight = (AvailableSize.Height - childSize.Height) * 0.5; + + if (node.Top.IsNaN()) + node.Top = halfSubHeight; + else + node.Top = (node.Top + halfSubHeight) * 0.5; + + if (node.Bottom.IsNaN()) + node.Bottom = halfSubHeight; + else + node.Bottom = (node.Bottom + halfSubHeight) * 0.5; + } + + #endregion + + if (node.Left.IsNaN()) + { + if (!node.Right.IsNaN()) + node.Left = AvailableSize.Width - node.Right - childSize.Width; + else { - childPos = childPos.WithY(_arrangeSize.Height - childSize.Height); + node.Left = 0; + node.Right = AvailableSize.Width - childSize.Width; } + } + else if (!node.Left.IsNaN() && node.Right.IsNaN()) + { + node.Right = AvailableSize.Width - node.Left - childSize.Width; + } + + if (node.Top.IsNaN()) + { + if (!node.Bottom.IsNaN()) + node.Top = AvailableSize.Height - node.Bottom - childSize.Height; else { - childPos = childPos.WithY((_arrangeSize.Height + node.AlignTopWithNode.Position.Y - childSize.Height) / 2); + node.Top = 0; + node.Bottom = AvailableSize.Height - childSize.Height; } } + else if (!node.Top.IsNaN() && node.Bottom.IsNaN()) + { + node.Bottom = AvailableSize.Height - node.Top - childSize.Height; + } + + node.Measured = true; + } + + public Size GetBoundingSize(bool calcWidth, bool calcHeight) + { + var boundingSize = new Size(); + + foreach (var node in _nodeDic.Values) + { + var size = node.GetBoundingSize(); + boundingSize = boundingSize.WithWidth(Math.Max(boundingSize.Width, size.Width)); + boundingSize = boundingSize.WithHeight(Math.Max(boundingSize.Height, size.Height)); + } - child.Arrange(new Rect(childPos.X, childPos.Y, childSize.Width, childSize.Height)); - node.Position = childPos; - node.Arranged = true; + boundingSize = boundingSize.WithWidth(calcWidth ? boundingSize.Width : AvailableSize.Width); + boundingSize = boundingSize.WithHeight(calcHeight ? boundingSize.Height : AvailableSize.Height); + return boundingSize; } } } From c96f328d0633cdb27d126ac965fa746d1b3c999e Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 14 Jul 2020 07:49:39 +0300 Subject: [PATCH 14/85] Fix typo in PointerReleasedEventMessage --- src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index f5c673d5f9..931c27c575 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -192,7 +192,7 @@ namespace Avalonia.Controls.Remote.Server GetAvaloniaInputModifiers(pressed.Modifiers))); }, DispatcherPriority.Input); } - if (obj is PointerPressedEventMessage released) + if (obj is PointerReleasedEventMessage released) { Dispatcher.UIThread.Post(() => { From 1c8541c06011f7827bf61aa5227ea894854b6f34 Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Tue, 14 Jul 2020 11:52:04 +0300 Subject: [PATCH 15/85] fix typo & add missing attribute --- src/Avalonia.Controls/RelativePanel.AttachedProperties.cs | 2 +- tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index f93de5ca15..f64c26682b 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls AlignVerticalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); BelowProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); - LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + RightOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); } /// diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 4248e643eb..99991cca02 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -31,6 +31,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(20, 0, 20, 20), target.Children[1].Bounds); } + [Fact] public void Lays_Out_1_Child_Below_the_other() { var rect1 = new Rectangle { Height = 20, Width = 20 }; From 9f6c52711ef6e2cbd6c23634c869acfb914b8348 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 10:59:46 -0300 Subject: [PATCH 16/85] add more relativepanel tests. --- .../RelativePanelTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 4248e643eb..7b1f6d07b7 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -31,6 +31,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(20, 0, 20, 20), target.Children[1].Bounds); } + [Fact] public void Lays_Out_1_Child_Below_the_other() { var rect1 = new Rectangle { Height = 20, Width = 20 }; @@ -55,5 +56,31 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds); } + + [Fact] + public void RelativePanel_Can_Center() + { + var rect1 = new Rectangle { Height = 20, Width = 20 }; + var rect2 = new Rectangle { Height = 20, Width = 20 }; + + var target = new RelativePanel + { + VerticalAlignment = Layout.VerticalAlignment.Center, + HorizontalAlignment = Layout.HorizontalAlignment.Center, + Children = + { + rect1, rect2 + } + }; + + RelativePanel.SetAlignLeftWithPanel(rect1, true); + RelativePanel.SetBelow(rect2, rect1); + target.Measure(new Size(400, 400)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(20, 40), target.Bounds.Size); + Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); + Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds); + } } } From 0c365b837d030896a8503298db78e19e275ba738 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 11:02:04 -0300 Subject: [PATCH 17/85] fix relativepanel --- src/Avalonia.Controls/RelativePanel.AttachedProperties.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index f93de5ca15..f64c26682b 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls AlignVerticalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); BelowProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); - LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + RightOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); } /// From cdd8df383eb6c850b93e470fcf8292a7143e87bf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 11:02:18 -0300 Subject: [PATCH 18/85] make utility methods internal. --- src/Avalonia.Controls/RelativePanel.cs | 54 +++++++++++++------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index a5743f4153..c50817f460 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -8,36 +8,12 @@ using Avalonia.Layout; namespace Avalonia.Controls { - public static partial class Extensions - { - /// - /// Returns a value that indicates whether the specified value is not a number (). - /// - /// A double-precision floating-point number. - /// true if evaluates to ; otherwise, false. - public static bool IsNaN(this double d) - { - return double.IsNaN(d); - } - - public static IEnumerable Do(this IEnumerable source, Action predicate) - { - var enumerable = source as IList ?? source.ToList(); - foreach (var item in enumerable) - { - predicate.Invoke(item); - } - - return enumerable; - } - } - public partial class RelativePanel : Panel { private readonly Graph _childGraph; public RelativePanel() => _childGraph = new Graph(); - + private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { @@ -76,7 +52,7 @@ namespace Avalonia.Controls node.BelowNode = _childGraph.AddLink(node, GetDependencyElement(BelowProperty, child)); node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child)); - node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); + node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); } _childGraph.Measure(availableSize); @@ -207,7 +183,7 @@ namespace Avalonia.Controls prevSize = prevSize.WithHeight(prevSize.Height + node.BoundingSize.Height); if (GetAlignVerticalCenterWithPanel(node.Element) || node.VerticalOffsetFlag) { - prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height); + prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height); prevNode.VerticalOffsetFlag = true; } if (node.HorizontalOffsetFlag) @@ -568,4 +544,28 @@ namespace Avalonia.Controls } } } + + internal static partial class Extensions + { + /// + /// Returns a value that indicates whether the specified value is not a number (). + /// + /// A double-precision floating-point number. + /// true if evaluates to ; otherwise, false. + public static bool IsNaN(this double d) + { + return double.IsNaN(d); + } + + public static IEnumerable Do(this IEnumerable source, Action predicate) + { + var enumerable = source as IList ?? source.ToList(); + foreach (var item in enumerable) + { + predicate.Invoke(item); + } + + return enumerable; + } + } } From 70c71263ae8841d81121a13902723ec62e2bd014 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 11:04:34 -0300 Subject: [PATCH 19/85] fix nullable implementation. --- src/Avalonia.Controls/RelativePanel.cs | 27 +++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index c50817f460..e0a47b056b 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -1,4 +1,4 @@ -/// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs +// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs using System; using System.Collections.Generic; using System.Linq; @@ -14,7 +14,6 @@ namespace Avalonia.Controls public RelativePanel() => _childGraph = new Graph(); - private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { var dependency = child.GetValue(property); @@ -100,25 +99,25 @@ namespace Avalonia.Controls public HashSet OutgoingNodes { get; } - public GraphNode AlignLeftWithNode { get; set; } + public GraphNode? AlignLeftWithNode { get; set; } - public GraphNode AlignTopWithNode { get; set; } + public GraphNode? AlignTopWithNode { get; set; } - public GraphNode AlignRightWithNode { get; set; } + public GraphNode? AlignRightWithNode { get; set; } - public GraphNode AlignBottomWithNode { get; set; } + public GraphNode? AlignBottomWithNode { get; set; } - public GraphNode LeftOfNode { get; set; } + public GraphNode? LeftOfNode { get; set; } - public GraphNode AboveNode { get; set; } + public GraphNode? AboveNode { get; set; } - public GraphNode RightOfNode { get; set; } + public GraphNode? RightOfNode { get; set; } - public GraphNode BelowNode { get; set; } + public GraphNode? BelowNode { get; set; } - public GraphNode AlignHorizontalCenterWith { get; set; } + public GraphNode? AlignHorizontalCenterWith { get; set; } - public GraphNode AlignVerticalCenterWith { get; set; } + public GraphNode? AlignVerticalCenterWith { get; set; } public GraphNode(Layoutable element) { @@ -220,7 +219,7 @@ namespace Avalonia.Controls public void Reset() => _nodeDic.Values.Do(node => node.Reset()); - public GraphNode AddLink(GraphNode from, Layoutable to) + public GraphNode? AddLink(GraphNode from, Layoutable? to) { if (to == null) return null; @@ -258,7 +257,7 @@ namespace Avalonia.Controls Measure(_nodeDic.Values, null); } - private void Measure(IEnumerable nodes, HashSet set) + private void Measure(IEnumerable nodes, HashSet? set) { set ??= new HashSet(); From 0001578f0cc4d5c0fb2c16249b0900f150aace34 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 11:09:46 -0300 Subject: [PATCH 20/85] remove regions. --- src/Avalonia.Controls/RelativePanel.cs | 32 -------------------------- 1 file changed, 32 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index e0a47b056b..a3ad30db76 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -31,8 +31,6 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - #region Calc DesiredSize - _childGraph.Clear(); foreach (Layoutable child in Children) { @@ -56,17 +54,11 @@ namespace Avalonia.Controls } _childGraph.Measure(availableSize); - #endregion - - #region Calc AvailableSize - _childGraph.Reset(); var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN()); _childGraph.Reset(); _childGraph.Measure(boundingSize); return boundingSize; - - #endregion } protected override Size ArrangeOverride(Size arrangeSize) @@ -305,8 +297,6 @@ namespace Avalonia.Controls var alignRightWithPanel = GetAlignRightWithPanel(child); var alignBottomWithPanel = GetAlignBottomWithPanel(child); - #region Panel alignment - if (alignLeftWithPanel) node.Left = 0; if (alignTopWithPanel) @@ -316,10 +306,6 @@ namespace Avalonia.Controls if (alignBottomWithPanel) node.Bottom = 0; - #endregion - - #region Sibling alignment - if (node.AlignLeftWithNode != null) { node.Left = node.Left.IsNaN() ? node.AlignLeftWithNode.Left : node.AlignLeftWithNode.Left * 0.5; @@ -344,10 +330,6 @@ namespace Avalonia.Controls : node.AlignBottomWithNode.Bottom * 0.5; } - #endregion - - #region Measure - var availableHeight = AvailableSize.Height - node.Top - node.Bottom; if (availableHeight.IsNaN()) { @@ -381,10 +363,6 @@ namespace Avalonia.Controls child.Measure(new Size(Math.Max(availableWidth, 0), Math.Max(availableHeight, 0))); var childSize = child.DesiredSize; - #endregion - - #region Sibling positional - if (node.LeftOfNode != null && node.Left.IsNaN()) { node.Left = node.LeftOfNode.Left - childSize.Width; @@ -421,10 +399,6 @@ namespace Avalonia.Controls } } - #endregion - - #region Sibling-center alignment - if (node.AlignHorizontalCenterWith != null) { var halfWidthLeft = (AvailableSize.Width + node.AlignHorizontalCenterWith.Left - node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5; @@ -457,10 +431,6 @@ namespace Avalonia.Controls node.Bottom = (node.Bottom + halfHeightBottom) * 0.5; } - #endregion - - #region Panel-center alignment - if (GetAlignHorizontalCenterWithPanel(child)) { var halfSubWidth = (AvailableSize.Width - childSize.Width) * 0.5; @@ -491,8 +461,6 @@ namespace Avalonia.Controls node.Bottom = (node.Bottom + halfSubHeight) * 0.5; } - #endregion - if (node.Left.IsNaN()) { if (!node.Right.IsNaN()) From 3fb97afa6d88431812e0398d29eb03127b667d8d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 13:33:35 -0300 Subject: [PATCH 21/85] make toggle switch use verticalcontentalignment center by default. --- src/Avalonia.Themes.Default/ToggleSwitch.xaml | 3 ++- src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml index 893d64f505..ded121f5f6 100644 --- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml @@ -43,7 +43,8 @@ - + + diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index e7f8fb1641..4309edefe3 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -43,7 +43,8 @@ - + + From 5cf6662f741cb0b9c1bc5cefc4ebe5f058aa8135 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 17:25:15 +0200 Subject: [PATCH 22/85] Bind ListBox.SelectedItems again. Was removed accidentally. --- samples/ControlCatalog/Pages/ListBoxPage.xaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 47b4ce7151..f4d81418ac 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -10,7 +10,13 @@ HorizontalAlignment="Center" Spacing="16"> - + From 162583b2decfd264c6e4be9661d31209045eb36e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 14:30:31 -0300 Subject: [PATCH 23/85] add a failing unit test. --- .../ApplicationTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index e533001242..d485d424fb 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Reactive.Subjects; +using Avalonia.Data; using Avalonia.Threading; using Avalonia.UnitTests; using Xunit; @@ -32,5 +34,20 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } } + + [Fact] + public void Can_Bind_To_DataContext() + { + using (UnitTestApplication.Start()) + { + var application = Application.Current; + + application.DataContext = "Test"; + + application.Bind(Application.NameProperty, new Binding(".")); + + Assert.Equal("Test", Application.Current.Name); + } + } } } From f1eae6ce1289140063f36f8c86800f3d2cf2a314 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 14:31:00 -0300 Subject: [PATCH 24/85] correctly cast to IDataContextProvider. --- src/Markup/Avalonia.Markup/Data/BindingBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index 7c4e7b5efe..3dbc83a7df 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -137,9 +137,9 @@ namespace Avalonia.Data { Contract.Requires(target != null); - if (!(target is IStyledElement)) + if (!(target is IDataContextProvider)) { - target = anchor as IStyledElement; + target = anchor as IDataContextProvider; if (target == null) { From a5499a908d8a7d740fc2873ce16298c08c34deae Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 14:33:45 -0300 Subject: [PATCH 25/85] remove usings. --- tests/Avalonia.Controls.UnitTests/ApplicationTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index d485d424fb..58ddc8ca60 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Reactive.Subjects; using Avalonia.Data; -using Avalonia.Threading; using Avalonia.UnitTests; using Xunit; From 73a2637eed83f082be19a7d136aa5a0da164e1aa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 14 Jul 2020 22:04:47 +0200 Subject: [PATCH 26/85] Added failing test for removing selected item with BeginInit. --- .../Primitives/SelectingItemsControlTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index fe9c7b1261..9ef2750ff3 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -531,6 +531,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); target.SelectedIndex = 1; Assert.Equal(items[1], target.SelectedItem); @@ -549,6 +550,45 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.NotNull(receivedArgs); Assert.Empty(receivedArgs.AddedItems); Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); + Assert.False(items.Single().IsSelected); + } + + [Fact] + public void Removing_Selected_Item_Should_Clear_Selection_With_BeginInit() + { + var items = new AvaloniaList + { + new Item(), + new Item(), + }; + + var target = new SelectingItemsControl(); + target.BeginInit(); + target.Items = items; + target.Template = Template(); + target.EndInit(); + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectedIndex = 0; + + Assert.Equal(items[0], target.SelectedItem); + Assert.Equal(0, target.SelectedIndex); + + SelectionChangedEventArgs receivedArgs = null; + + target.SelectionChanged += (_, args) => receivedArgs = args; + + var removed = items[0]; + + items.RemoveAt(0); + + Assert.Null(target.SelectedItem); + Assert.Equal(-1, target.SelectedIndex); + Assert.NotNull(receivedArgs); + Assert.Empty(receivedArgs.AddedItems); + Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); + Assert.False(items.Single().IsSelected); } [Fact] From 6555a51f5ae6986c17ad2da3f767e467712d612a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 14 Jul 2020 22:37:23 +0200 Subject: [PATCH 27/85] Fix selection after deleting an item. `SelectionModel` needs to subscribe to `CollectionChanged` on the items before `ItemsControl` in order for the selection to be correct when we come to setting the selected state. Because `SelectionModel.Source` isn't subscribed during initialization in `ItemsChanged`, we also need to make sure we don't subscribe `ItemsControl` to the collection changes during initialization. Instead subscribe in `OnInitialized` (this requires a few tests to be rooted in order to be called). Fixes #4293 --- src/Avalonia.Controls/ItemsControl.cs | 11 +++++++++-- tests/Avalonia.Controls.UnitTests/CarouselTests.cs | 3 +++ .../Avalonia.Controls.UnitTests/ItemsControlTests.cs | 4 ++++ .../Primitives/SelectingItemsControlTests.cs | 4 ++++ .../Primitives/SelectingItemsControlTests_Multiple.cs | 4 ++++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 6e0ad66699..da9f619932 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -70,7 +70,6 @@ namespace Avalonia.Controls public ItemsControl() { PseudoClasses.Add(":empty"); - SubscribeToItems(_items); } /// @@ -265,6 +264,11 @@ namespace Avalonia.Controls { } + protected override void OnInitialized() + { + SubscribeToItems(_items); + } + /// /// Handles directional navigation within the . /// @@ -330,7 +334,10 @@ namespace Avalonia.Controls Presenter.Items = newValue; } - SubscribeToItems(newValue); + if (IsInitialized) + { + SubscribeToItems(newValue); + } } /// diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs index a292910fae..c6ca0fb2bf 100644 --- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.LogicalTree; +using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; @@ -155,6 +156,7 @@ namespace Avalonia.Controls.UnitTests IsVirtualized = false }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -247,6 +249,7 @@ namespace Avalonia.Controls.UnitTests IsVirtualized = false }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 684486cbae..faaa3ed063 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -131,6 +131,7 @@ namespace Avalonia.Controls.UnitTests var child = new Control(); var items = new AvaloniaList(child); + var root = new TestRoot(target); target.Template = GetTemplate(); target.Items = items; items.RemoveAt(0); @@ -283,6 +284,7 @@ namespace Avalonia.Controls.UnitTests var items = new AvaloniaList { "Foo" }; var called = false; + var root = new TestRoot(target); target.Template = GetTemplate(); target.Items = items; target.ApplyTemplate(); @@ -303,6 +305,7 @@ namespace Avalonia.Controls.UnitTests var items = new AvaloniaList { "Foo", "Bar" }; var called = false; + var root = new TestRoot(target); target.Template = GetTemplate(); target.Items = items; target.ApplyTemplate(); @@ -376,6 +379,7 @@ namespace Avalonia.Controls.UnitTests Items = new[] { 1, 2, 3 }, }; + var root = new TestRoot(target); Assert.DoesNotContain(":empty", target.Classes); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 9ef2750ff3..e43e855ae0 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -170,6 +170,8 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected }; + var root = new TestRoot(listBox); + listBox.BeginInit(); listBox.SelectedIndex = 1; @@ -480,6 +482,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); items.Add(new Item { IsSelected = true }); @@ -919,6 +922,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Items = items, }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index dcf25beb50..e9ec8d114f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -1014,6 +1014,7 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Multiple, }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -1043,6 +1044,7 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Multiple, }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -1076,6 +1078,7 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Multiple, }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -1199,6 +1202,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), }; + var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); items.Add(new ItemContainer { IsSelected = true }); From e87697f901798c7f1fffe7bcca3a36c59f8c2a3c Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 15 Jul 2020 06:19:14 +0200 Subject: [PATCH 28/85] Allow combining TextTrimming and TextWrapping --- .../GenericTextParagraphProperties.cs | 15 +- .../TextFormatting/ShapedTextCharacters.cs | 3 +- .../TextCollapsingProperties.cs | 23 ++ .../TextFormatting/TextCollapsingStyle.cs | 18 ++ .../Media/TextFormatting/TextFormatterImpl.cs | 221 +++++------------- .../Media/TextFormatting/TextLayout.cs | 62 +++-- .../Media/TextFormatting/TextLine.cs | 11 + .../Media/TextFormatting/TextLineImpl.cs | 136 ++++++++++- .../Media/TextFormatting/TextLineMetrics.cs | 11 +- .../TextFormatting/TextParagraphProperties.cs | 5 - .../TextTrailingCharacterEllipsis.cs | 33 +++ .../TextTrailingWordEllipsis.cs | 37 +++ .../Unicode/LineBreakEnumerator.cs | 1 - src/Avalonia.Visuals/Media/TextWrapping.cs | 16 +- .../Media/TextFormatting/TextLayoutTests.cs | 12 +- .../Media/TextFormatting/TextLineTests.cs | 58 +++++ 16 files changed, 441 insertions(+), 221 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs create mode 100644 src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs create mode 100644 src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs create mode 100644 src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs diff --git a/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs index c4302aecec..8e7d934bca 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs @@ -4,14 +4,12 @@ { private TextAlignment _textAlignment; private TextWrapping _textWrapping; - private TextTrimming _textTrimming; private double _lineHeight; public GenericTextParagraphProperties( TextRunProperties defaultTextRunProperties, TextAlignment textAlignment = TextAlignment.Left, - TextWrapping textWrapping = TextWrapping.WrapWithOverflow, - TextTrimming textTrimming = TextTrimming.None, + TextWrapping textWrapping = TextWrapping.NoWrap, double lineHeight = 0) { DefaultTextRunProperties = defaultTextRunProperties; @@ -20,8 +18,6 @@ _textWrapping = textWrapping; - _textTrimming = textTrimming; - _lineHeight = lineHeight; } @@ -31,8 +27,6 @@ public override TextWrapping TextWrapping => _textWrapping; - public override TextTrimming TextTrimming => _textTrimming; - public override double LineHeight => _lineHeight; /// @@ -50,13 +44,6 @@ { _textWrapping = textWrapping; } - /// - /// Set text trimming - /// - internal void SetTextTrimming(TextTrimming textTrimming) - { - _textTrimming = textTrimming; - } /// /// Set line height diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs index b71fe5bc3c..9e67a03f45 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs @@ -1,5 +1,4 @@ -using Avalonia.Media.TextFormatting.Unicode; -using Avalonia.Utilities; +using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs new file mode 100644 index 0000000000..ffd65423a3 --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs @@ -0,0 +1,23 @@ +namespace Avalonia.Media.TextFormatting +{ + /// + /// Properties of text collapsing + /// + public abstract class TextCollapsingProperties + { + /// + /// Gets the width in which the collapsible range is constrained to + /// + public abstract double Width { get; } + + /// + /// Gets the text run that is used as collapsing symbol + /// + public abstract TextRun Symbol { get; } + + /// + /// Gets the style of collapsing + /// + public abstract TextCollapsingStyle Style { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs new file mode 100644 index 0000000000..1523cc4d9a --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs @@ -0,0 +1,18 @@ +namespace Avalonia.Media.TextFormatting +{ + /// + /// Text collapsing style + /// + public enum TextCollapsingStyle + { + /// + /// Collapse trailing characters + /// + TrailingCharacter, + + /// + /// Collapse trailing words + /// + TrailingWord, + } +} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 3ad23f3504..061949a5c9 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -1,49 +1,41 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Media.TextFormatting.Unicode; -using Avalonia.Platform; -using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { internal class TextFormatterImpl : TextFormatter { - private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' }); - /// public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null) { - var textTrimming = paragraphProperties.TextTrimming; var textWrapping = paragraphProperties.TextWrapping; - TextLine textLine = null; var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak, out var nextLineBreak); var textRange = GetTextRange(textRuns); - if (textTrimming != TextTrimming.None) - { - textLine = PerformTextTrimming(textRuns, textRange, paragraphWidth, paragraphProperties); - } - else + TextLine textLine; + + switch (textWrapping) { - switch (textWrapping) - { - case TextWrapping.NoWrap: - { - var textLineMetrics = - TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties); + case TextWrapping.NoWrap: + { + var textLineMetrics = + TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties); - textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak); - break; - } - case TextWrapping.WrapWithOverflow: - case TextWrapping.Wrap: - { - textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties); - break; - } - } + textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak); + break; + } + case TextWrapping.WrapWithOverflow: + case TextWrapping.Wrap: + { + textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties); + break; + } + default: + throw new ArgumentOutOfRangeException(); } return textLine; @@ -174,87 +166,6 @@ namespace Avalonia.Media.TextFormatting return false; } - /// - /// Performs text trimming and returns a trimmed line. - /// - /// The text runs to perform the trimming on. - /// The text range that is covered by the text runs. - /// A value that specifies the width of the paragraph that the line fills. - /// A value that represents paragraph properties, - /// such as TextWrapping, TextAlignment, or TextStyle. - /// - private static TextLine PerformTextTrimming(IReadOnlyList textRuns, TextRange textRange, - double paragraphWidth, TextParagraphProperties paragraphProperties) - { - var textTrimming = paragraphProperties.TextTrimming; - var availableWidth = paragraphWidth; - var currentWidth = 0.0; - var runIndex = 0; - - while (runIndex < textRuns.Count) - { - var currentRun = textRuns[runIndex]; - - currentWidth += currentRun.GlyphRun.Bounds.Width; - - if (currentWidth > availableWidth) - { - var ellipsisRun = CreateEllipsisRun(currentRun.Properties); - - var measuredLength = MeasureText(currentRun, availableWidth - ellipsisRun.GlyphRun.Bounds.Width); - - if (textTrimming == TextTrimming.WordEllipsis) - { - if (measuredLength < textRange.End) - { - var currentBreakPosition = 0; - - var lineBreaker = new LineBreakEnumerator(currentRun.Text); - - while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) - { - var nextBreakPosition = lineBreaker.Current.PositionWrap; - - if (nextBreakPosition == 0) - { - break; - } - - if (nextBreakPosition > measuredLength) - { - break; - } - - currentBreakPosition = nextBreakPosition; - } - - measuredLength = currentBreakPosition; - } - } - - var splitResult = SplitTextRuns(textRuns, measuredLength); - - var trimmedRuns = new List(splitResult.First.Count + 1); - - trimmedRuns.AddRange(splitResult.First); - - trimmedRuns.Add(ellipsisRun); - - var textLineMetrics = - TextLineMetrics.Create(trimmedRuns, textRange, paragraphWidth, paragraphProperties); - - return new TextLineImpl(trimmedRuns, textLineMetrics); - } - - availableWidth -= currentRun.GlyphRun.Bounds.Width; - - runIndex++; - } - - return new TextLineImpl(textRuns, - TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)); - } - /// /// Performs text wrapping returns a list of text lines. /// @@ -269,7 +180,7 @@ namespace Avalonia.Media.TextFormatting var availableWidth = paragraphWidth; var currentWidth = 0.0; var runIndex = 0; - var length = 0; + var currentLength = 0; while (runIndex < textRuns.Count) { @@ -279,58 +190,53 @@ namespace Avalonia.Media.TextFormatting { var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth); + var breakFound = false; + + var currentBreakPosition = 0; + if (measuredLength < currentRun.Text.Length) { - if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) - { - var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(measuredLength)); + var lineBreaker = new LineBreakEnumerator(currentRun.Text); - if (lineBreaker.MoveNext()) - { - measuredLength += lineBreaker.Current.PositionWrap; - } - else - { - measuredLength = currentRun.Text.Length; - } - } - else + while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) { - var currentBreakPosition = -1; + var nextBreakPosition = lineBreaker.Current.PositionWrap; - var lineBreaker = new LineBreakEnumerator(currentRun.Text); - - while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) + if (nextBreakPosition == 0 || nextBreakPosition > measuredLength) { - var nextBreakPosition = lineBreaker.Current.PositionWrap; + break; + } - if (nextBreakPosition == 0) - { - break; - } + breakFound = lineBreaker.Current.Required || + lineBreaker.Current.PositionWrap != currentRun.Text.Length; - if (nextBreakPosition > measuredLength) - { - break; - } + currentBreakPosition = nextBreakPosition; + } + } - currentBreakPosition = nextBreakPosition; - } + if (breakFound) + { + measuredLength = currentBreakPosition; + } + else + { + if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) + { + var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(currentBreakPosition)); - if (currentBreakPosition != -1) + if (lineBreaker.MoveNext()) { - measuredLength = currentBreakPosition; + measuredLength = currentBreakPosition + lineBreaker.Current.PositionWrap; } - } } - length += measuredLength; + currentLength += measuredLength; - var splitResult = SplitTextRuns(textRuns, length); + var splitResult = SplitTextRuns(textRuns, currentLength); var textLineMetrics = TextLineMetrics.Create(splitResult.First, - new TextRange(textRange.Start, length), paragraphWidth, paragraphProperties); + new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties); var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ? new TextLineBreak(splitResult.Second) : @@ -341,7 +247,7 @@ namespace Avalonia.Media.TextFormatting currentWidth += currentRun.GlyphRun.Bounds.Width; - length += currentRun.GlyphRun.Characters.Length; + currentLength += currentRun.GlyphRun.Characters.Length; runIndex++; } @@ -356,7 +262,7 @@ namespace Avalonia.Media.TextFormatting /// The text run. /// The available width. /// - private static int MeasureText(ShapedTextCharacters textCharacters, double availableWidth) + internal static int MeasureText(ShapedTextCharacters textCharacters, double availableWidth) { var glyphRun = textCharacters.GlyphRun; @@ -391,10 +297,8 @@ namespace Avalonia.Media.TextFormatting } else { - for (var i = 0; i < glyphRun.GlyphAdvances.Length; i++) + foreach (var advance in glyphRun.GlyphAdvances) { - var advance = glyphRun.GlyphAdvances[i]; - if (currentWidth + advance > availableWidth) { break; @@ -423,21 +327,6 @@ namespace Avalonia.Media.TextFormatting return lastCluster - firstCluster; } - /// - /// Creates an ellipsis. - /// - /// The text run properties. - /// - private static ShapedTextCharacters CreateEllipsisRun(TextRunProperties properties) - { - var formatterImpl = AvaloniaLocator.Current.GetService(); - - var glyphRun = formatterImpl.ShapeText(s_ellipsis, properties.Typeface, properties.FontRenderingEmSize, - properties.CultureInfo); - - return new ShapedTextCharacters(glyphRun, properties); - } - /// /// Gets the text range that is covered by the text runs. /// @@ -470,7 +359,7 @@ namespace Avalonia.Media.TextFormatting /// The text run's. /// The length to split at. /// The split text runs. - private static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length) + internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length) { var currentLength = 0; @@ -543,7 +432,7 @@ namespace Avalonia.Media.TextFormatting return new SplitTextRunsResult(textRuns, null); } - private readonly struct SplitTextRunsResult + internal readonly struct SplitTextRunsResult { public SplitTextRunsResult(IReadOnlyList first, IReadOnlyList second) { diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index 54745144c8..92db6b69c4 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utilities; -using Avalonia.Platform; namespace Avalonia.Media.TextFormatting { @@ -17,6 +15,7 @@ namespace Avalonia.Media.TextFormatting private readonly ReadOnlySlice _text; private readonly TextParagraphProperties _paragraphProperties; private readonly IReadOnlyList> _textStyleOverrides; + private readonly TextTrimming _textTrimming; /// /// Initializes a new instance of the class. @@ -54,9 +53,11 @@ namespace Avalonia.Media.TextFormatting new ReadOnlySlice(text.AsMemory()); _paragraphProperties = - CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, textTrimming, + CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, textDecorations, lineHeight); + _textTrimming = textTrimming; + _textStyleOverrides = textStyleOverrides; LineHeight = lineHeight; @@ -143,18 +144,16 @@ namespace Avalonia.Media.TextFormatting /// The foreground. /// The text alignment. /// The text wrapping. - /// The text trimming. /// The text decorations. /// The height of each line of text. /// private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize, - IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping, TextTrimming textTrimming, + IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping, TextDecorationCollection textDecorations, double lineHeight) { var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground); - return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, textTrimming, - lineHeight); + return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, lineHeight); } /// @@ -214,25 +213,44 @@ namespace Avalonia.Media.TextFormatting var textSource = new FormattedTextSource(_text, _paragraphProperties.DefaultTextRunProperties, _textStyleOverrides); - TextLineBreak previousLineBreak = null; + TextLine previousLine = null; - while (currentPosition < _text.Length && (MaxLines == 0 || textLines.Count < MaxLines)) + while (currentPosition < _text.Length) { var textLine = TextFormatter.Current.FormatLine(textSource, currentPosition, MaxWidth, - _paragraphProperties, previousLineBreak); + _paragraphProperties, previousLine?.LineBreak); - previousLineBreak = textLine.LineBreak; + currentPosition += textLine.TextRange.Length; - textLines.Add(textLine); + if (textLines.Count > 0) + { + if (textLines.Count == MaxLines || !double.IsPositiveInfinity(MaxHeight) && + height + textLine.LineMetrics.Size.Height > MaxHeight) + { + if (previousLine?.LineBreak != null && _textTrimming != TextTrimming.None) + { + var collapsedLine = + previousLine.Collapse(GetCollapsingProperties(MaxWidth)); - UpdateBounds(textLine, ref width, ref height); + textLines[textLines.Count - 1] = collapsedLine; + } + + break; + } + } - if (!double.IsPositiveInfinity(MaxHeight) && height > MaxHeight) + var hasOverflowed = textLine.LineMetrics.HasOverflowed; + + if (hasOverflowed && _textTrimming != TextTrimming.None) { - break; + textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth)); } - currentPosition += textLine.TextRange.Length; + textLines.Add(textLine); + + UpdateBounds(textLine, ref width, ref height); + + previousLine = textLine; if (currentPosition != _text.Length || textLine.LineBreak == null) { @@ -250,6 +268,18 @@ namespace Avalonia.Media.TextFormatting } } + private TextCollapsingProperties GetCollapsingProperties(double width) + { + return _textTrimming switch + { + TextTrimming.CharacterEllipsis => new TextTrailingCharacterEllipsis(width, + _paragraphProperties.DefaultTextRunProperties), + TextTrimming.WordEllipsis => new TextTrailingWordEllipsis(width, + _paragraphProperties.DefaultTextRunProperties), + _ => throw new ArgumentOutOfRangeException(), + }; + } + private readonly struct FormattedTextSource : ITextSource { private readonly ReadOnlySlice _text; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs index c3b7dfc77a..3e3258f38a 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs @@ -39,6 +39,11 @@ namespace Avalonia.Media.TextFormatting /// public abstract TextLineBreak LineBreak { get; } + /// + /// Client to get a boolean value indicates whether a line has been collapsed + /// + public abstract bool HasCollapsed { get; } + /// /// Draws the at the given origin. /// @@ -46,6 +51,12 @@ namespace Avalonia.Media.TextFormatting /// The origin. public abstract void Draw(DrawingContext drawingContext, Point origin); + /// + /// Client to collapse the line and get a collapsed line that fits for display + /// + /// a list of collapsing properties + public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList); + /// /// Client to get the character hit corresponding to the specified /// distance from the beginning of the line. diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index a1a9b50793..820c943aea 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using Avalonia.Media.TextFormatting.Unicode; +using Avalonia.Platform; namespace Avalonia.Media.TextFormatting { @@ -7,11 +9,12 @@ namespace Avalonia.Media.TextFormatting private readonly IReadOnlyList _textRuns; public TextLineImpl(IReadOnlyList textRuns, TextLineMetrics lineMetrics, - TextLineBreak lineBreak = null) + TextLineBreak lineBreak = null, bool hasCollapsed = false) { _textRuns = textRuns; LineMetrics = lineMetrics; LineBreak = lineBreak; + HasCollapsed = hasCollapsed; } /// @@ -26,6 +29,9 @@ namespace Avalonia.Media.TextFormatting /// public override TextLineBreak LineBreak { get; } + /// + public override bool HasCollapsed { get; } + /// public override void Draw(DrawingContext drawingContext, Point origin) { @@ -41,6 +47,98 @@ namespace Avalonia.Media.TextFormatting } } + public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList) + { + if (collapsingPropertiesList == null || collapsingPropertiesList.Length == 0) + { + return this; + } + + var collapsingProperties = collapsingPropertiesList[0]; + var runIndex = 0; + var currentWidth = 0.0; + var textRange = TextRange; + var collapsedLength = 0; + TextLineMetrics textLineMetrics; + + var shapedSymbol = CreateShapedSymbol(collapsingProperties.Symbol); + + var availableWidth = collapsingProperties.Width - shapedSymbol.Bounds.Width; + + while (runIndex < _textRuns.Count) + { + var currentRun = _textRuns[runIndex]; + + currentWidth += currentRun.GlyphRun.Bounds.Width; + + if (currentWidth > availableWidth) + { + var measuredLength = TextFormatterImpl.MeasureText(currentRun, availableWidth); + + var currentBreakPosition = 0; + + if (measuredLength < textRange.End) + { + var lineBreaker = new LineBreakEnumerator(currentRun.Text); + + while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) + { + var nextBreakPosition = lineBreaker.Current.PositionWrap; + + if (nextBreakPosition == 0) + { + break; + } + + if (nextBreakPosition > measuredLength) + { + break; + } + + currentBreakPosition = nextBreakPosition; + } + } + + if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord) + { + measuredLength = currentBreakPosition; + } + + collapsedLength += measuredLength; + + var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength); + + var shapedTextCharacters = new List(splitResult.First.Count + 1); + + shapedTextCharacters.AddRange(splitResult.First); + + shapedTextCharacters.Add(shapedSymbol); + + textRange = new TextRange(textRange.Start, collapsedLength); + + var shapedWidth = GetShapedWidth(shapedTextCharacters); + + textLineMetrics = new TextLineMetrics(new Size(shapedWidth, LineMetrics.Size.Height), + LineMetrics.TextBaseline, textRange, false); + + return new TextLineImpl(shapedTextCharacters, textLineMetrics, LineBreak, true); + } + + availableWidth -= currentRun.GlyphRun.Bounds.Width; + + collapsedLength += currentRun.GlyphRun.Characters.Length; + + runIndex++; + } + + textLineMetrics = + new TextLineMetrics(LineMetrics.Size.WithWidth(LineMetrics.Size.Width + shapedSymbol.Bounds.Width), + LineMetrics.TextBaseline, TextRange, LineMetrics.HasOverflowed); + + return new TextLineImpl(new List(_textRuns) { shapedSymbol }, textLineMetrics, null, + true); + } + /// public override CharacterHit GetCharacterHitFromDistance(double distance) { @@ -230,5 +328,41 @@ namespace Avalonia.Media.TextFormatting return runIndex; } + + /// + /// Creates a shaped symbol. + /// + /// The symbol run to shape. + /// + /// The shaped symbol. + /// + internal static ShapedTextCharacters CreateShapedSymbol(TextRun textRun) + { + var formatterImpl = AvaloniaLocator.Current.GetService(); + + var glyphRun = formatterImpl.ShapeText(textRun.Text, textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize, + textRun.Properties.CultureInfo); + + return new ShapedTextCharacters(glyphRun, textRun.Properties); + } + + /// + /// Gets the shaped width of specified shaped text characters. + /// + /// The shaped text characters. + /// + /// The shaped width. + /// + private static double GetShapedWidth(IReadOnlyList shapedTextCharacters) + { + var shapedWidth = 0.0; + + for (var i = 0; i < shapedTextCharacters.Count; i++) + { + shapedWidth += shapedTextCharacters[i].Bounds.Width; + } + + return shapedWidth; + } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs index 2f7809ff35..6875cc1c04 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs @@ -9,11 +9,12 @@ namespace Avalonia.Media.TextFormatting /// public readonly struct TextLineMetrics { - public TextLineMetrics(Size size, double textBaseline, TextRange textRange) + public TextLineMetrics(Size size, double textBaseline, TextRange textRange, bool hasOverflowed) { Size = size; TextBaseline = textBaseline; TextRange = textRange; + HasOverflowed = hasOverflowed; } /// @@ -37,6 +38,12 @@ namespace Avalonia.Media.TextFormatting /// public double TextBaseline { get; } + /// + /// Gets a boolean value that indicates whether content of the line overflows + /// the specified paragraph width. + /// + public bool HasOverflowed { get; } + /// /// Creates the text line metrics. /// @@ -83,7 +90,7 @@ namespace Avalonia.Media.TextFormatting descent - ascent + lineGap : paragraphProperties.LineHeight); - return new TextLineMetrics(size, -ascent, textRange); + return new TextLineMetrics(size, -ascent, textRange, size.Width > paragraphWidth); } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs index 39eb695404..3ecd1aafd9 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs @@ -26,11 +26,6 @@ /// public abstract TextWrapping TextWrapping { get; } - /// - /// Gets the text trimming. - /// - public abstract TextTrimming TextTrimming { get; } - /// /// Paragraph's line height /// diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs new file mode 100644 index 0000000000..4bd46e8c75 --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs @@ -0,0 +1,33 @@ +using Avalonia.Utilities; + +namespace Avalonia.Media.TextFormatting +{ + /// + /// a collapsing properties to collapse whole line toward the end + /// at character granularity and with ellipsis being the collapsing symbol + /// + public class TextTrailingCharacterEllipsis : TextCollapsingProperties + { + private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' }); + + /// + /// Construct a text trailing character ellipsis collapsing properties + /// + /// width in which collapsing is constrained to + /// text run properties of ellispis symbol + public TextTrailingCharacterEllipsis(double width, TextRunProperties textRunProperties) + { + Width = width; + Symbol = new TextCharacters(s_ellipsis, textRunProperties); + } + + /// + public sealed override double Width { get; } + + /// + public sealed override TextRun Symbol { get; } + + /// + public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingCharacter; + } +} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs new file mode 100644 index 0000000000..9dffddd207 --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs @@ -0,0 +1,37 @@ +using Avalonia.Utilities; + +namespace Avalonia.Media.TextFormatting +{ + /// + /// a collapsing properties to collapse whole line toward the end + /// at word granularity and with ellipsis being the collapsing symbol + /// + public class TextTrailingWordEllipsis : TextCollapsingProperties + { + private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' }); + + /// + /// Construct a text trailing word ellipsis collapsing properties + /// + /// width in which collapsing is constrained to + /// text run properties of ellispis symbol + public TextTrailingWordEllipsis( + double width, + TextRunProperties textRunProperties + ) + { + Width = width; + Symbol = new TextCharacters(s_ellipsis, textRunProperties); + } + + + /// + public sealed override double Width { get; } + + /// + public sealed override TextRun Symbol { get; } + + /// + public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingWord; + } +} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs index 26f7721128..76bb9ac44f 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs @@ -109,7 +109,6 @@ namespace Avalonia.Media.TextFormatting.Unicode { case PairBreakType.DI: // Direct break shouldBreak = true; - _lastPos = _pos; break; case PairBreakType.IN: // possible indirect break diff --git a/src/Avalonia.Visuals/Media/TextWrapping.cs b/src/Avalonia.Visuals/Media/TextWrapping.cs index d649bda23f..b7915e5612 100644 --- a/src/Avalonia.Visuals/Media/TextWrapping.cs +++ b/src/Avalonia.Visuals/Media/TextWrapping.cs @@ -5,13 +5,6 @@ namespace Avalonia.Media /// public enum TextWrapping { - /// - /// Line-breaking occurs if the line overflows the available block width. - /// However, a line may overflow the block width if the line breaking algorithm - /// cannot determine a break opportunity, as in the case of a very long word. - /// - WrapWithOverflow, - /// /// Text should not wrap. /// @@ -20,6 +13,13 @@ namespace Avalonia.Media /// /// Text can wrap. /// - Wrap + Wrap, + + /// + /// Line-breaking occurs if the line overflows the available block width. + /// However, a line may overflow the block width if the line breaking algorithm + /// cannot determine a break opportunity, as in the case of a very long word. + /// + WrapWithOverflow } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 43a791b2cb..bf41381b52 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -490,10 +490,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } - [InlineData("0123456789\r0123456789", 2)] - [InlineData("0123456789", 1)] + [InlineData("0123456789\r0123456789")] + [InlineData("0123456789")] [Theory] - public void Should_Include_Last_Line_When_Constraint_Is_Surpassed(string text, int numberOfLines) + public void Should_Include_First_Line_When_Constraint_Is_Surpassed(string text) { using (Start()) { @@ -508,11 +508,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Typeface.Default, 12, Brushes.Black.ToImmutable(), - maxHeight: lineHeight * numberOfLines - lineHeight * 0.5); + maxHeight: lineHeight - lineHeight * 0.5); - Assert.Equal(numberOfLines, layout.TextLines.Count); + Assert.Equal(1, layout.TextLines.Count); - Assert.Equal(numberOfLines * lineHeight, layout.Size.Height); + Assert.Equal(lineHeight, layout.Size.Height); } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index ed00d6aaed..f0951c61d3 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -162,6 +162,64 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [InlineData("01234 01234", 8, TextCollapsingStyle.TrailingCharacter, "01234 0\u2026")] + [InlineData("01234 01234", 8, TextCollapsingStyle.TrailingWord, "01234 \u2026")] + [Theory] + public void Should_Collapse_Line(string text, int numberOfCharacters, TextCollapsingStyle style, string expected) + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + + var textSource = new SingleBufferTextSource(text, defaultProperties); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + Assert.False(textLine.HasCollapsed); + + var glyphTypeface = Typeface.Default.GlyphTypeface; + + var scale = defaultProperties.FontRenderingEmSize / glyphTypeface.DesignEmHeight; + + var width = 1.0; + + for (var i = 0; i < numberOfCharacters; i++) + { + var glyph = glyphTypeface.GetGlyph(text[i]); + + width += glyphTypeface.GetGlyphAdvance(glyph) * scale; + } + + TextCollapsingProperties collapsingProperties; + + if (style == TextCollapsingStyle.TrailingCharacter) + { + collapsingProperties = new TextTrailingCharacterEllipsis(width, defaultProperties); + } + else + { + collapsingProperties = new TextTrailingWordEllipsis(width, defaultProperties); + } + + var collapsedLine = textLine.Collapse(collapsingProperties); + + Assert.True(collapsedLine.HasCollapsed); + + var trimmedText = collapsedLine.TextRuns.SelectMany(x => x.Text).ToArray(); + + Assert.Equal(expected.Length, trimmedText.Length); + + for (var i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], trimmedText[i]); + } + } + } + private static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface From 62fd036d55e010fe6b59a35ae0178aab5247162f Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Wed, 15 Jul 2020 13:17:06 +0300 Subject: [PATCH 29/85] small adjustment to avoid IndexOutOfRangeException --- .../DateTimePickers/DatePicker.cs | 16 ++++++++++------ .../DateTimePickers/DatePickerPresenter.cs | 16 ++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index 5d3311e8c6..a41c159980 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -88,7 +88,7 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(SelectedDate), x => x.SelectedDate, (x, v) => x.SelectedDate = v); - //Template Items + // Template Items private Button _flyoutButton; private TextBlock _dayText; private TextBlock _monthText; @@ -359,10 +359,14 @@ namespace Avalonia.Controls } } - Grid.SetColumn(_spacer1, 1); - Grid.SetColumn(_spacer2, 3); - _spacer1.IsVisible = columnIndex > 1; - _spacer2.IsVisible = columnIndex > 2; + var isSpacer1Visible = columnIndex > 1; + var isSpacer2Visible = columnIndex > 2; + // ternary conditional operator is used to make sure grid cells will be validated + Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0); + Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0); + + _spacer1.IsVisible = isSpacer1Visible; + _spacer2.IsVisible = isSpacer2Visible; } private void SetSelectedDateText() @@ -398,7 +402,7 @@ namespace Avalonia.Controls var deltaY = _presenter.GetOffsetForPopup(); - //The extra 5 px I think is related to default popup placement behavior + // The extra 5 px I think is related to default popup placement behavior _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index 8b86e46e88..0da46bb74a 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -77,7 +77,7 @@ namespace Avalonia.Controls DatePicker.YearVisibleProperty.AddOwner(x => x.YearVisible, (x, v) => x.YearVisible = v); - //Template Items + // Template Items private Grid _pickerContainer; private Button _acceptButton; private Button _dismissButton; @@ -107,7 +107,7 @@ namespace Avalonia.Controls private bool _yearVisible = true; private DateTimeOffset _syncDate; - private GregorianCalendar _calendar; + private readonly GregorianCalendar _calendar; private bool _suppressUpdateSelection; public DatePickerPresenter() @@ -234,7 +234,7 @@ namespace Avalonia.Controls protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - //These are requirements, so throw if not found + // These are requirements, so throw if not found _pickerContainer = e.NameScope.Get("PickerContainer"); _monthHost = e.NameScope.Get("MonthHost"); _dayHost = e.NameScope.Get("DayHost"); @@ -326,7 +326,7 @@ namespace Avalonia.Controls /// private void InitPicker() { - //OnApplyTemplate must've been called before we can init here... + // OnApplyTemplate must've been called before we can init here... if (_pickerContainer == null) return; @@ -344,7 +344,7 @@ namespace Avalonia.Controls SetGrid(); - //Date should've been set when we reach this point + // Date should've been set when we reach this point var dt = Date; if (DayVisible) { @@ -433,12 +433,12 @@ namespace Avalonia.Controls } } - private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void OnDismissButtonClicked(object sender, RoutedEventArgs e) { OnDismiss(); } - private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void OnAcceptButtonClicked(object sender, RoutedEventArgs e) { Date = _syncDate; OnConfirmed(); @@ -471,7 +471,7 @@ namespace Avalonia.Controls _syncDate = newDate; - //We don't need to update the days if not displaying day, not february + // We don't need to update the days if not displaying day, not february if (!DayVisible || _syncDate.Month != 2) return; From de87609de1859e1c28fad65af4e233ff5a489bca Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 15 Jul 2020 13:32:06 -0300 Subject: [PATCH 30/85] add failing unit test. --- .../ToolTipTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index 34b37e7635..9d7bc6af74 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs @@ -30,6 +30,40 @@ namespace Avalonia.Controls.UnitTests Assert.False(ToolTip.GetIsOpen(control)); } + + [Fact] + public void Should_Close_When_Control_Detaches() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window(); + + var panel = new Panel(); + + var target = new Decorator() + { + [ToolTip.TipProperty] = "Tip", + [ToolTip.ShowDelayProperty] = 0 + }; + + panel.Children.Add(target); + + window.Content = panel; + + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + + Assert.True((target as IVisual).IsAttachedToVisualTree); + + _mouseHelper.Enter(target); + + Assert.True(ToolTip.GetIsOpen(target)); + + panel.Children.Remove(target); + + Assert.False(ToolTip.GetIsOpen(target)); + } + } [Fact] public void Should_Open_On_Pointer_Enter() From a542d8753de026bd786c914b571c2a680a2ce77c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 15 Jul 2020 13:33:20 -0300 Subject: [PATCH 31/85] ensure tooltips are closed when its parent detaches from visual tree. --- src/Avalonia.Controls/ToolTipService.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index d90729e8a5..569697304f 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -28,14 +28,22 @@ namespace Avalonia.Controls { control.PointerEnter -= ControlPointerEnter; control.PointerLeave -= ControlPointerLeave; + control.DetachedFromVisualTree -= ControlDetaching; } if (e.NewValue != null) { control.PointerEnter += ControlPointerEnter; control.PointerLeave += ControlPointerLeave; + control.DetachedFromVisualTree += ControlDetaching; } } + + private void ControlDetaching(object sender, VisualTreeAttachmentEventArgs e) + { + var control = (Control)sender; + Close(control); + } /// /// Called when the pointer enters a control with an attached tooltip. From 8916fda98db209ed511f3395547b9179c5764ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Onak?= Date: Wed, 15 Jul 2020 21:24:23 +0200 Subject: [PATCH 32/85] Force recalculation of column width after edit is completed --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index fe9656a00b..7e921944ea 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -3922,6 +3922,12 @@ namespace Avalonia.Controls dataGridCell: editingCell); EditingRow.InvalidateDesiredHeight(); + var column = editingCell.OwningColumn; + if (column.Width.IsSizeToCells || column.Width.IsAuto) + {// Invalidate desired width and force recalculation + column.SetWidthDesiredValue(0); + EditingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width); + } } // We're done, so raise the CellEditEnded event From 6e106fb9e7cdd810f7924e0bac04308cd6a803bc Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 15 Jul 2020 22:09:40 +0200 Subject: [PATCH 33/85] [Win32] only steal input focus if it's currently held by Popup's parent's child --- .../Interop/UnmanagedMethods.cs | 12 ++++++++ src/Windows/Avalonia.Win32/PopupImpl.cs | 29 ++++++++++++------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 392ca31282..b7c68c4b95 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1060,9 +1060,21 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool SetFocus(IntPtr hWnd); [DllImport("user32.dll")] + public static extern IntPtr GetFocus(); + [DllImport("user32.dll")] public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent); [DllImport("user32.dll")] public static extern IntPtr GetParent(IntPtr hWnd); + + public enum GetAncestorFlags + { + GA_PARENT = 1, + GA_ROOT = 2, + GA_ROOTOWNER = 3 + } + + [DllImport("user32.dll")] + public static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags gaFlags); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow); diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index cd25b32ed9..525e5e0d52 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -7,6 +7,7 @@ namespace Avalonia.Win32 { class PopupImpl : WindowImpl, IPopupImpl { + private readonly IWindowBaseImpl _parent; private bool _dropShadowHint = true; private Size? _maxAutoSize; @@ -19,18 +20,25 @@ namespace Avalonia.Win32 public override void Show() { UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate); - var parent = UnmanagedMethods.GetParent(Handle.Handle); - if (parent != IntPtr.Zero) - { - IntPtr nextParent = parent; - while (nextParent != IntPtr.Zero) - { - parent = nextParent; - nextParent = UnmanagedMethods.GetParent(parent); - } - UnmanagedMethods.SetFocus(parent); + // We need to steal focus if it's held by a child window of our toplevel window + var parent = _parent; + while(parent != null) + { + if(parent is PopupImpl pi) + parent = pi._parent; + else + break; } + + if(parent == null) + return; + + var focusOwner = UnmanagedMethods.GetFocus(); + if (focusOwner != IntPtr.Zero && + UnmanagedMethods.GetAncestor(focusOwner, UnmanagedMethods.GetAncestorFlags.GA_ROOT) + == parent.Handle.Handle) + UnmanagedMethods.SetFocus(parent.Handle.Handle); } protected override bool ShouldTakeFocusOnClick => false; @@ -118,6 +126,7 @@ namespace Avalonia.Win32 private PopupImpl(IWindowBaseImpl parent, bool dummy) : base() { + _parent = parent; PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); } From 1c099b6e8ca96b60ab8d7a2de4f81c3f79e547c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 15 Jul 2020 19:15:59 -0300 Subject: [PATCH 34/85] add failing unit tests for cases. --- .../RelativePanelTests.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 7b1f6d07b7..6e171a58e7 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -82,5 +82,55 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds); } + + [Fact] + public void LeftOf_Measures_Correctly() + { + var rect1 = new Rectangle { Height = 20, Width = 20 }; + var rect2 = new Rectangle { Height = 20, Width = 20 }; + + var target = new RelativePanel + { + VerticalAlignment = Layout.VerticalAlignment.Center, + HorizontalAlignment = Layout.HorizontalAlignment.Center, + Children = + { + rect1, rect2 + } + }; + + RelativePanel.SetLeftOf(rect2, rect1); + target.Measure(new Size(400, 400)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(20, 20), target.Bounds.Size); + Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); + Assert.Equal(new Rect(-20, 0, 20, 20), target.Children[1].Bounds); + } + + [Fact] + public void Above_Measures_Correctly() + { + var rect1 = new Rectangle { Height = 20, Width = 20 }; + var rect2 = new Rectangle { Height = 20, Width = 20 }; + + var target = new RelativePanel + { + VerticalAlignment = Layout.VerticalAlignment.Center, + HorizontalAlignment = Layout.HorizontalAlignment.Center, + Children = + { + rect1, rect2 + } + }; + + RelativePanel.SetAbove(rect2, rect1); + target.Measure(new Size(400, 400)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(20, 20), target.Bounds.Size); + Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); + Assert.Equal(new Rect(0, -20, 20, 20), target.Children[1].Bounds); + } } } From 3449784b09d529b950dd3ccd1560a08c5e9703de Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 15 Jul 2020 19:16:53 -0300 Subject: [PATCH 35/85] add fixes for relative panel --- src/Avalonia.Controls/RelativePanel.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index a3ad30db76..38b5d9807c 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -54,7 +54,7 @@ namespace Avalonia.Controls } _childGraph.Measure(availableSize); - _childGraph.Reset(); + _childGraph.Reset(false); var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN()); _childGraph.Reset(); _childGraph.Measure(boundingSize); @@ -119,17 +119,22 @@ namespace Avalonia.Controls public void Arrange(Size arrangeSize) => Element.Arrange(new Rect(Left, Top, Math.Max(arrangeSize.Width - Left - Right, 0), Math.Max(arrangeSize.Height - Top - Bottom, 0))); - public void Reset() + public void Reset(bool clearPos) { - Left = double.NaN; - Top = double.NaN; - Right = double.NaN; - Bottom = double.NaN; + if (clearPos) + { + Left = double.NaN; + Top = double.NaN; + Right = double.NaN; + Bottom = double.NaN; + } + Measured = false; } public Size GetBoundingSize() { + if (Left < 0 || Top < 0) return default; if (Measured) return BoundingSize; @@ -209,7 +214,7 @@ namespace Avalonia.Controls _nodeDic.Clear(); } - public void Reset() => _nodeDic.Values.Do(node => node.Reset()); + public void Reset(bool clearPos = true) => _nodeDic.Values.Do(node => node.Reset(clearPos)); public GraphNode? AddLink(GraphNode from, Layoutable? to) { From d7d63e26c4bc46278815ec56aad5795ed2fdb0d7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 15 Jul 2020 19:17:42 -0300 Subject: [PATCH 36/85] remove chinese comments. --- src/Avalonia.Controls/RelativePanel.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 38b5d9807c..27f13a3f57 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -260,28 +260,21 @@ namespace Avalonia.Controls foreach (var node in nodes) { - /* - * 该节点无任何依赖,所以从这里开始计算元素位置。 - * 因为无任何依赖,所以忽略同级元素 - */ if (!node.Measured && !node.OutgoingNodes.Any()) { MeasureChild(node); continue; } - - // 判断依赖元素是否全部排列完毕 + if (node.OutgoingNodes.All(item => item.Measured)) { MeasureChild(node); continue; } - - // 判断是否有循环 + if (!set.Add(node.Element)) throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); - - // 没有循环,且有依赖,则继续往下 + Measure(node.OutgoingNodes, set); if (!node.Measured) From 281e31a65ce7b65e7d6215b87eb302b5a69ed30b Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Wed, 15 Jul 2020 18:40:08 -0400 Subject: [PATCH 37/85] Add missing fluent resources for default styles on ControlCatalog --- samples/ControlCatalog/App.xaml.cs | 24 ++++++++++++++++++++---- samples/ControlCatalog/MainView.xaml | 1 - 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 0baa6589c6..3f1ec289d1 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -29,11 +29,19 @@ namespace ControlCatalog { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") }, new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") }, }; @@ -41,11 +49,19 @@ namespace ControlCatalog { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml") }, new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") }, }; diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index fa4fd7dd07..af95e3c356 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -2,7 +2,6 @@ xmlns:pages="clr-namespace:ControlCatalog.Pages" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.MainView" - Background="Black" Foreground="{DynamicResource ThemeForegroundBrush}" FontSize="{DynamicResource FontSizeNormal}"> From 7ad6171d13fadf1d9052135cd91eafe474047b8b Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Wed, 15 Jul 2020 19:09:47 -0400 Subject: [PATCH 38/85] RenderDemo: use FluentLight intead of DefaultLight --- samples/RenderDemo/App.xaml | 5 +- samples/RenderDemo/SideBar.xaml | 125 ++++++++++++++++---------------- 2 files changed, 66 insertions(+), 64 deletions(-) diff --git a/samples/RenderDemo/App.xaml b/samples/RenderDemo/App.xaml index ccc3f54cc0..61e4d2385b 100644 --- a/samples/RenderDemo/App.xaml +++ b/samples/RenderDemo/App.xaml @@ -3,8 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="RenderDemo.App"> - - + - \ No newline at end of file + diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml index 07fdb91a16..fd23067f61 100644 --- a/samples/RenderDemo/SideBar.xaml +++ b/samples/RenderDemo/SideBar.xaml @@ -1,65 +1,68 @@ - + - - - - - + + + + + + From 5bce867f5258fba927267c84a1c252053199d6e1 Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Thu, 16 Jul 2020 09:47:30 +0300 Subject: [PATCH 39/85] additional check for presenter --- .../DateTimePickers/DatePickerPresenter.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index 0da46bb74a..31527ccb16 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -348,8 +348,7 @@ namespace Avalonia.Controls var dt = Date; if (DayVisible) { - GregorianCalendar gc = new GregorianCalendar(); - var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month); + var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month); _daySelector.MaximumValue = maxDays; _daySelector.MinimumValue = 1; _daySelector.SelectedValue = dt.Day; @@ -407,10 +406,14 @@ namespace Avalonia.Controls } } - Grid.SetColumn(_spacer1, 1); - Grid.SetColumn(_spacer2, 3); - _spacer1.IsVisible = columnIndex > 1; - _spacer2.IsVisible = columnIndex > 2; + var isSpacer1Visible = columnIndex > 1; + var isSpacer2Visible = columnIndex > 2; + // ternary conditional operator is used to make sure grid cells will be validated + Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0); + Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0); + + _spacer1.IsVisible = isSpacer1Visible; + _spacer2.IsVisible = isSpacer2Visible; } private void SetInitialFocus() From 41831066c202a24331bde2acbe7740caa953e868 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 Jul 2020 10:30:41 +0200 Subject: [PATCH 40/85] Revert "Fix selection after deleting an item." --- samples/ControlCatalog/Pages/ListBoxPage.xaml | 8 +--- src/Avalonia.Controls/ItemsControl.cs | 11 +---- .../CarouselTests.cs | 3 -- .../ItemsControlTests.cs | 4 -- .../Primitives/SelectingItemsControlTests.cs | 44 ------------------- .../SelectingItemsControlTests_Multiple.cs | 4 -- 6 files changed, 3 insertions(+), 71 deletions(-) diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index f4d81418ac..47b4ce7151 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -10,13 +10,7 @@ HorizontalAlignment="Center" Spacing="16"> - + diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index da9f619932..6e0ad66699 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -70,6 +70,7 @@ namespace Avalonia.Controls public ItemsControl() { PseudoClasses.Add(":empty"); + SubscribeToItems(_items); } /// @@ -264,11 +265,6 @@ namespace Avalonia.Controls { } - protected override void OnInitialized() - { - SubscribeToItems(_items); - } - /// /// Handles directional navigation within the . /// @@ -334,10 +330,7 @@ namespace Avalonia.Controls Presenter.Items = newValue; } - if (IsInitialized) - { - SubscribeToItems(newValue); - } + SubscribeToItems(newValue); } /// diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs index c6ca0fb2bf..a292910fae 100644 --- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs @@ -4,7 +4,6 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.LogicalTree; -using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; @@ -156,7 +155,6 @@ namespace Avalonia.Controls.UnitTests IsVirtualized = false }; - var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -249,7 +247,6 @@ namespace Avalonia.Controls.UnitTests IsVirtualized = false }; - var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index faaa3ed063..684486cbae 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -131,7 +131,6 @@ namespace Avalonia.Controls.UnitTests var child = new Control(); var items = new AvaloniaList(child); - var root = new TestRoot(target); target.Template = GetTemplate(); target.Items = items; items.RemoveAt(0); @@ -284,7 +283,6 @@ namespace Avalonia.Controls.UnitTests var items = new AvaloniaList { "Foo" }; var called = false; - var root = new TestRoot(target); target.Template = GetTemplate(); target.Items = items; target.ApplyTemplate(); @@ -305,7 +303,6 @@ namespace Avalonia.Controls.UnitTests var items = new AvaloniaList { "Foo", "Bar" }; var called = false; - var root = new TestRoot(target); target.Template = GetTemplate(); target.Items = items; target.ApplyTemplate(); @@ -379,7 +376,6 @@ namespace Avalonia.Controls.UnitTests Items = new[] { 1, 2, 3 }, }; - var root = new TestRoot(target); Assert.DoesNotContain(":empty", target.Classes); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index e43e855ae0..fe9c7b1261 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -170,8 +170,6 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected }; - var root = new TestRoot(listBox); - listBox.BeginInit(); listBox.SelectedIndex = 1; @@ -482,7 +480,6 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), }; - var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); items.Add(new Item { IsSelected = true }); @@ -534,7 +531,6 @@ namespace Avalonia.Controls.UnitTests.Primitives }; target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); target.SelectedIndex = 1; Assert.Equal(items[1], target.SelectedItem); @@ -553,45 +549,6 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.NotNull(receivedArgs); Assert.Empty(receivedArgs.AddedItems); Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); - Assert.False(items.Single().IsSelected); - } - - [Fact] - public void Removing_Selected_Item_Should_Clear_Selection_With_BeginInit() - { - var items = new AvaloniaList - { - new Item(), - new Item(), - }; - - var target = new SelectingItemsControl(); - target.BeginInit(); - target.Items = items; - target.Template = Template(); - target.EndInit(); - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - target.SelectedIndex = 0; - - Assert.Equal(items[0], target.SelectedItem); - Assert.Equal(0, target.SelectedIndex); - - SelectionChangedEventArgs receivedArgs = null; - - target.SelectionChanged += (_, args) => receivedArgs = args; - - var removed = items[0]; - - items.RemoveAt(0); - - Assert.Null(target.SelectedItem); - Assert.Equal(-1, target.SelectedIndex); - Assert.NotNull(receivedArgs); - Assert.Empty(receivedArgs.AddedItems); - Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); - Assert.False(items.Single().IsSelected); } [Fact] @@ -922,7 +879,6 @@ namespace Avalonia.Controls.UnitTests.Primitives Items = items, }; - var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index e9ec8d114f..dcf25beb50 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -1014,7 +1014,6 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Multiple, }; - var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -1044,7 +1043,6 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Multiple, }; - var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -1078,7 +1076,6 @@ namespace Avalonia.Controls.UnitTests.Primitives SelectionMode = SelectionMode.Multiple, }; - var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); @@ -1202,7 +1199,6 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), }; - var root = new TestRoot(target); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); items.Add(new ItemContainer { IsSelected = true }); From bebe9ee3739364ed11ae4819acef07a9bb3890ac Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 17:25:15 +0200 Subject: [PATCH 41/85] Bind ListBox.SelectedItems again. Was removed accidentally. --- samples/ControlCatalog/Pages/ListBoxPage.xaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 47b4ce7151..f4d81418ac 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -10,7 +10,13 @@ HorizontalAlignment="Center" Spacing="16"> - + From 0301d80c302cde4a60aeec2b6816e91cc66fa8d0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 14 Jul 2020 22:04:47 +0200 Subject: [PATCH 42/85] Added failing test for removing selected item with BeginInit. --- .../Primitives/SelectingItemsControlTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index fe9c7b1261..9ef2750ff3 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -531,6 +531,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); target.SelectedIndex = 1; Assert.Equal(items[1], target.SelectedItem); @@ -549,6 +550,45 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.NotNull(receivedArgs); Assert.Empty(receivedArgs.AddedItems); Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); + Assert.False(items.Single().IsSelected); + } + + [Fact] + public void Removing_Selected_Item_Should_Clear_Selection_With_BeginInit() + { + var items = new AvaloniaList + { + new Item(), + new Item(), + }; + + var target = new SelectingItemsControl(); + target.BeginInit(); + target.Items = items; + target.Template = Template(); + target.EndInit(); + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectedIndex = 0; + + Assert.Equal(items[0], target.SelectedItem); + Assert.Equal(0, target.SelectedIndex); + + SelectionChangedEventArgs receivedArgs = null; + + target.SelectionChanged += (_, args) => receivedArgs = args; + + var removed = items[0]; + + items.RemoveAt(0); + + Assert.Null(target.SelectedItem); + Assert.Equal(-1, target.SelectedIndex); + Assert.NotNull(receivedArgs); + Assert.Empty(receivedArgs.AddedItems); + Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); + Assert.False(items.Single().IsSelected); } [Fact] From 125ae96859df3bf6e602bc9f85171234fdd16f55 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 Jul 2020 12:29:33 +0200 Subject: [PATCH 43/85] Handle selected items being removed. Fixes #4293. --- .../Primitives/SelectingItemsControl.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index c915dc70b6..59b7777b1b 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -692,14 +692,24 @@ namespace Avalonia.Controls.Primitives } } - foreach (var i in e.SelectedIndices) + if (e.SelectedIndices.Count > 0 || e.DeselectedIndices.Count > 0) { - Mark(i.GetAt(0), true); - } + foreach (var i in e.SelectedIndices) + { + Mark(i.GetAt(0), true); + } - foreach (var i in e.DeselectedIndices) + foreach (var i in e.DeselectedIndices) + { + Mark(i.GetAt(0), false); + } + } + else if (e.DeselectedItems.Count > 0) { - Mark(i.GetAt(0), false); + // (De)selected indices being empty means that a selected item was removed from + // the Items (it can't tell us the index of the item because the index is no longer + // valid). In this case, we just update the selection state of all containers. + UpdateContainerSelection(); } var newSelectedIndex = SelectedIndex; From e042266678779029d8660335bdb475e3b2207278 Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Thu, 16 Jul 2020 14:07:55 +0300 Subject: [PATCH 44/85] add some properties to `AffectsRender` list --- src/Avalonia.Controls/TickBar.cs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs index 22145d8742..6ea5277a55 100644 --- a/src/Avalonia.Controls/TickBar.cs +++ b/src/Avalonia.Controls/TickBar.cs @@ -39,11 +39,15 @@ namespace Avalonia.Controls { static TickBar() { - AffectsRender(ReservedSpaceProperty, + AffectsRender(FillProperty, + IsDirectionReversedProperty, + ReservedSpaceProperty, MaximumProperty, MinimumProperty, OrientationProperty, - TickFrequencyProperty); + PlacementProperty, + TickFrequencyProperty, + TicksProperty); } public TickBar() : base() @@ -137,7 +141,7 @@ namespace Avalonia.Controls /// /// The Ticks property contains collection of value of type Double which /// are the logical positions use to draw the ticks. - /// The property value is a . + /// The property value is a . /// public AvaloniaList Ticks { @@ -169,7 +173,6 @@ namespace Avalonia.Controls public static readonly StyledProperty PlacementProperty = AvaloniaProperty.Register(nameof(Placement), 0d); - /// /// Placement property specified how the Tick will be placed. /// This property affects the way ticks are drawn. @@ -189,7 +192,7 @@ namespace Avalonia.Controls /// /// TickBar will use ReservedSpaceProperty for left and right spacing (for horizontal orientation) or - /// tob and bottom spacing (for vertical orienation). + /// top and bottom spacing (for vertical orienation). /// The space on both sides of TickBar is half of specified ReservedSpace. /// This property has type of . /// @@ -201,7 +204,7 @@ namespace Avalonia.Controls /// /// Draw ticks. - /// Ticks can be draw in 8 diffrent ways depends on Placment property and IsDirectionReversed property. + /// Ticks can be draw in 8 different ways depends on Placement property and IsDirectionReversed property. /// /// This function also draw selection-tick(s) if IsSelectionRangeEnabled is 'true' and /// SelectionStart and SelectionEnd are valid. @@ -211,9 +214,7 @@ namespace Avalonia.Controls /// /// The secondary ticks (all other ticks, including selection-tics) height will be 75% of TickBar's render size. /// - /// Brush that use to fill ticks is specified by Shape.Fill property. - /// - /// Pen that use to draw ticks is specified by Shape.Pen property. + /// Brush that use to fill ticks is specified by Fill property. /// public override void Render(DrawingContext dc) { @@ -222,7 +223,6 @@ namespace Avalonia.Controls var tickLen = 0.0d; // Height for Primary Tick (for Mininum and Maximum value) var tickLen2 = 0.0d; // Height for Secondary Tick var logicalToPhysical = 1.0; - var progression = 1.0d; var startPoint = new Point(); var endPoint = new Point(); var rSpace = Orientation == Orientation.Horizontal ? ReservedSpace.Width : ReservedSpace.Height; @@ -242,7 +242,6 @@ namespace Avalonia.Controls startPoint = new Point(halfReservedSpace, size.Height); endPoint = new Point(halfReservedSpace + size.Width, size.Height); logicalToPhysical = size.Width / range; - progression = 1; break; case TickBarPlacement.Bottom: @@ -255,7 +254,6 @@ namespace Avalonia.Controls startPoint = new Point(halfReservedSpace, 0d); endPoint = new Point(halfReservedSpace + size.Width, 0d); logicalToPhysical = size.Width / range; - progression = 1; break; case TickBarPlacement.Left: @@ -269,7 +267,6 @@ namespace Avalonia.Controls startPoint = new Point(size.Width, size.Height + halfReservedSpace); endPoint = new Point(size.Width, halfReservedSpace); logicalToPhysical = size.Height / range * -1; - progression = -1; break; case TickBarPlacement.Right: @@ -282,7 +279,6 @@ namespace Avalonia.Controls startPoint = new Point(0d, size.Height + halfReservedSpace); endPoint = new Point(0d, halfReservedSpace); logicalToPhysical = size.Height / range * -1; - progression = -1; break; }; @@ -291,7 +287,6 @@ namespace Avalonia.Controls // Invert direciton of the ticks if (IsDirectionReversed) { - progression *= -progression; logicalToPhysical *= -1; // swap startPoint & endPoint From 453222d8de6d8ac74361e554dea20b97529369f2 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 16 Jul 2020 19:18:40 +0200 Subject: [PATCH 45/85] Fix some comments --- .../Media/TextFormatting/TextFormatterImpl.cs | 302 +++++++++--------- .../Media/TextFormatting/TextLayout.cs | 5 + .../Media/TextFormatting/TextLine.cs | 40 ++- .../Media/TextFormatting/TextLineImpl.cs | 3 +- .../Media/TextFormatting/TextRunProperties.cs | 7 +- 5 files changed, 184 insertions(+), 173 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 061949a5c9..9318fcc68e 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -41,6 +41,156 @@ namespace Avalonia.Media.TextFormatting return textLine; } + /// + /// Measures the number of characters that fits into available width. + /// + /// The text run. + /// The available width. + /// + internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth) + { + var glyphRun = textCharacters.GlyphRun; + + if (glyphRun.Bounds.Width < availableWidth) + { + return glyphRun.Characters.Length; + } + + var glyphCount = 0; + + var currentWidth = 0.0; + + if (glyphRun.GlyphAdvances.IsEmpty) + { + var glyphTypeface = glyphRun.GlyphTypeface; + + for (var i = 0; i < glyphRun.GlyphClusters.Length; i++) + { + var glyph = glyphRun.GlyphIndices[i]; + + var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; + + if (currentWidth + advance > availableWidth) + { + break; + } + + currentWidth += advance; + + glyphCount++; + } + } + else + { + foreach (var advance in glyphRun.GlyphAdvances) + { + if (currentWidth + advance > availableWidth) + { + break; + } + + currentWidth += advance; + + glyphCount++; + } + } + + if (glyphCount == glyphRun.GlyphIndices.Length) + { + return glyphRun.Characters.Length; + } + + if (glyphRun.GlyphClusters.IsEmpty) + { + return glyphCount; + } + + var firstCluster = glyphRun.GlyphClusters[0]; + + var lastCluster = glyphRun.GlyphClusters[glyphCount]; + + return lastCluster - firstCluster; + } + + /// + /// Split a sequence of runs into two segments at specified length. + /// + /// The text run's. + /// The length to split at. + /// The split text runs. + internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length) + { + var currentLength = 0; + + for (var i = 0; i < textRuns.Count; i++) + { + var currentRun = textRuns[i]; + + if (currentLength + currentRun.GlyphRun.Characters.Length < length) + { + currentLength += currentRun.GlyphRun.Characters.Length; + continue; + } + + var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i; + + var first = new ShapedTextCharacters[firstCount]; + + if (firstCount > 1) + { + for (var j = 0; j < i; j++) + { + first[j] = textRuns[j]; + } + } + + var secondCount = textRuns.Count - firstCount; + + if (currentLength + currentRun.GlyphRun.Characters.Length == length) + { + var second = new ShapedTextCharacters[secondCount]; + + var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0; + + if (secondCount > 0) + { + for (var j = 0; j < secondCount; j++) + { + second[j] = textRuns[i + j + offset]; + } + } + + first[i] = currentRun; + + return new SplitTextRunsResult(first, second); + } + else + { + secondCount++; + + var second = new ShapedTextCharacters[secondCount]; + + if (secondCount > 0) + { + for (var j = 1; j < secondCount; j++) + { + second[j] = textRuns[i + j]; + } + } + + var split = currentRun.Split(length - currentLength); + + first[i] = split.First; + + second[0] = split.Second; + + return new SplitTextRunsResult(first, second); + } + } + + return new SplitTextRunsResult(textRuns, null); + } + /// /// Fetches text runs. /// @@ -188,7 +338,7 @@ namespace Avalonia.Media.TextFormatting if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth) { - var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth); + var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth); var breakFound = false; @@ -256,77 +406,6 @@ namespace Avalonia.Media.TextFormatting TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)); } - /// - /// Measures the number of characters that fits into available width. - /// - /// The text run. - /// The available width. - /// - internal static int MeasureText(ShapedTextCharacters textCharacters, double availableWidth) - { - var glyphRun = textCharacters.GlyphRun; - - if (glyphRun.Bounds.Width < availableWidth) - { - return glyphRun.Characters.Length; - } - - var glyphCount = 0; - - var currentWidth = 0.0; - - if (glyphRun.GlyphAdvances.IsEmpty) - { - var glyphTypeface = glyphRun.GlyphTypeface; - - for (var i = 0; i < glyphRun.GlyphClusters.Length; i++) - { - var glyph = glyphRun.GlyphIndices[i]; - - var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; - - if (currentWidth + advance > availableWidth) - { - break; - } - - currentWidth += advance; - - glyphCount++; - } - } - else - { - foreach (var advance in glyphRun.GlyphAdvances) - { - if (currentWidth + advance > availableWidth) - { - break; - } - - currentWidth += advance; - - glyphCount++; - } - } - - if (glyphCount == glyphRun.GlyphIndices.Length) - { - return glyphRun.Characters.Length; - } - - if (glyphRun.GlyphClusters.IsEmpty) - { - return glyphCount; - } - - var firstCluster = glyphRun.GlyphClusters[0]; - - var lastCluster = glyphRun.GlyphClusters[glyphCount]; - - return lastCluster - firstCluster; - } - /// /// Gets the text range that is covered by the text runs. /// @@ -353,85 +432,6 @@ namespace Avalonia.Media.TextFormatting return new TextRange(start, end - start); } - /// - /// Split a sequence of runs into two segments at specified length. - /// - /// The text run's. - /// The length to split at. - /// The split text runs. - internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length) - { - var currentLength = 0; - - for (var i = 0; i < textRuns.Count; i++) - { - var currentRun = textRuns[i]; - - if (currentLength + currentRun.GlyphRun.Characters.Length < length) - { - currentLength += currentRun.GlyphRun.Characters.Length; - continue; - } - - var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i; - - var first = new ShapedTextCharacters[firstCount]; - - if (firstCount > 1) - { - for (var j = 0; j < i; j++) - { - first[j] = textRuns[j]; - } - } - - var secondCount = textRuns.Count - firstCount; - - if (currentLength + currentRun.GlyphRun.Characters.Length == length) - { - var second = new ShapedTextCharacters[secondCount]; - - var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0; - - if (secondCount > 0) - { - for (var j = 0; j < secondCount; j++) - { - second[j] = textRuns[i + j + offset]; - } - } - - first[i] = currentRun; - - return new SplitTextRunsResult(first, second); - } - else - { - secondCount++; - - var second = new ShapedTextCharacters[secondCount]; - - if (secondCount > 0) - { - for (var j = 1; j < secondCount; j++) - { - second[j] = textRuns[i + j]; - } - } - - var split = currentRun.Split(length - currentLength); - - first[i] = split.First; - - second[0] = split.Second; - - return new SplitTextRunsResult(first, second); - } - } - - return new SplitTextRunsResult(textRuns, null); - } - internal readonly struct SplitTextRunsResult { public SplitTextRunsResult(IReadOnlyList first, IReadOnlyList second) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index 92db6b69c4..14602a2560 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -268,6 +268,11 @@ namespace Avalonia.Media.TextFormatting } } + /// + /// Gets the for current text trimming mode. + /// + /// The collapsing width. + /// The . private TextCollapsingProperties GetCollapsingProperties(double width) { return _textTrimming switch diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs index 3e3258f38a..423ca9fb7f 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs @@ -40,8 +40,11 @@ namespace Avalonia.Media.TextFormatting public abstract TextLineBreak LineBreak { get; } /// - /// Client to get a boolean value indicates whether a line has been collapsed + /// Gets a value that indicates whether the line is collapsed. /// + /// + /// true, if the line is collapsed; otherwise, false. + /// public abstract bool HasCollapsed { get; } /// @@ -52,46 +55,49 @@ namespace Avalonia.Media.TextFormatting public abstract void Draw(DrawingContext drawingContext, Point origin); /// - /// Client to collapse the line and get a collapsed line that fits for display + /// Create a collapsed line based on collapsed text properties. /// - /// a list of collapsing properties + /// A list of + /// objects that represent the collapsed text properties. + /// + /// A value that represents a collapsed line that can be displayed. + /// public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList); /// - /// Client to get the character hit corresponding to the specified - /// distance from the beginning of the line. + /// Gets the character hit corresponding to the specified distance from the beginning of the line. /// - /// distance in text flow direction from the beginning of the line - /// The + /// A value that represents the distance from the beginning of the line. + /// The object at the specified distance from the beginning of the line. public abstract CharacterHit GetCharacterHitFromDistance(double distance); /// - /// Client to get the distance from the beginning of the line from the specified + /// Gets the distance from the beginning of the line to the specified character hit. /// . /// - /// of the character to query the distance. - /// Distance in text flow direction from the beginning of the line. + /// The object whose distance you want to query. + /// A that represents the distance from the beginning of the line. public abstract double GetDistanceFromCharacterHit(CharacterHit characterHit); /// - /// Client to get the next for caret navigation. + /// Gets the next character hit for caret navigation. /// /// The current . /// The next . public abstract CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit); /// - /// Client to get the previous character hit for caret navigation + /// Gets the previous character hit for caret navigation. /// - /// the current character hit - /// The previous + /// The current . + /// The previous . public abstract CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit); /// - /// Client to get the previous character hit after backspacing + /// Gets the previous character hit after backspacing. /// - /// the current character hit - /// The after backspacing + /// The current . + /// The after backspacing. public abstract CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit); /// diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 820c943aea..980b1a2d40 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -47,6 +47,7 @@ namespace Avalonia.Media.TextFormatting } } + /// public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList) { if (collapsingPropertiesList == null || collapsingPropertiesList.Length == 0) @@ -73,7 +74,7 @@ namespace Avalonia.Media.TextFormatting if (currentWidth > availableWidth) { - var measuredLength = TextFormatterImpl.MeasureText(currentRun, availableWidth); + var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth); var currentBreakPosition = 0; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs index bbcdfe2d8e..c4f9443c3d 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs @@ -4,12 +4,11 @@ using System.Globalization; namespace Avalonia.Media.TextFormatting { /// - /// Properties that can change from one run to the next, such as typeface or foreground brush. + /// Provides a set of properties, such as typeface or foreground brush, that can be applied to a TextRun object. This is an abstract class. /// /// - /// The client provides a concrete implementation of this abstract run properties class. This - /// allows client to implement their run properties the way that fits with their run formatting - /// store. + /// The text layout client provides a concrete implementation of this abstract class. + /// This enables the client to implement text run properties in a way that corresponds with the associated formatting store. /// public abstract class TextRunProperties : IEquatable { From 856118f21c6ca84a93bd293f0dba14208084a0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Fri, 17 Jul 2020 06:57:54 +0100 Subject: [PATCH 46/85] Use TextBox text alignment and wrapping in watermark. --- src/Avalonia.Themes.Default/TextBox.xaml | 2 ++ src/Avalonia.Themes.Fluent/TextBox.xaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 4fb3653e89..6a746dda30 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -40,6 +40,8 @@ Date: Fri, 17 Jul 2020 16:33:31 +0300 Subject: [PATCH 47/85] Extracted runtime XAML loader to a separate package --- .gitmodules | 2 +- Avalonia.sln | 27 ++++++ Directory.Build.props | 1 + .../Avalonia.Build.Tasks.csproj | 10 +-- .../DesignWindowLoader.cs | 7 +- .../Remote/FileWatcherTransport.cs | 10 ++- .../HtmlTransport/webapp/package-lock.json | 28 +++++-- .../Remote/RemoteDesignerEntryPoint.cs | 8 +- .../Avalonia.Markup.Xaml.Loader.csproj | 14 ++++ .../AvaloniaRuntimeXamlLoader.cs | 42 ++++++++++ .../AvaloniaXamlIlRuntimeCompiler.cs | 0 .../AvaloniaXamlIlCompiler.cs | 0 .../AvaloniaXamlIlCompilerConfiguration.cs | 0 .../AvaloniaXamlIlLanguage.cs | 0 .../Transformers/AddNameScopeRegistration.cs | 0 .../AvaloniaBindingExtensionTransformer.cs | 0 .../AvaloniaXamlIlAvaloniaPropertyResolver.cs | 0 .../AvaloniaXamlIlBindingPathParser.cs | 0 .../AvaloniaXamlIlBindingPathTransformer.cs | 0 ...iaXamlIlCompiledBindingsMetadataRemover.cs | 0 ...IlConstructorServiceProviderTransformer.cs | 0 ...olTemplateTargetTypeMetadataTransformer.cs | 0 ...valoniaXamlIlDataContextTypeTransformer.cs | 0 ...aloniaXamlIlDesignPropertiesTransformer.cs | 0 .../AvaloniaXamlIlMetadataRemover.cs | 0 .../AvaloniaXamlIlPropertyPathTransformer.cs | 0 ...lIlResolveByNameMarkupExtensionReplacer.cs | 0 ...valoniaXamlIlRootObjectScopeTransformer.cs | 0 .../AvaloniaXamlIlSelectorTransformer.cs | 0 .../AvaloniaXamlIlSetterTransformer.cs | 0 ...mlIlTransformInstanceAttachedProperties.cs | 0 ...ransformSyntheticCompiledBindingMembers.cs | 0 ...amlIlTransitionsTypeMetadataTransformer.cs | 0 .../AvaloniaXamlIlWellKnownTypes.cs | 0 .../IgnoredDirectivesTransformer.cs | 0 .../Transformers/XNameTransformer.cs | 0 .../XamlIlAvaloniaPropertyHelper.cs | 0 .../XamlIlBindingPathHelper.cs | 0 .../XamlIlClrPropertyInfoHelper.cs | 0 ...amlIlPropertyInfoAccessorFactoryEmitter.cs | 0 .../IncludeXamlIlSre.props | 12 +++ .../xamlil.github | 0 .../Avalonia.Markup.Xaml.csproj | 36 -------- .../AvaloniaXamlLoader.cs | 52 +----------- .../MarkupExtensions/ResourceInclude.cs | 3 +- .../Styling/StyleInclude.cs | 3 +- .../Avalonia.Designer.HostApp.csproj | 6 +- .../DesignXamlLoader.cs | 16 ++++ .../Avalonia.Designer.HostApp/Program.cs | 4 +- .../Avalonia.Controls.UnitTests.csproj | 1 + .../ContextMenuTests.cs | 6 +- .../TabControlTests.cs | 4 +- .../Avalonia.Markup.Xaml.UnitTests.csproj | 1 + .../Converters/ConverterTests.cs | 2 +- .../Converters/MultiValueConverterTests.cs | 3 +- .../Converters/NullableConverterTests.cs | 3 +- .../PointsListTypeConverterTests.cs | 3 +- .../Converters/ValueConverterTests.cs | 3 +- .../Data/BindingTests.cs | 9 +- .../Data/BindingTests_Method.cs | 9 +- .../Data/BindingTests_TemplatedParent.cs | 3 +- .../MarkupExtensions/BindingExtensionTests.cs | 9 +- .../CompiledBindingExtensionTests.cs | 69 +++++---------- .../DynamicResourceExtensionTests.cs | 78 ++++++----------- .../MarkupExtensions/ResourceIncludeTests.cs | 6 +- .../StaticResourceExtensionTests.cs | 57 +++++-------- .../StyleIncludeTests.cs | 3 +- .../StyleTests.cs | 3 +- .../Xaml/BasicTests.cs | 83 +++++++++---------- .../Xaml/BindingTests.cs | 47 ++++------- .../Xaml/BindingTests_RelativeSource.cs | 42 ++++------ .../Xaml/ControlBindingTests.cs | 9 +- .../Xaml/DataTemplateTests.cs | 9 +- .../Xaml/EventTests.cs | 9 +- .../Xaml/ResourceDictionaryTests.cs | 6 +- .../Xaml/StyleTests.cs | 44 ++++------ .../Xaml/TreeDataTemplateTests.cs | 3 +- .../Xaml/XamlIlTests.cs | 32 +++---- .../Avalonia.ReactiveUI.UnitTests.csproj | 3 +- .../AvaloniaActivationForViewFetcherTest.cs | 6 +- .../Avalonia.Skia.UnitTests.csproj | 1 + tests/Avalonia.UnitTests/TestServices.cs | 3 +- 82 files changed, 386 insertions(+), 464 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/AvaloniaXamlIlRuntimeCompiler.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/AvaloniaXamlIlCompiler.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/AvaloniaXamlIlLanguage.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AddNameScopeRegistration.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/Transformers/XNameTransformer.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/XamlIlBindingPathHelper.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs (100%) rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs (100%) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props rename src/Markup/{Avalonia.Markup.Xaml/XamlIl => Avalonia.Markup.Xaml.Loader}/xamlil.github (100%) create mode 100644 src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs diff --git a/.gitmodules b/.gitmodules index 9dbc50ef61..2d11fdfa9e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,5 +2,5 @@ path = nukebuild/Numerge url = https://github.com/kekekeks/Numerge.git [submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"] - path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github + path = src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github url = https://github.com/kekekeks/XamlX.git diff --git a/Avalonia.sln b/Avalonia.sln index 4ab647a25e..4954260e12 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -211,6 +211,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless", "src\Av EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -1998,6 +2000,30 @@ Global {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhone.Build.0 = Release|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.ActiveCfg = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.Build.0 = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.ActiveCfg = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.Build.0 = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2056,6 +2082,7 @@ Global {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} + {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/Directory.Build.props b/Directory.Build.props index 1f26df9bbc..b41f8c488e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,6 @@ $(MSBuildThisFileDirectory)build-intermediate/nuget + $(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\netcoreapp2.0\Avalonia.Designer.HostApp.dll diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 7117fd51a2..490364c0d8 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -17,14 +17,14 @@ Shared/AvaloniaResourceXamlInfo.cs - + XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension) - + XamlIl/%(RecursiveDir)%(FileName)%(Extension) - + XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension) @@ -57,8 +57,8 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) - - + + diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index f3bb0edce5..d7c6cc3693 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -12,13 +12,18 @@ namespace Avalonia.DesignerSupport { public class DesignWindowLoader { + public interface IDesignXamlLoader + { + object Load(MemoryStream stream, Assembly localAsm, object o, Uri baseUri); + } + public static Window LoadDesignerWindow(string xaml, string assemblyPath, string xamlFileProjectPath) { Window window; Control control; using (PlatformManager.DesignerMode()) { - var loader = new AvaloniaXamlLoader() {IsDesignMode = true}; + var loader = AvaloniaLocator.Current.GetService(); var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); diff --git a/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs b/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs index 0cb71dd217..0448a5c05d 100644 --- a/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs @@ -9,12 +9,14 @@ namespace Avalonia.DesignerSupport.Remote { class FileWatcherTransport : IAvaloniaRemoteTransportConnection, ITransportWithEnforcedMethod { + private readonly string _appPath; private string _path; private string _lastContents; private bool _disposed; - public FileWatcherTransport(Uri file) + public FileWatcherTransport(Uri file, string appPath) { + _appPath = appPath; _path = file.LocalPath; } @@ -73,7 +75,11 @@ namespace Avalonia.DesignerSupport.Remote { Console.WriteLine("Triggering XAML update"); _lastContents = data; - _onMessage?.Invoke(this, new UpdateXamlMessage { Xaml = data }); + _onMessage?.Invoke(this, new UpdateXamlMessage + { + Xaml = data, + AssemblyPath = _appPath + }); } await Task.Delay(100); diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json index 87536c670f..028027a974 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json @@ -3564,12 +3564,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3584,17 +3586,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3711,7 +3716,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3723,6 +3729,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3737,6 +3744,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3744,12 +3752,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3768,6 +3778,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3848,7 +3859,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3860,6 +3872,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3981,6 +3994,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index e61fe82c41..3e26ded22d 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -112,8 +112,9 @@ namespace Avalonia.DesignerSupport.Remote return rv; } - static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport) + static IAvaloniaRemoteTransportConnection CreateTransport(CommandLineArgs args) { + var transport = args.Transport; if (transport.Scheme == "tcp-bson") { return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result; @@ -121,7 +122,7 @@ namespace Avalonia.DesignerSupport.Remote if (transport.Scheme == "file") { - return new FileWatcherTransport(transport); + return new FileWatcherTransport(transport, args.AppPath); } PrintUsage(); return null; @@ -160,7 +161,7 @@ namespace Avalonia.DesignerSupport.Remote public static void Main(string[] cmdline) { var args = ParseCommandLineArgs(cmdline); - var transport = CreateTransport(args.Transport); + var transport = CreateTransport(args); if (transport is ITransportWithEnforcedMethod enforcedMethod) args.Method = enforcedMethod.PreviewerMethod; var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath)); @@ -234,6 +235,7 @@ namespace Avalonia.DesignerSupport.Remote } catch (Exception e) { + Console.Error.WriteLine(e.ToString()); s_transport.Send(new UpdateXamlResultMessage { Error = e.ToString(), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj new file mode 100644 index 0000000000..768545eb7e --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + true + + + + + + + + + diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs new file mode 100644 index 0000000000..4569970d01 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using System.Reflection; +using System.Text; +using Avalonia.Markup.Xaml.XamlIl; +// ReSharper disable CheckNamespace + +namespace Avalonia.Markup.Xaml +{ + public static class AvaloniaRuntimeXamlLoader + { + /// + /// Loads XAML from a string. + /// + /// The string containing the XAML. + /// Default assembly for clr-namespace: + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public static object Load(string xaml, Assembly localAssembly = null, object rootInstance = null, Uri uri = null, bool designMode = false) + { + Contract.Requires(xaml != null); + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) + { + return Load(stream, localAssembly, rootInstance, uri, designMode); + } + } + + public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null, + bool designMode = false) + => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode); + + public static object Parse(string xaml, Assembly localAssembly = null) + => Load(xaml, localAssembly); + + public static T Parse(string xaml, Assembly localAssembly = null) + => (T)Parse(xaml, localAssembly); + + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XNameTransformer.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XNameTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props b/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props new file mode 100644 index 0000000000..c902fa956a --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github similarity index 100% rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github rename to src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 3979312ce0..24428253aa 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -45,44 +45,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -96,8 +62,6 @@ - - diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 5c21037924..0e81ca2be8 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -10,10 +10,8 @@ namespace Avalonia.Markup.Xaml /// /// Loads XAML for a avalonia application. /// - public class AvaloniaXamlLoader + public static class AvaloniaXamlLoader { - public bool IsDesignMode { get; set; } - /// /// Loads the XAML into a Avalonia component. /// @@ -32,7 +30,7 @@ namespace Avalonia.Markup.Xaml /// A base URI to use if is relative. /// /// The loaded object. - public object Load(Uri uri, Uri baseUri = null) + public static object Load(Uri uri, Uri baseUri = null) { Contract.Requires(uri != null); @@ -56,51 +54,9 @@ namespace Avalonia.Markup.Xaml return compiledResult; } - - var asset = assetLocator.OpenAndGetAssembly(uri, baseUri); - using (var stream = asset.stream) - { - var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri); - return Load(stream, asset.assembly, null, absoluteUri); - } + throw new XamlLoadException( + $"No precompiled XAML found for {uri} (baseUri: {baseUri}), make sure to specify x:Class and include your XAML file as AvaloniaResource"); } - /// - /// Loads XAML from a string. - /// - /// The string containing the XAML. - /// Default assembly for clr-namespace: - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(string xaml, Assembly localAssembly = null, object rootInstance = null) - { - Contract.Requires(xaml != null); - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) - { - return Load(stream, localAssembly, rootInstance); - } - } - - /// - /// Loads XAML from a stream. - /// - /// The stream containing the XAML. - /// Default assembly for clr-namespace - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The URI of the XAML - /// The loaded object. - public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null) - => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, IsDesignMode); - - public static object Parse(string xaml, Assembly localAssembly = null) - => new AvaloniaXamlLoader().Load(xaml, localAssembly); - - public static T Parse(string xaml, Assembly localAssembly = null) - => (T)Parse(xaml, localAssembly); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs index 0cedf4f364..b6137aa89f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs @@ -25,8 +25,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions if (_loaded == null) { _isLoading = true; - var loader = new AvaloniaXamlLoader(); - _loaded = (IResourceDictionary)loader.Load(Source, _baseUri); + _loaded = (IResourceDictionary)AvaloniaXamlLoader.Load(Source, _baseUri); _isLoading = false; } diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index ea9042f779..607b552c28 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -51,8 +51,7 @@ namespace Avalonia.Markup.Xaml.Styling if (_loaded == null) { _isLoading = true; - var loader = new AvaloniaXamlLoader(); - var loaded = (IStyle)loader.Load(Source, _baseUri); + var loaded = (IStyle)AvaloniaXamlLoader.Load(Source, _baseUri); _loaded = new[] { loaded }; _isLoading = false; } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 1c7077870a..aa40f1d75a 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -18,6 +18,10 @@ - + + + + + diff --git a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs new file mode 100644 index 0000000000..b873027765 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs @@ -0,0 +1,16 @@ +using System; +using System.IO; +using System.Reflection; +using Avalonia.DesignerSupport; +using Avalonia.Markup.Xaml.XamlIl; + +namespace Avalonia.Designer.HostApp +{ + class DesignXamlLoader : DesignWindowLoader.IDesignXamlLoader + { + public object Load(MemoryStream stream, Assembly localAsm, object o, Uri baseUri) + { + return AvaloniaXamlIlRuntimeCompiler.Load(stream, localAsm, o, baseUri, true); + } + } +} diff --git a/src/tools/Avalonia.Designer.HostApp/Program.cs b/src/tools/Avalonia.Designer.HostApp/Program.cs index 3163e1fbc3..7469f946a4 100644 --- a/src/tools/Avalonia.Designer.HostApp/Program.cs +++ b/src/tools/Avalonia.Designer.HostApp/Program.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Reflection; +using Avalonia.DesignerSupport; namespace Avalonia.Designer.HostApp { @@ -40,8 +41,9 @@ namespace Avalonia.Designer.HostApp public static void Main(string[] args) #endif { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new DesignXamlLoader()); Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); } - } } diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index 2ca93dcf56..19c4454d3d 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -13,6 +13,7 @@ + diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 9a81d19bb9..cf8f7c266a 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -191,8 +191,7 @@ namespace Avalonia.Controls.UnitTests "; - var loader = new AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var target1 = window.Find("target1"); var target2 = window.Find("target2"); var mouse = new MouseTestHelper(); @@ -235,8 +234,7 @@ namespace Avalonia.Controls.UnitTests "; - var loader = new AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var target1 = window.Find("target1"); var target2 = window.Find("target2"); var mouse = new MouseTestHelper(); diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index db9211ac3c..fd52aeb9af 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Controls.Utils; using Avalonia.LogicalTree; +using Avalonia.Markup.Xaml; using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -338,8 +339,7 @@ namespace Avalonia.Controls.UnitTests xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'> "; - var loader = new Markup.Xaml.AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var tabControl = window.FindControl("tabs"); tabControl.DataContext = new { Tabs = new List() }; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index e8c4daa7bc..ad3592294d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -11,6 +11,7 @@ + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs index b424003ed6..c9420f1696 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs @@ -9,7 +9,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters public void Bug_2228_Relative_Uris_Should_Be_Correctly_Parsed() { var testClass = typeof(TestClassWithUri); - var parsed = AvaloniaXamlLoader.Parse( + var parsed = AvaloniaRuntimeXamlLoader.Parse( $"<{testClass.Name} xmlns='clr-namespace:{testClass.Namespace}' Uri='/test'/>", testClass.Assembly); Assert.False(parsed.Uri.IsAbsoluteUri); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs index a77723afe1..466ae1bf7c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs @@ -29,8 +29,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters "; - var loader = new AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); window.ApplyTemplate(); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs index cdd40ed80f..eb8851c80b 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs @@ -22,8 +22,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters xmlns='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Converters' Thickness = '5' Orientation='Vertical' >"; - var loader = new AvaloniaXamlLoader(); - var data = (ClassWithNullableProperties)loader.Load(xaml, typeof(ClassWithNullableProperties).Assembly); + var data = (ClassWithNullableProperties)AvaloniaRuntimeXamlLoader.Load(xaml, typeof(ClassWithNullableProperties).Assembly); Assert.Equal(new Thickness(5), data.Thickness); Assert.Equal(Orientation.Vertical, data.Orientation); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs index b060905f38..3b729e9cd8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs @@ -31,8 +31,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters public void Should_Parse_Points_in_Xaml(string input) { var xaml = $""; - var loader = new AvaloniaXamlLoader(); - var polygon = (Polygon)loader.Load(xaml); + var polygon = (Polygon)AvaloniaRuntimeXamlLoader.Load(xaml); var points = polygon.Points; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ValueConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ValueConverterTests.cs index 5e698117c3..4d5983e276 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ValueConverterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ValueConverterTests.cs @@ -21,8 +21,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters xmlns:c='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Converters;assembly=Avalonia.Markup.Xaml.UnitTests'> "; - var loader = new AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); window.ApplyTemplate(); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs index 6730e3134d..afc4a36fea 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -24,8 +24,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'> "; - var loader = new AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); window.DataContext = "foo"; @@ -45,8 +44,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'> "; - var loader = new AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); window.ApplyTemplate(); @@ -86,8 +84,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data "; - var loader = new AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBox = window.FindControl("textBox"); window.ApplyTemplate(); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs index b2b4c4da1a..a7a004bd49 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs @@ -20,8 +20,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'> "; - var loader = new AvaloniaXamlLoader(); - var window = (Window)loader.Load(xaml); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var button = window.FindControl "; - var control = AvaloniaXamlLoader.Parse(xaml); + var control = AvaloniaRuntimeXamlLoader.Parse(xaml); var button = control.FindControl "; - var control = AvaloniaXamlLoader.Parse(xaml); + var control = AvaloniaRuntimeXamlLoader.Parse(xaml); var button = control.FindControl public static class AvaloniaXamlLoader { + public interface IRuntimeXamlLoader + { + object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode); + } + /// /// Loads the XAML into a Avalonia component. /// @@ -53,7 +58,19 @@ namespace Avalonia.Markup.Xaml if (compiledResult != null) return compiledResult; } - + + // This is intended for unit-tests only + var runtimeLoader = AvaloniaLocator.Current.GetService(); + if (runtimeLoader != null) + { + var asset = assetLocator.OpenAndGetAssembly(uri, baseUri); + using (var stream = asset.stream) + { + var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri); + return runtimeLoader.Load(stream, asset.assembly, null, absoluteUri, false); + } + } + throw new XamlLoadException( $"No precompiled XAML found for {uri} (baseUri: {baseUri}), make sure to specify x:Class and include your XAML file as AvaloniaResource"); } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index aa40f1d75a..51d18e55d1 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs index b873027765..7af29a56a1 100644 --- a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs +++ b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs @@ -1,16 +1,16 @@ using System; using System.IO; using System.Reflection; -using Avalonia.DesignerSupport; +using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.XamlIl; namespace Avalonia.Designer.HostApp { - class DesignXamlLoader : DesignWindowLoader.IDesignXamlLoader + class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader { - public object Load(MemoryStream stream, Assembly localAsm, object o, Uri baseUri) + public object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode) { - return AvaloniaXamlIlRuntimeCompiler.Load(stream, localAsm, o, baseUri, true); + return AvaloniaXamlIlRuntimeCompiler.Load(stream, localAsm, o, baseUri, designMode); } } } diff --git a/src/tools/Avalonia.Designer.HostApp/Program.cs b/src/tools/Avalonia.Designer.HostApp/Program.cs index 7469f946a4..4472dac4e3 100644 --- a/src/tools/Avalonia.Designer.HostApp/Program.cs +++ b/src/tools/Avalonia.Designer.HostApp/Program.cs @@ -2,6 +2,7 @@ using System.IO; using System.Reflection; using Avalonia.DesignerSupport; +using Avalonia.Markup.Xaml; namespace Avalonia.Designer.HostApp { @@ -41,7 +42,7 @@ namespace Avalonia.Designer.HostApp public static void Main(string[] args) #endif { - AvaloniaLocator.CurrentMutable.Bind() + AvaloniaLocator.CurrentMutable.Bind() .ToConstant(new DesignXamlLoader()); Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs index d9505ba3ed..54e89ae37e 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs @@ -7,7 +7,7 @@ using Xunit; namespace Avalonia.Markup.Xaml.UnitTests.MakrupExtensions { - public class ResourceIncludeTests + public class ResourceIncludeTests : XamlTestBase { public class StaticResourceExtensionTests : XamlTestBase { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs index 5172b2e830..2bc82d1353 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Reflection; using System.Text; using Avalonia.Data; @@ -11,6 +13,15 @@ namespace Avalonia.Markup.Xaml.UnitTests { // Ensure necessary assemblies are loaded. var _ = typeof(TemplateBinding); + if (AvaloniaLocator.Current.GetService() == null) + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestXamlLoaderShim()); + } + + class TestXamlLoaderShim : AvaloniaXamlLoader.IRuntimeXamlLoader + { + public object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode) + => AvaloniaRuntimeXamlLoader.Load(stream, localAsm, o, baseUri, designMode); } } } From 835aed07a5155d111d9bf9d9fd1e005784016f11 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 17 Jul 2020 17:13:24 +0300 Subject: [PATCH 51/85] Whoopsie --- src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index 3e26ded22d..764fc7b332 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -235,7 +235,6 @@ namespace Avalonia.DesignerSupport.Remote } catch (Exception e) { - Console.Error.WriteLine(e.ToString()); s_transport.Send(new UpdateXamlResultMessage { Error = e.ToString(), From a6c0968218f21302011d4959fda0c4a05d124cdc Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 17 Jul 2020 17:34:05 +0200 Subject: [PATCH 52/85] Correctly check for text.Length --- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index ffe1175567..558e96b097 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -82,7 +82,7 @@ namespace Avalonia.Skia if (codepoint.IsBreakChar) { - if (i < text.End) + if (i < text.Length) { var nextCodepoint = Codepoint.ReadAt(text, i + 1, out _); From 718a50ccd8ff8d1eb12e0629c4da5209a7538a88 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 17 Jul 2020 18:28:25 +0200 Subject: [PATCH 53/85] Fix FillBuffer --- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index 558e96b097..b0384a1fdf 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -82,7 +82,7 @@ namespace Avalonia.Skia if (codepoint.IsBreakChar) { - if (i < text.Length) + if (i + 1 < text.Length) { var nextCodepoint = Codepoint.ReadAt(text, i + 1, out _); From 05154391fb39d430ae0c0df1344feea111ef54e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Tue, 14 Jul 2020 19:29:40 +0100 Subject: [PATCH 54/85] Fixed item type inference in compiled bindings. --- ...valoniaXamlIlDataContextTypeTransformer.cs | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs index 5a0d6bac8d..241976241f 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; -using Avalonia.Markup.Parsers; -using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; -using Avalonia.Utilities; using XamlX; using XamlX.Ast; using XamlX.Transform; @@ -129,12 +124,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (itemsCollectionType != null) { - var elementType = itemsCollectionType - .GetAllInterfaces() - .FirstOrDefault(i => - i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true) - .GenericArguments[0]; - return new AvaloniaXamlIlDataContextTypeMetadataNode(on, elementType); + foreach (var i in GetAllInterfacesIncludingSelf(itemsCollectionType)) + { + if (i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true) + { + return new AvaloniaXamlIlDataContextTypeMetadataNode(on, i.GenericArguments[0]); + } + } } // We can't infer the collection type and the currently calculated type is definitely wrong. // Notify the user that we were unable to infer the data context type if they use a compiled binding. @@ -165,6 +161,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on); } + + private static IEnumerable GetAllInterfacesIncludingSelf(IXamlType type) + { + if (type.IsInterface) + yield return type; + + foreach (var i in type.GetAllInterfaces()) + yield return i; + } } [DebuggerDisplay("DataType = {DataContextType}")] From c414e26cf67a60c625a8f925cd87f303b162fb9d Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 19 Jul 2020 00:15:08 +0200 Subject: [PATCH 55/85] Ensure that TryParse won't throw. --- src/Avalonia.Visuals/Media/Color.cs | 13 ++++++++---- .../Media/ColorTests.cs | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs index 052ee5e1b7..40515423dd 100644 --- a/src/Avalonia.Visuals/Media/Color.cs +++ b/src/Avalonia.Visuals/Media/Color.cs @@ -89,6 +89,11 @@ namespace Avalonia.Media /// The . public static Color Parse(string s) { + if (s is null) + { + throw new ArgumentNullException(nameof(s)); + } + if (TryParse(s, out Color color)) { return color; @@ -120,14 +125,16 @@ namespace Avalonia.Media /// The status of the operation. public static bool TryParse(string s, out Color color) { + color = default; + if (s == null) { - throw new ArgumentNullException(nameof(s)); + return false; } if (s.Length == 0) { - throw new FormatException(); + return false; } if (s[0] == '#' && TryParseInternal(s.AsSpan(), out color)) @@ -144,8 +151,6 @@ namespace Avalonia.Media return true; } - color = default; - return false; } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs index f3f3c9a4ca..d68c2fd5fd 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs @@ -179,5 +179,25 @@ namespace Avalonia.Visuals.UnitTests.Media { Assert.False(Color.TryParse("#ff808g80", out _)); } + + [Fact] + public void Parse_Throws_ArgumentNullException_For_Null_Input() + { + Assert.Throws(() => Color.Parse((string)null)); + } + + [Fact] + public void Parse_Throws_FormatException_For_Invalid_Input() + { + Assert.Throws(() => Color.Parse(string.Empty)); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void TryParse_Returns_False_For_Invalid_Input(string input) + { + Assert.False(Color.TryParse(input, out _)); + } } } From 50925b988e04b1cb06e906bd81d9b3760c004d49 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 19 Jul 2020 00:19:23 +0200 Subject: [PATCH 56/85] Use is check. --- src/Avalonia.Visuals/Media/Color.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs index 40515423dd..16b4f90d57 100644 --- a/src/Avalonia.Visuals/Media/Color.cs +++ b/src/Avalonia.Visuals/Media/Color.cs @@ -127,7 +127,7 @@ namespace Avalonia.Media { color = default; - if (s == null) + if (s is null) { return false; } From 09099c1234bb5fdf0285d3855bf8c515ae056184 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sun, 19 Jul 2020 14:50:29 +0200 Subject: [PATCH 57/85] Fix text wrapping for fluent TextBox --- src/Avalonia.Controls/TextBox.cs | 2 +- src/Avalonia.Themes.Fluent/TextBox.xaml | 105 +++++++++----------- src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 2 +- 3 files changed, 51 insertions(+), 58 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 394699ce64..87a674fabd 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -134,7 +134,7 @@ namespace Avalonia.Controls { if (acceptsReturn) { - return wrapping == TextWrapping.NoWrap ? + return wrapping != TextWrapping.Wrap ? ScrollBarVisibility.Auto : ScrollBarVisibility.Disabled; } diff --git a/src/Avalonia.Themes.Fluent/TextBox.xaml b/src/Avalonia.Themes.Fluent/TextBox.xaml index 278cde934c..0327e776e3 100644 --- a/src/Avalonia.Themes.Fluent/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/TextBox.xaml @@ -10,73 +10,65 @@ - - - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + - - - - - - + + + + + + + diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index d1f8d6a779..5e630e54a6 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -570,7 +570,7 @@ namespace Avalonia.Skia float constraint = -1; - if (_wrapping != TextWrapping.NoWrap) + if (_wrapping == TextWrapping.Wrap) { constraint = widthConstraint <= 0 ? MAX_LINE_WIDTH : widthConstraint; if (constraint > MAX_LINE_WIDTH) From 2ede354bbc7268f39c733342212a7730e976a7e0 Mon Sep 17 00:00:00 2001 From: Lorenzo Delana Date: Mon, 20 Jul 2020 14:53:19 +0200 Subject: [PATCH 58/85] fix X11 XDestroyWindow crash --- src/Avalonia.X11/X11Window.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2a13999e8d..1f1f31db0a 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -760,11 +760,7 @@ namespace Avalonia.X11 public void Dispose() { - if (_handle != IntPtr.Zero) - { - XDestroyWindow(_x11.Display, _handle); - Cleanup(); - } + Cleanup(); } void Cleanup() @@ -787,8 +783,7 @@ namespace Avalonia.X11 } if (_useRenderWindow && _renderHandle != IntPtr.Zero) - { - XDestroyWindow(_x11.Display, _renderHandle); + { _renderHandle = IntPtr.Zero; } } From c5edf9bc5da4e1dabad5d5a9f3cd8b078bd254d2 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 20 Jul 2020 18:37:11 +0200 Subject: [PATCH 59/85] Initial --- .../Media/TextFormatting/TextLineImpl.cs | 20 ++++-- .../Media/TextFormatting/TextLineTests.cs | 62 +++++++++++++++++-- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 980b1a2d40..f73a7be759 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -181,7 +181,7 @@ namespace Avalonia.Media.TextFormatting return nextCharacterHit; } - return new CharacterHit(TextRange.End); // Can't move, we're after the last character + return characterHit; // Can't move, we're after the last character } /// @@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting return previousCharacterHit; } - return new CharacterHit(TextRange.Start); // Can't move, we're before the first character + return characterHit; // Can't move, we're before the first character } /// @@ -247,9 +247,13 @@ namespace Avalonia.Media.TextFormatting { var run = _textRuns[runIndex]; - nextCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); + var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); - if (codepointIndex <= nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength) + nextCharacterHit = characterHit.TrailingLength != 0 ? + foundCharacterHit : + new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength); + + if (nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex) { return true; } @@ -283,9 +287,13 @@ namespace Avalonia.Media.TextFormatting { var run = _textRuns[runIndex]; - previousCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); + var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); + + previousCharacterHit = characterHit.TrailingLength != 0 ? + foundCharacterHit : + new CharacterHit(foundCharacterHit.FirstCharacterIndex); - if (previousCharacterHit.FirstCharacterIndex < codepointIndex) + if (previousCharacterHit.FirstCharacterIndex < characterHit.FirstCharacterIndex) { return true; } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index f0951c61d3..09cbf3bf08 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -31,12 +31,37 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var nextCharacterHit = new CharacterHit(0); - for (var i = 1; i < clusters.Length; i++) + for (var i = 0; i < clusters.Length; i++) { + Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex); + nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit); + } + + var lastCharacterHit = nextCharacterHit; + + nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit); + + Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex); + + Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength); + + nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]); - Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength); + for (var i = 0; i < clusters.Length; i++) + { + Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex); + + nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit); } + + lastCharacterHit = nextCharacterHit; + + nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit); + + Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex); + + Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength); } } @@ -60,14 +85,41 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var clusters = textLine.TextRuns.Cast().SelectMany(x => x.GlyphRun.GlyphClusters) .ToArray(); - var previousCharacterHit = new CharacterHit(clusters[^1]); + var previousCharacterHit = new CharacterHit(text.Length); - for (var i = clusters.Length - 2; i > 0; i--) + for (var i = clusters.Length - 1; i >= 0; i--) { previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit); - Assert.Equal(clusters[i], previousCharacterHit.FirstCharacterIndex); + Assert.Equal(clusters[i], + previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength); } + + var firstCharacterHit = previousCharacterHit; + + previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit); + + Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex); + + Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength); + + previousCharacterHit = new CharacterHit(clusters[^1], text.Length - clusters[^1]); + + for (var i = clusters.Length - 1; i > 0; i--) + { + previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit); + + Assert.Equal(clusters[i], + previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength); + } + + firstCharacterHit = previousCharacterHit; + + previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit); + + Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex); + + Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength); } } From 8c331534a9071a4b95e927cf9805ad5b455963c1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 15:44:19 -0300 Subject: [PATCH 60/85] fix nre on osx when tooltip closes. --- src/Avalonia.Native/WindowImplBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f916e95d7c..88956e41e1 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -432,7 +432,7 @@ namespace Avalonia.Native TransparencyLevel = transparencyLevel; - _native.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); + _native?.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); TransparencyLevelChanged?.Invoke(TransparencyLevel); } } From 0148106f545a3afa3b4b87b3f7a731f04a54d9ea Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 16:27:26 -0300 Subject: [PATCH 61/85] Seperate RenderScaling from DesktopScaling. --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- .../Offscreen/OffscreenTopLevelImpl.cs | 2 +- .../Platform/ITopLevelImpl.cs | 6 +-- .../Platform/IWindowBaseImpl.cs | 5 +++ .../ManagedPopupPositionerPopupImplHelper.cs | 4 +- src/Avalonia.Controls/TopLevel.cs | 4 +- src/Avalonia.Controls/Window.cs | 2 +- .../Remote/PreviewerWindowImpl.cs | 1 + src/Avalonia.DesignerSupport/Remote/Stubs.cs | 3 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 9 +++-- ...sxManagedPopupPositionerPopupImplHelper.cs | 15 -------- src/Avalonia.Native/PopupImpl.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 8 ++-- src/Avalonia.X11/X11NativeControlHost.cs | 4 +- src/Avalonia.X11/X11Window.cs | 38 ++++++++++--------- .../FramebufferToplevelImpl.cs | 4 +- .../Wpf/WpfTopLevelImpl.cs | 6 +-- src/Windows/Avalonia.Win32/PopupImpl.cs | 2 +- .../Avalonia.Win32/Win32NativeControlHost.cs | 4 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 14 +++---- .../WindowImpl.CustomCaptionProc.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 28 ++++++++------ src/iOS/Avalonia.iOS/TopLevelImpl.cs | 2 +- .../ContextMenuTests.cs | 2 +- .../DesktopStyleApplicationLifetimeTests.cs | 2 +- .../TopLevelTests.cs | 4 +- .../WindowBaseTests.cs | 10 ++--- .../WindowTests.cs | 14 +++---- .../WindowingPlatformMock.cs | 4 +- tests/Avalonia.LeakTests/ControlTests.cs | 2 +- .../MockWindowingPlatform.cs | 4 +- 31 files changed, 105 insertions(+), 104 deletions(-) delete mode 100644 src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 69fd2a9f13..71dce93ce7 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -126,7 +126,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _view.Visibility = ViewStates.Visible; } - public double Scaling => 1; + public double RenderScaling => 1; void Draw() { diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index f8bd2878d9..522103c7bd 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -35,7 +35,7 @@ namespace Avalonia.Controls.Embedding.Offscreen } } - public double Scaling + public double RenderScaling { get { return _scaling; } set diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 0d77cbf802..7514f214aa 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -23,10 +23,10 @@ namespace Avalonia.Platform Size ClientSize { get; } /// - /// Gets the scaling factor for the toplevel. + /// Gets the scaling factor for the toplevel. This is used for rendering. /// - double Scaling { get; } - + double RenderScaling { get; } + /// /// The list of native platform's surfaces that can be consumed by rendering subsystems. /// diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index b190c4f2e7..ecaf87d1ed 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -13,6 +13,11 @@ namespace Avalonia.Platform /// Hides the window. /// void Hide(); + + /// + /// Gets the scaling factor for Window positioning and sizing. + /// + double DesktopScaling { get; } /// /// Gets the position of the window in device pixels. diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs index b0e3d1ab08..91ed5d975d 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs @@ -40,9 +40,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning public void MoveAndResize(Point devicePoint, Size virtualSize) { - _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling); + _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.RenderScaling); } - public virtual double Scaling => _parent.Scaling; + public virtual double Scaling => _parent.DesktopScaling; } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 611f0c9290..3d24f60463 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -280,10 +280,10 @@ namespace Avalonia.Controls } /// - double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1; + double ILayoutRoot.LayoutScaling => PlatformImpl?.RenderScaling ?? 1; /// - double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1; + double IRenderRoot.RenderScaling => PlatformImpl?.RenderScaling ?? 1; IStyleHost IStyleHost.StylingParent => _globalStyles; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 18d8c89f49..90e5c22c45 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -818,7 +818,7 @@ namespace Avalonia.Controls private void SetWindowStartupLocation(IWindowBaseImpl owner = null) { - var scaling = owner?.Scaling ?? PlatformImpl?.Scaling ?? 1; + var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1; // TODO: We really need non-client size here. var rect = new PixelRect( diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index dce24df9d9..25c26be91e 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -36,6 +36,7 @@ namespace Avalonia.DesignerSupport.Remote { } + public double DesktopScaling => 1.0; public PixelPoint Position { get; set; } public Action PositionChanged { get; set; } public Action Deactivated { get; set; } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 168cdbc03f..f377b1bcd1 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -21,7 +21,8 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public Size MaxAutoSizeHint { get; } public Size ClientSize { get; } - public double Scaling { get; } = 1.0; + public double RenderScaling { get; } = 1.0; + public double DesktopScaling => 1.0; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 5bd46b6714..8f4fa5e304 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -41,7 +41,8 @@ namespace Avalonia.Headless } public Size ClientSize { get; set; } - public double Scaling { get; } = 1; + public double RenderScaling { get; } = 1; + public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } @@ -62,9 +63,9 @@ namespace Avalonia.Headless public IInputRoot InputRoot { get; set; } - public Point PointToClient(PixelPoint point) => point.ToPoint(Scaling); + public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling); - public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, Scaling); + public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling); public void SetCursor(IPlatformHandle cursor) { @@ -201,7 +202,7 @@ namespace Avalonia.Headless public ILockedFramebuffer Lock() { - var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, Scaling), new Vector(96, 96) * Scaling); + var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, RenderScaling), new Vector(96, 96) * RenderScaling); var fb = bmp.Lock(); return new FramebufferProxy(fb, () => { diff --git a/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs deleted file mode 100644 index 8aa9b1a122..0000000000 --- a/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia.Controls.Primitives.PopupPositioning; -using Avalonia.Platform; - -namespace Avalonia.Native -{ - class OsxManagedPopupPositionerPopupImplHelper : ManagedPopupPositionerPopupImplHelper - { - public OsxManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize) : base(parent, moveResize) - { - - } - - public override double Scaling => 1; - } -} diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index b0da5fdc43..2d246e08d2 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -26,7 +26,7 @@ namespace Avalonia.Native var context = _opts.UseGpu ? glFeature?.DeferredContext : null; Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context); } - PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize)); + PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); } private void MoveResize(PixelPoint position, Size size, double scaling) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f916e95d7c..42eecc36ea 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -81,7 +81,7 @@ namespace Avalonia.Native _glSurface = new GlPlatformSurface(window, _glContext); Screen = new ScreenImpl(screens); _savedLogicalSize = ClientSize; - _savedScaling = Scaling; + _savedScaling = RenderScaling; _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost()); var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) @@ -369,7 +369,9 @@ namespace Avalonia.Native _native.SetTopMost(value); } - public double Scaling => _native?.GetScaling() ?? 1; + public double RenderScaling => _native?.GetScaling() ?? 1; + + public double DesktopScaling => 1; public Action Deactivated { get; set; } public Action Activated { get; set; } @@ -432,7 +434,7 @@ namespace Avalonia.Native TransparencyLevel = transparencyLevel; - _native.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); + _native?.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); TransparencyLevelChanged?.Invoke(TransparencyLevel); } } diff --git a/src/Avalonia.X11/X11NativeControlHost.cs b/src/Avalonia.X11/X11NativeControlHost.cs index 23fb27f72b..6c4eb81c84 100644 --- a/src/Avalonia.X11/X11NativeControlHost.cs +++ b/src/Avalonia.X11/X11NativeControlHost.cs @@ -167,7 +167,7 @@ namespace Avalonia.X11 XUnmapWindow(_display, _holder.Handle); } - size *= _attachedTo.Window.Scaling; + size *= _attachedTo.Window.RenderScaling; XResizeWindow(_display, _child.Handle, Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height)); } @@ -179,7 +179,7 @@ namespace Avalonia.X11 CheckDisposed(); if (_attachedTo == null) throw new InvalidOperationException("The control isn't currently attached to a toplevel"); - bounds *= _attachedTo.Window.Scaling; + bounds *= _attachedTo.Window.RenderScaling; var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height)); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 1f1f31db0a..c24abcd230 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -163,7 +163,7 @@ namespace Avalonia.X11 var surfaces = new List { new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, - depth, () => Scaling) + depth, () => RenderScaling) }; if (egl != null) @@ -217,7 +217,7 @@ namespace Avalonia.X11 } } - public double Scaling => _window.Scaling; + public double Scaling => _window.RenderScaling; } void UpdateMotifHints() @@ -284,9 +284,9 @@ namespace Avalonia.X11 XSetWMNormalHints(_x11.Display, _handle, ref hints); } - public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling); + public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); - public double Scaling + public double RenderScaling { get { @@ -296,6 +296,8 @@ namespace Avalonia.X11 } private set => _scaling = value; } + + public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } public Action Input { get; set; } @@ -538,14 +540,14 @@ namespace Avalonia.X11 { var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity) .FirstOrDefault(m => m.Bounds.Contains(Position)); - newScaling = monitor?.PixelDensity ?? Scaling; + newScaling = monitor?.PixelDensity ?? RenderScaling; } - if (Scaling != newScaling) + if (RenderScaling != newScaling) { var oldScaledSize = ClientSize; - Scaling = newScaling; - ScalingChanged?.Invoke(Scaling); + RenderScaling = newScaling; + ScalingChanged?.Invoke(RenderScaling); SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize); if(!skipResize) Resize(oldScaledSize, true); @@ -707,9 +709,9 @@ namespace Avalonia.X11 private void ScheduleInput(RawInputEventArgs args) { if (args is RawPointerEventArgs mouse) - mouse.Position = mouse.Position / Scaling; + mouse.Position = mouse.Position / RenderScaling; if (args is RawDragEvent drag) - drag.Location = drag.Location / Scaling; + drag.Location = drag.Location / RenderScaling; _lastEvent = new InputEventContainer() {Event = args}; _inputQueue.Enqueue(_lastEvent); @@ -816,11 +818,11 @@ namespace Avalonia.X11 public void Hide() => XUnmapWindow(_x11.Display, _handle); - public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling); + public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / RenderScaling, (point.Y - Position.Y) / RenderScaling); public PixelPoint PointToScreen(Point point) => new PixelPoint( - (int)(point.X * Scaling + Position.X), - (int)(point.Y * Scaling + Position.Y)); + (int)(point.X * RenderScaling + Position.X), + (int)(point.Y * RenderScaling + Position.Y)); public void SetSystemDecorations(SystemDecorations enabled) { @@ -840,7 +842,7 @@ namespace Avalonia.X11 Resize(size, true); } - PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling)); + PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling)); void Resize(Size clientSize, bool force) { @@ -1020,13 +1022,13 @@ namespace Avalonia.X11 { _scaledMinMaxSize = (minSize, maxSize); var min = new PixelSize( - (int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling), - (int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling)); + (int)(minSize.Width < 1 ? 1 : minSize.Width * RenderScaling), + (int)(minSize.Height < 1 ? 1 : minSize.Height * RenderScaling)); const int maxDim = MaxWindowDimension; var max = new PixelSize( - (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * Scaling)), - (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * Scaling))); + (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * RenderScaling)), + (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * RenderScaling))); _minMaxSize = (min, max); UpdateSizeHints(null); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index b8ae2eb4d8..0a101eec7a 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -65,7 +65,7 @@ namespace Avalonia.LinuxFramebuffer public IMouseDevice MouseDevice => new MouseDevice(); public IPopupImpl CreatePopup() => null; - public double Scaling => _outputBackend.Scaling; + public double RenderScaling => _outputBackend.Scaling; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } @@ -77,7 +77,7 @@ namespace Avalonia.LinuxFramebuffer public Action Closed { get; set; } public Action LostFocus { get; set; } - public Size ScaledSize => _outputBackend.PixelSize.ToSize(Scaling); + public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling); public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index f5d83611bb..3467a33d16 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -75,7 +75,7 @@ namespace Avalonia.Win32.Interop.Wpf private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) { if (msg == (int)UnmanagedMethods.WindowsMessage.WM_DPICHANGED) - _ttl.ScalingChanged?.Invoke(_ttl.Scaling); + _ttl.ScalingChanged?.Invoke(_ttl.RenderScaling); return IntPtr.Zero; } @@ -84,7 +84,7 @@ namespace Avalonia.Win32.Interop.Wpf _currentHwndSource?.RemoveHook(_hook); _currentHwndSource = e.NewSource as HwndSource; _currentHwndSource?.AddHook(_hook); - _ttl.ScalingChanged?.Invoke(_ttl.Scaling); + _ttl.ScalingChanged?.Invoke(_ttl.RenderScaling); } public IRenderer CreateRenderer(IRenderRoot root) @@ -102,7 +102,7 @@ namespace Avalonia.Win32.Interop.Wpf Size ITopLevelImpl.ClientSize => _finalSize; IMouseDevice ITopLevelImpl.MouseDevice => _mouse; - double ITopLevelImpl.Scaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; + double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; IEnumerable ITopLevelImpl.Surfaces => _surfaces; diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 525e5e0d52..57da1c4d66 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -57,7 +57,7 @@ namespace Avalonia.Win32 { var info = UnmanagedMethods.MONITORINFO.Create(); UnmanagedMethods.GetMonitorInfo(monitor, ref info); - _maxAutoSize = info.rcWork.ToPixelRect().ToRect(Scaling).Size; + _maxAutoSize = info.rcWork.ToPixelRect().ToRect(RenderScaling).Size; } } diff --git a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs index d7bb2c037e..8f62163d81 100644 --- a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs +++ b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs @@ -176,7 +176,7 @@ namespace Avalonia.Win32 UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); if (_attachedTo == null || _child == null) return; - size *= _attachedTo.Window.Scaling; + size *= _attachedTo.Window.RenderScaling; UnmanagedMethods.MoveWindow(_child.Handle, 0, 0, Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false); } @@ -186,7 +186,7 @@ namespace Avalonia.Win32 CheckDisposed(); if (_attachedTo == null) throw new InvalidOperationException("The control isn't currently attached to a toplevel"); - bounds *= _attachedTo.Window.Scaling; + bounds *= _attachedTo.Window.RenderScaling; var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height)); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 0ba1d311bc..ee6845e2eb 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -343,7 +343,7 @@ namespace Avalonia.Win32 { if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) { - var f = Scaling; + var f = RenderScaling; var r = ps.rcPaint; Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, (r.bottom - r.top) / f)); @@ -368,7 +368,7 @@ namespace Avalonia.Win32 size == SizeCommand.Maximized)) { var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); - Resized(clientSize / Scaling); + Resized(clientSize / RenderScaling); } var windowState = size == SizeCommand.Maximized ? @@ -406,25 +406,25 @@ namespace Avalonia.Win32 if (_minSize.Width > 0) { mmi.ptMinTrackSize.X = - (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + (int)((_minSize.Width * RenderScaling) + BorderThickness.Left + BorderThickness.Right); } if (_minSize.Height > 0) { mmi.ptMinTrackSize.Y = - (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + (int)((_minSize.Height * RenderScaling) + BorderThickness.Top + BorderThickness.Bottom); } if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) { mmi.ptMaxTrackSize.X = - (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + (int)((_maxSize.Width * RenderScaling) + BorderThickness.Left + BorderThickness.Right); } if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0) { mmi.ptMaxTrackSize.Y = - (int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + (int)((_maxSize.Height * RenderScaling) + BorderThickness.Top + BorderThickness.Bottom); } Marshal.StructureToPtr(mmi, lParam, true); @@ -480,7 +480,7 @@ namespace Avalonia.Win32 private Point DipFromLParam(IntPtr lParam) { - return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; + return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / RenderScaling; } private PixelPoint PointFromLParam(IntPtr lParam) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index 2badf99f7f..a3b7574369 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -37,7 +37,7 @@ namespace Avalonia.Win32 if (_extendTitleBarHint >= 0) { - border_thickness.top = (int)(_extendedMargins.Top * Scaling); + border_thickness.top = (int)(_extendedMargins.Top * RenderScaling); } // Determine if the hit test is for resizing. Default middle (1,1). diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 0ee1342d27..6f22f94056 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -164,7 +164,9 @@ namespace Avalonia.Win32 } } - public double Scaling => _scaling; + public double RenderScaling => _scaling; + + public double DesktopScaling => RenderScaling; public Size ClientSize { @@ -172,7 +174,7 @@ namespace Avalonia.Win32 { GetClientRect(_hwnd, out var rect); - return new Size(rect.right, rect.bottom) / Scaling; + return new Size(rect.right, rect.bottom) / RenderScaling; } } @@ -180,7 +182,7 @@ namespace Avalonia.Win32 public IPlatformHandle Handle { get; private set; } - public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / Scaling, _maxTrackSize.Y / Scaling); + public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / RenderScaling, _maxTrackSize.Y / RenderScaling); public IMouseDevice MouseDevice => _mouseDevice; @@ -342,8 +344,8 @@ namespace Avalonia.Win32 public void Resize(Size value) { - int requestedClientWidth = (int)(value.Width * Scaling); - int requestedClientHeight = (int)(value.Height * Scaling); + int requestedClientWidth = (int)(value.Width * RenderScaling); + int requestedClientHeight = (int)(value.Height * RenderScaling); GetClientRect(_hwnd, out var clientRect); @@ -395,7 +397,7 @@ namespace Avalonia.Win32 public void Invalidate(Rect rect) { - var scaling = Scaling; + var scaling = RenderScaling; var r = new RECT { left = (int)Math.Floor(rect.X * scaling), @@ -411,12 +413,12 @@ namespace Avalonia.Win32 { var p = new POINT { X = point.X, Y = point.Y }; UnmanagedMethods.ScreenToClient(_hwnd, ref p); - return new Point(p.X, p.Y) / Scaling; + return new Point(p.X, p.Y) / RenderScaling; } public PixelPoint PointToScreen(Point point) { - point *= Scaling; + point *= RenderScaling; var p = new POINT { X = (int)point.X, Y = (int)point.Y }; ClientToScreen(_hwnd, ref p); return new PixelPoint(p.X, p.Y); @@ -710,19 +712,19 @@ namespace Avalonia.Win32 if (_extendTitleBarHint != -1) { - borderCaptionThickness.top = (int)(_extendTitleBarHint * Scaling); + borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling); } margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1; if (WindowState == WindowState.Maximized) { - _extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / Scaling, 0, 0); - _offScreenMargin = new Thickness(borderThickness.left / Scaling, borderThickness.top / Scaling, borderThickness.right / Scaling, borderThickness.bottom / Scaling); + _extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / RenderScaling, 0, 0); + _offScreenMargin = new Thickness(borderThickness.left / RenderScaling, borderThickness.top / RenderScaling, borderThickness.right / RenderScaling, borderThickness.bottom / RenderScaling); } else { - _extendedMargins = new Thickness(0, (borderCaptionThickness.top) / Scaling, 0, 0); + _extendedMargins = new Thickness(0, (borderCaptionThickness.top) / RenderScaling, 0, 0); _offScreenMargin = new Thickness(); } @@ -1034,6 +1036,8 @@ namespace Avalonia.Win32 } } + double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => RenderScaling; + IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; public void SetExtendClientAreaToDecorationsHint(bool hint) diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs index 83a68990d7..5a85a5ea88 100644 --- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs +++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs @@ -48,7 +48,7 @@ namespace Avalonia.iOS public new IPlatformHandle Handle => null; - public double Scaling => UIScreen.MainScreen.Scale; + public double RenderScaling => UIScreen.MainScreen.Scale; public override void LayoutSubviews() => Resized?.Invoke(ClientSize); diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index cf8f7c266a..7a2109e5a7 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -296,7 +296,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = MockWindowingPlatform.CreateWindowMock(); popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object); - popupImpl.SetupGet(x => x.Scaling).Returns(1); + popupImpl.SetupGet(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.CreatePopup()).Returns(popupImpl.Object); windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object); diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index dee7a84812..e6deabfe25 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index e49e273bec..6b30aed257 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -93,7 +93,7 @@ namespace Avalonia.Controls.UnitTests { var impl = new Mock(); impl.SetupProperty(x => x.Resized); - impl.SetupGet(x => x.Scaling).Returns(1); + impl.SetupGet(x => x.RenderScaling).Returns(1); var target = new TestTopLevel(impl.Object) { @@ -290,7 +290,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(TestServices.StyledWindow)) { var impl = new Mock(); - impl.SetupGet(x => x.Scaling).Returns(1); + impl.SetupGet(x => x.RenderScaling).Returns(1); var child = new Border { Classes = { "foo" } }; var target = new TestTopLevel(impl.Object) diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 697ea9cff8..7e3130377f 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -110,7 +110,7 @@ namespace Avalonia.Controls.UnitTests public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -128,7 +128,7 @@ namespace Avalonia.Controls.UnitTests public void Setting_IsVisible_True_Shows_Window() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -143,7 +143,7 @@ namespace Avalonia.Controls.UnitTests public void Setting_IsVisible_False_Hides_Window() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -208,7 +208,7 @@ namespace Avalonia.Controls.UnitTests { var renderer = new Mock(); var windowImpl = new Mock(); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); @@ -237,7 +237,7 @@ namespace Avalonia.Controls.UnitTests public TestWindowBase(IRenderer renderer = null) : base(Mock.Of(x => - x.Scaling == 1 && + x.RenderScaling == 1 && x.CreateRenderer(It.IsAny()) == renderer)) { } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index e2b0def00b..2b736ae38b 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -100,7 +100,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); @@ -206,7 +206,7 @@ namespace Avalonia.Controls.UnitTests var parent = new Mock(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -245,7 +245,7 @@ namespace Avalonia.Controls.UnitTests var parent = new Mock(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -273,7 +273,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(screens.Object); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -298,12 +298,12 @@ namespace Avalonia.Controls.UnitTests var parentWindowImpl = MockWindowingPlatform.CreateWindowMock(); parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); parentWindowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); - parentWindowImpl.Setup(x => x.Scaling).Returns(1); + parentWindowImpl.Setup(x => x.RenderScaling).Returns(1); var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200)); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var parentWindowServices = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object)); @@ -565,7 +565,7 @@ namespace Avalonia.Controls.UnitTests private IWindowImpl CreateImpl(Mock renderer) { return Mock.Of(x => - x.Scaling == 1 && + x.RenderScaling == 1 && x.CreateRenderer(It.IsAny()) == renderer.Object); } diff --git a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs b/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs index 25e8c82b1a..bf1322afbc 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs @@ -17,7 +17,7 @@ namespace Avalonia.Controls.UnitTests public IWindowImpl CreateWindow() { - return _windowImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1); + return _windowImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1); } public IWindowImpl CreateEmbeddableWindow() @@ -25,6 +25,6 @@ namespace Avalonia.Controls.UnitTests throw new NotImplementedException(); } - public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1); + public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1); } } diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 00ef503b8d..530b8fa20c 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -355,7 +355,7 @@ namespace Avalonia.LeakTests var renderer = new Mock(); renderer.Setup(x => x.Dispose()); var impl = new Mock(); - impl.SetupGet(x => x.Scaling).Returns(1); + impl.SetupGet(x => x.RenderScaling).Returns(1); impl.SetupProperty(x => x.Closed); impl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed()); diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 48a333dc54..e5fcce13c0 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -29,7 +29,7 @@ namespace Avalonia.UnitTests windowImpl.SetupAllProperties(); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object); windowImpl.Setup(x => x.Position).Returns(() => position); SetupToplevel(windowImpl); @@ -81,7 +81,7 @@ namespace Avalonia.UnitTests popupImpl.SetupAllProperties(); popupImpl.Setup(x => x.ClientSize).Returns(() => clientSize); popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); - popupImpl.Setup(x => x.Scaling).Returns(1); + popupImpl.Setup(x => x.RenderScaling).Returns(1); popupImpl.Setup(x => x.PopupPositioner).Returns(positioner); SetupToplevel(popupImpl); From 2ad40026865fd2189fbabfaa20df1bdbd56f06ce Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 16:56:00 -0300 Subject: [PATCH 62/85] fix unit tests. --- .../DesktopStyleApplicationLifetimeTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs | 8 ++++---- tests/Avalonia.Controls.UnitTests/WindowTests.cs | 12 ++++++------ tests/Avalonia.UnitTests/MockWindowingPlatform.cs | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index e6deabfe25..837a62f40e 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 7e3130377f..1c72eb5069 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -110,7 +110,7 @@ namespace Avalonia.Controls.UnitTests public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -128,7 +128,7 @@ namespace Avalonia.Controls.UnitTests public void Setting_IsVisible_True_Shows_Window() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -143,7 +143,7 @@ namespace Avalonia.Controls.UnitTests public void Setting_IsVisible_False_Hides_Window() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -208,7 +208,7 @@ namespace Avalonia.Controls.UnitTests { var renderer = new Mock(); var windowImpl = new Mock(); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 2b736ae38b..5cf65115bb 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -100,7 +100,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); @@ -206,7 +206,7 @@ namespace Avalonia.Controls.UnitTests var parent = new Mock(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -245,7 +245,7 @@ namespace Avalonia.Controls.UnitTests var parent = new Mock(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -273,7 +273,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(screens.Object); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -298,12 +298,12 @@ namespace Avalonia.Controls.UnitTests var parentWindowImpl = MockWindowingPlatform.CreateWindowMock(); parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); parentWindowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); - parentWindowImpl.Setup(x => x.RenderScaling).Returns(1); + parentWindowImpl.Setup(x => x.DesktopScaling).Returns(1); var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200)); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var parentWindowServices = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object)); diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index e5fcce13c0..e265b49af3 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -29,7 +29,7 @@ namespace Avalonia.UnitTests windowImpl.SetupAllProperties(); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object); windowImpl.Setup(x => x.Position).Returns(() => position); SetupToplevel(windowImpl); From 87c326a8fd0c7e54c60750179b00231e8cef9168 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 17:13:27 -0300 Subject: [PATCH 63/85] fix mocks. --- .../DesktopStyleApplicationLifetimeTests.cs | 1 + tests/Avalonia.UnitTests/MockWindowingPlatform.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index 837a62f40e..84f02aeda5 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -192,6 +192,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index e265b49af3..67503ef0d0 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -30,6 +30,7 @@ namespace Avalonia.UnitTests windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object); windowImpl.Setup(x => x.Position).Returns(() => position); SetupToplevel(windowImpl); From 4aa51e80da8fda5314b0913d42dc3495dced8b1d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 17:26:03 -0300 Subject: [PATCH 64/85] fix more tests. --- tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs | 4 ++++ tests/Avalonia.Controls.UnitTests/WindowTests.cs | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 1c72eb5069..84f212d1b3 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -111,6 +111,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -129,6 +130,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -144,6 +146,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -209,6 +212,7 @@ namespace Avalonia.Controls.UnitTests var renderer = new Mock(); var windowImpl = new Mock(); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 5cf65115bb..ba29001cf3 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -101,6 +101,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); @@ -207,6 +208,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -246,6 +248,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -274,6 +277,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(screens.Object); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -299,11 +303,13 @@ namespace Avalonia.Controls.UnitTests parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); parentWindowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); parentWindowImpl.Setup(x => x.DesktopScaling).Returns(1); + parentWindowImpl.Setup(x => x.RenderScaling).Returns(1); var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200)); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var parentWindowServices = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object)); From 88db585d42ff663fb42ab1d3ef6bda5485298d68 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 17:52:56 -0300 Subject: [PATCH 65/85] fix calculation of working area. --- native/Avalonia.Native/src/OSX/Screens.mm | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index 278daf9a18..455cfa2e41 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -4,6 +4,14 @@ class Screens : public ComSingleObject { public: FORWARD_IUNKNOWN() + + private: + CGFloat PrimaryDisplayHeight() + { + return NSMaxY([[[NSScreen screens] firstObject] frame]); + } + +public: virtual HRESULT GetScreenCount (int* ret) override { @autoreleasepool @@ -25,15 +33,15 @@ class Screens : public ComSingleObject auto screen = [[NSScreen screens] objectAtIndex:index]; - ret->Bounds.X = [screen frame].origin.x; - ret->Bounds.Y = [screen frame].origin.y; ret->Bounds.Height = [screen frame].size.height; ret->Bounds.Width = [screen frame].size.width; + ret->Bounds.X = [screen frame].origin.x; + ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height; - ret->WorkingArea.X = [screen visibleFrame].origin.x; - ret->WorkingArea.Y = [screen visibleFrame].origin.y; ret->WorkingArea.Height = [screen visibleFrame].size.height; ret->WorkingArea.Width = [screen visibleFrame].size.width; + ret->WorkingArea.X = [screen visibleFrame].origin.x; + ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height; ret->PixelDensity = [screen backingScaleFactor]; From 297ed15bb5ae08ab06cbc22b2de004dd8f786bc4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 20:54:33 -0300 Subject: [PATCH 66/85] fix sizetocontent on win32. --- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index ee6845e2eb..25a34561fc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -402,6 +402,8 @@ namespace Avalonia.Win32 case WindowsMessage.WM_GETMINMAXINFO: { MINMAXINFO mmi = Marshal.PtrToStructure(lParam); + + _maxTrackSize = mmi.ptMaxTrackSize; if (_minSize.Width > 0) { From b0bcd2e5e62ce4ea94a094677cdb191adf53c214 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 21:16:35 -0300 Subject: [PATCH 67/85] dont emit excessive Resized events on OSX. --- native/Avalonia.Native/src/OSX/window.mm | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 872269bb26..2d0ffbe4f0 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -1291,10 +1291,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _parent->UpdateCursor(); auto fsize = [self convertSizeToBacking: [self frame].size]; - _lastPixelSize.Width = (int)fsize.width; - _lastPixelSize.Height = (int)fsize.height; - [self updateRenderTarget]; - _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); + + if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height) + { + _lastPixelSize.Width = (int)fsize.width; + _lastPixelSize.Height = (int)fsize.height; + [self updateRenderTarget]; + + _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); + } } - (void)updateLayer From e5324684a179e4ede5cfb5982f948ac18b3cbf93 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 22:15:12 -0300 Subject: [PATCH 68/85] ensure managed titlebar is attached in window when template is applied. --- src/Avalonia.Controls/Chrome/TitleBar.cs | 8 ++++---- src/Avalonia.Controls/Window.cs | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 78b49d2a03..91cbc8b497 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -29,12 +29,12 @@ namespace Avalonia.Controls.Chrome { if (_disposables == null) { - var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - - layer?.Children.Add(this); - if (_hostWindow != null) { + var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + + layer?.Children.Add(this); + _disposables = new CompositeDisposable { _hostWindow.GetObservable(Window.WindowDecorationMarginProperty) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 90e5c22c45..874c94a974 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -6,6 +6,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls.Chrome; using Avalonia.Controls.Platform; +using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; @@ -75,6 +76,7 @@ namespace Avalonia.Controls private bool _isExtendedIntoWindowDecorations; private Thickness _windowDecorationMargin; private Thickness _offScreenMargin; + private bool _templateApplied; /// /// Defines the property. @@ -553,7 +555,7 @@ namespace Avalonia.Controls WindowDecorationMargin = PlatformImpl.ExtendedMargins; OffScreenMargin = PlatformImpl.OffScreenMargin; - if (PlatformImpl.NeedsManagedDecorations) + if (PlatformImpl.NeedsManagedDecorations && _templateApplied) { if (_managedTitleBar == null) { @@ -568,6 +570,15 @@ namespace Avalonia.Controls } } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + _templateApplied = true; + + ExtendClientAreaToDecorationsChanged(PlatformImpl.IsClientAreaExtendedToDecorations); + } + /// /// Hides the window but does not close it. /// From d8604567eec0445e2898a78437b5016b021a2cd4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 22:38:31 -0300 Subject: [PATCH 69/85] fix window background interfering with titlebar and window dragging. --- src/Avalonia.Themes.Default/Window.xaml | 31 ++++++++++++------------- src/Avalonia.Themes.Fluent/Window.xaml | 19 +++++++-------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Themes.Default/Window.xaml b/src/Avalonia.Themes.Default/Window.xaml index 739887fb35..2aa62df01f 100644 --- a/src/Avalonia.Themes.Default/Window.xaml +++ b/src/Avalonia.Themes.Default/Window.xaml @@ -1,23 +1,22 @@ + + + + + + + + + + diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs index dc9c4a8f49..9e7ae8b716 100644 --- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs @@ -17,6 +17,23 @@ namespace ControlCatalog.ViewModels SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false)); OpenRecentCommand = ReactiveCommand.Create(OpenRecent); + var recentItems = new[] + { + new MenuItemViewModel + { + Header = "File1.txt", + Command = OpenRecentCommand, + CommandParameter = @"c:\foo\File1.txt" + }, + new MenuItemViewModel + { + Header = "File2.txt", + Command = OpenRecentCommand, + CommandParameter = @"c:\foo\File2.txt" + }, + }; + + RecentItems = recentItems; MenuItems = new[] { new MenuItemViewModel @@ -24,27 +41,13 @@ namespace ControlCatalog.ViewModels Header = "_File", Items = new[] { - new MenuItemViewModel { Header = "_Open...", Command = OpenCommand }, + new MenuItemViewModel { Header = "O_pen...", Command = OpenCommand }, new MenuItemViewModel { Header = "Save", Command = SaveCommand }, new MenuItemViewModel { Header = "-" }, new MenuItemViewModel { Header = "Recent", - Items = new[] - { - new MenuItemViewModel - { - Header = "File1.txt", - Command = OpenRecentCommand, - CommandParameter = @"c:\foo\File1.txt" - }, - new MenuItemViewModel - { - Header = "File2.txt", - Command = OpenRecentCommand, - CommandParameter = @"c:\foo\File2.txt" - }, - } + Items = recentItems }, } }, @@ -61,6 +64,7 @@ namespace ControlCatalog.ViewModels } public IReadOnlyList MenuItems { get; set; } + public IReadOnlyList RecentItems { get; set; } public ReactiveCommand OpenCommand { get; } public ReactiveCommand SaveCommand { get; } public ReactiveCommand OpenRecentCommand { get; } From 3c444dc279fe8a030d489315ddc8888b21140a2e Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Tue, 21 Jul 2020 19:49:58 -0400 Subject: [PATCH 80/85] Fix MenuItem:pressed state --- src/Avalonia.Themes.Fluent/MenuItem.xaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/MenuItem.xaml b/src/Avalonia.Themes.Fluent/MenuItem.xaml index fbb994e90c..4899bf264f 100644 --- a/src/Avalonia.Themes.Fluent/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/MenuItem.xaml @@ -183,10 +183,10 @@ - + @@ -212,14 +212,15 @@ - - - From 5e2c641f02f0c6d4382ed1a040c86c5576d825ca Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Tue, 21 Jul 2020 20:21:13 -0400 Subject: [PATCH 81/85] Move Popup in Fluent MenuItem to another parent node and add MenuFlyoutSubItemPopupHorizontalOffset --- src/Avalonia.Themes.Fluent/MenuItem.xaml | 155 ++++++++++++----------- 1 file changed, 79 insertions(+), 76 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/MenuItem.xaml b/src/Avalonia.Themes.Fluent/MenuItem.xaml index 4899bf264f..0442c38025 100644 --- a/src/Avalonia.Themes.Fluent/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/MenuItem.xaml @@ -40,6 +40,7 @@ + -4 0,4,0,4 0,0,12,0 24,0,0,0 @@ -54,83 +55,85 @@ - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + From bb46dee949714829a7515e0637c1f32b5061c61d Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Tue, 21 Jul 2020 20:48:07 -0400 Subject: [PATCH 82/85] MenuBar item header should be centered --- src/Avalonia.Themes.Fluent/Menu.xaml | 8 +++++--- src/Avalonia.Themes.Fluent/MenuItem.xaml | 9 ++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Menu.xaml b/src/Avalonia.Themes.Fluent/Menu.xaml index 5f22f77d18..cf647ec64a 100644 --- a/src/Avalonia.Themes.Fluent/Menu.xaml +++ b/src/Avalonia.Themes.Fluent/Menu.xaml @@ -10,11 +10,13 @@ - - 32 + + 32 + 12,0,12,0 + - + @@ -187,7 +189,12 @@ + + - + - +