From e5324684a179e4ede5cfb5982f948ac18b3cbf93 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 22:15:12 -0300 Subject: [PATCH 001/110] 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 002/110] 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 014/110] 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 015/110] 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 016/110] 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 @@ + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - From 836a4d88c89c227f98b82abe38510ddb469cce36 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 25 Jul 2020 20:37:45 +0200 Subject: [PATCH 056/110] Add missing style include for TitleBar in default theme. --- src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 97cff5d94b..4e63d1d223 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -37,6 +37,7 @@ + From 027783e3decf635ba2ba38033355563ebdfa832f Mon Sep 17 00:00:00 2001 From: Anton Mitsengendler Date: Sun, 26 Jul 2020 01:26:36 +0300 Subject: [PATCH 057/110] Added ComboBox placeholder to default theme template --- src/Avalonia.Themes.Default/ComboBox.xaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/ComboBox.xaml b/src/Avalonia.Themes.Default/ComboBox.xaml index ae5b902ae8..8ee818bad2 100644 --- a/src/Avalonia.Themes.Default/ComboBox.xaml +++ b/src/Avalonia.Themes.Default/ComboBox.xaml @@ -1,4 +1,5 @@ - + @@ -24,6 +25,7 @@ + + Date: Sun, 26 Jul 2020 13:18:43 +0200 Subject: [PATCH 058/110] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 19a9a8420d..c1bf5bbfde 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Discord](https://img.shields.io/badge/discord-join%20chat-46BC99)]( https://aka.ms/dotnet-discord) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg)
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) From b04a1f78646099fa6f2a3dd4026ff2e7697d61cb Mon Sep 17 00:00:00 2001 From: Anton Mitsengendler Date: Sun, 26 Jul 2020 22:12:39 +0300 Subject: [PATCH 059/110] Changed resource name for a default placeholder foreground Added template binding for the placeholder foreground property to make it customizable from user code --- src/Avalonia.Themes.Default/ComboBox.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/ComboBox.xaml b/src/Avalonia.Themes.Default/ComboBox.xaml index 8ee818bad2..0fd6b144a4 100644 --- a/src/Avalonia.Themes.Default/ComboBox.xaml +++ b/src/Avalonia.Themes.Default/ComboBox.xaml @@ -25,7 +25,7 @@ - + Date: Mon, 27 Jul 2020 16:03:16 +0300 Subject: [PATCH 060/110] Enabled custom renderer factory for X11 and macOS --- src/Avalonia.Native/WindowImplBase.cs | 10 +++++++++- src/Avalonia.X11/X11Window.cs | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 42eecc36ea..4b13666edd 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -300,7 +300,15 @@ namespace Avalonia.Native public IRenderer CreateRenderer(IRenderRoot root) { if (_deferredRendering) - return new DeferredRenderer(root, AvaloniaLocator.Current.GetService()); + { + var loop = AvaloniaLocator.Current.GetService(); + var customRendererFactory = AvaloniaLocator.Current.GetService(); + + if (customRendererFactory != null) + return customRendererFactory.Create(root, loop); + return new DeferredRenderer(root, loop); + } + return new ImmediateRenderer(root); } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index c24abcd230..0c0b942bcd 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -331,6 +331,11 @@ namespace Avalonia.X11 public IRenderer CreateRenderer(IRenderRoot root) { var loop = AvaloniaLocator.Current.GetService(); + var customRendererFactory = AvaloniaLocator.Current.GetService(); + + if (customRendererFactory != null) + return customRendererFactory.Create(root, loop); + return _platform.Options.UseDeferredRendering ? new DeferredRenderer(root, loop) : (IRenderer)new X11ImmediateRendererProxy(root, loop); From 8ac2e525ada5f667d77774dd5880efcbd7fc1bea Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 27 Jul 2020 16:03:40 +0300 Subject: [PATCH 061/110] Expose SKSurface from ISkiaDrawingContextImpl --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 8 +++++--- src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs | 2 +- src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs | 2 +- src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs | 2 +- src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs | 2 +- src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs | 1 + src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index d818e683c3..b93d0f8868 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -44,7 +44,7 @@ namespace Avalonia.Skia /// /// Canvas to draw to. /// - public SKCanvas Canvas; + public SKSurface Surface; /// /// Dpi of drawings. @@ -81,8 +81,8 @@ namespace Avalonia.Skia _grContext = createInfo.GrContext; if (_grContext != null) Monitor.Enter(_grContext); - - Canvas = createInfo.Canvas; + Surface = createInfo.Surface; + Canvas = createInfo.Surface.Canvas; if (Canvas == null) { @@ -102,8 +102,10 @@ namespace Avalonia.Skia /// Skia canvas. /// public SKCanvas Canvas { get; } + public SKSurface Surface { get; } SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas; + SKSurface ISkiaDrawingContextImpl.SkSurface => Surface; GRContext ISkiaDrawingContextImpl.GrContext => _grContext; /// diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 8b04676b09..8d35d27a81 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -52,7 +52,7 @@ namespace Avalonia.Skia var createInfo = new DrawingContextImpl.CreateInfo { - Canvas = canvas, + Surface = _framebufferSurface, Dpi = framebuffer.Dpi, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = true diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs index c54d1bd859..a4e2bfed52 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs @@ -16,7 +16,7 @@ namespace Avalonia.Skia /// /// Canvas that will be used to render. /// - SKCanvas Canvas { get; } + SKSurface SkSurface { get; } /// /// Scaling factor. diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index e0b7019672..2bb739f372 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -49,7 +49,7 @@ namespace Avalonia.Skia } public GRContext GrContext { get; } - public SKCanvas Canvas => _surface.Canvas; + public SKSurface SkSurface => _surface; public double ScaleFactor => _glSession.Scaling; } diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs index 94e513b2fd..ef5da5eb08 100644 --- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs @@ -27,7 +27,7 @@ namespace Avalonia.Skia var nfo = new DrawingContextImpl.CreateInfo { GrContext = session.GrContext, - Canvas = session.Canvas, + Surface = session.SkSurface, Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = true diff --git a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs b/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs index a9b91384d7..38fa5a5253 100644 --- a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs @@ -7,5 +7,6 @@ namespace Avalonia.Skia { SKCanvas SkCanvas { get; } GRContext GrContext { get; } + SKSurface SkSurface { get; } } } diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 588f7bee6c..27b29c6e1e 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -70,7 +70,7 @@ namespace Avalonia.Skia var createInfo = new DrawingContextImpl.CreateInfo { - Canvas = _canvas, + Surface = _surface, Dpi = Dpi, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = _disableLcdRendering, From 2bec4c14c5b77bf2b94f5943d311d18b78d2ce1b Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 27 Jul 2020 15:14:10 +0200 Subject: [PATCH 062/110] Fixes SKFontStyleSlant to FontStyle conversion --- src/Avalonia.Visuals/Media/FontStyle.cs | 8 ++++---- src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs | 3 ++- src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs | 11 +++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/FontStyle.cs b/src/Avalonia.Visuals/Media/FontStyle.cs index cbc92b1a9f..b9d04bf9ff 100644 --- a/src/Avalonia.Visuals/Media/FontStyle.cs +++ b/src/Avalonia.Visuals/Media/FontStyle.cs @@ -11,13 +11,13 @@ namespace Avalonia.Media Normal, /// - /// An oblique font. + /// An italic font. /// - Oblique, + Italic, /// - /// An italic font. + /// An oblique font. /// - Italic, + Oblique } } diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs index d36baf331d..7ca44e7282 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs @@ -56,7 +56,8 @@ namespace Avalonia.Skia continue; } - var key = new FontKey(fontFamily.Name, (FontStyle)typeface.FontSlant, (FontWeight)typeface.FontWeight); + var key = new FontKey(fontFamily.Name, typeface.FontSlant.ToAvalonia(), + (FontWeight)typeface.FontWeight); typeFaceCollection.AddTypeface(key, typeface); } diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs index ec7e0a67ed..1e772ef067 100644 --- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs +++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs @@ -138,6 +138,17 @@ namespace Avalonia.Skia } } + public static FontStyle ToAvalonia(this SKFontStyleSlant slant) + { + return slant switch + { + SKFontStyleSlant.Upright => FontStyle.Normal, + SKFontStyleSlant.Italic => FontStyle.Italic, + SKFontStyleSlant.Oblique => FontStyle.Oblique, + _ => throw new ArgumentOutOfRangeException(nameof (slant), slant, null) + }; + } + public static SKPath Clone(this SKPath src) { return src != null ? new SKPath(src) : null; From 54553b44d39d7e32d7980da4b7d9276bdbd69b94 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 27 Jul 2020 16:48:41 +0200 Subject: [PATCH 063/110] Make npm silent --- nukebuild/Build.cs | 4 +++- .../Remote/HtmlTransport/webapp/webpack.config.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index fe877dc49c..fbfbf47e1b 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -128,7 +128,9 @@ partial class Build : NukeBuild { var webappDir = RootDirectory / "src" / "Avalonia.DesignerSupport" / "Remote" / "HtmlTransport" / "webapp"; - NpmTasks.NpmInstall(c => c.SetWorkingDirectory(webappDir)); + NpmTasks.NpmInstall(c => c + .SetWorkingDirectory(webappDir) + .SetArgumentConfigurator(a => a.Add("--silent"))); NpmTasks.NpmRun(c => c .SetWorkingDirectory(webappDir) .SetCommand("dist")); diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/webpack.config.js b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/webpack.config.js index 3057f269a5..3750351165 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/webpack.config.js +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/webpack.config.js @@ -93,7 +93,7 @@ const config = { plugins: [ new Printer(), - new CleanWebpackPlugin([path.resolve(__dirname, 'build')]), + new CleanWebpackPlugin([path.resolve(__dirname, 'build')], { verbose: false }), new MiniCssExtractPlugin({ filename: "[name].[chunkhash]h" + ".css", From 8e1443384f5b8323178a5102e3fcdb1891008f5f Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 27 Jul 2020 17:25:33 +0200 Subject: [PATCH 064/110] Add app compat baseline --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/Avalonia.Visuals/ApiCompatBaseline.txt diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt new file mode 100644 index 0000000000..00618448a2 --- /dev/null +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -0,0 +1,4 @@ +Compat issues with assembly Avalonia.Visuals: +EnumValuesMustMatch : Enum value 'Avalonia.Media.FontStyle Avalonia.Media.FontStyle.Italic' is (System.Int32)1 in the implementation but (System.Int32)2 in the contract. +EnumValuesMustMatch : Enum value 'Avalonia.Media.FontStyle Avalonia.Media.FontStyle.Oblique' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. +Total Issues: 2 From 385dd5227155342ed5c1fb142bcbfcce21312584 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 27 Jul 2020 13:33:15 -0300 Subject: [PATCH 065/110] remove apicompat from backend impl checks. --- .../Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj | 3 +-- src/Skia/Avalonia.Skia/Avalonia.Skia.csproj | 3 +-- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj index 22599200bc..48095a4c25 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj +++ b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj @@ -7,6 +7,5 @@ - - + diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj index ef955eb8be..ac029f1062 100644 --- a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj +++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj @@ -16,6 +16,5 @@ - - + diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index bcbdde322c..7fc24d4d3d 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -8,6 +8,5 @@ - - + From 307ab82292bf4eed46e390e7d77def7acc28cbcb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 27 Jul 2020 13:37:01 -0300 Subject: [PATCH 066/110] remove opengl apicompat checks. --- src/Avalonia.OpenGL/Avalonia.OpenGL.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index 2e23f24deb..d761e60c07 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -9,7 +9,5 @@ - - - + From d759435c05a089086484d664752b5a35297e903d Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Mon, 27 Jul 2020 21:52:51 +0300 Subject: [PATCH 067/110] Reverts changing colors for default theme by '36c5ad4621d372cf932f1259c77a23c4d1cb6962' --- src/Avalonia.Themes.Default/TitleBar.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Default/TitleBar.xaml b/src/Avalonia.Themes.Default/TitleBar.xaml index 4dba5b4ba4..7f8ed24076 100644 --- a/src/Avalonia.Themes.Default/TitleBar.xaml +++ b/src/Avalonia.Themes.Default/TitleBar.xaml @@ -5,7 +5,7 @@ diff --git a/src/Avalonia.Themes.Fluent/DataValidationErrors.xaml b/src/Avalonia.Themes.Fluent/DataValidationErrors.xaml index f4145a51f5..88c6b661f1 100644 --- a/src/Avalonia.Themes.Fluent/DataValidationErrors.xaml +++ b/src/Avalonia.Themes.Fluent/DataValidationErrors.xaml @@ -1,5 +1,16 @@ - + diff --git a/src/Avalonia.Themes.Fluent/TextBox.xaml b/src/Avalonia.Themes.Fluent/TextBox.xaml index 0327e776e3..be5e21d03d 100644 --- a/src/Avalonia.Themes.Fluent/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/TextBox.xaml @@ -1,4 +1,13 @@ + + + + + + 0,0,0,4 @@ -42,7 +51,7 @@ @@ -136,7 +145,7 @@ diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 3f1ec289d1..b0fbcc76d2 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -67,9 +67,9 @@ namespace ControlCatalog public override void Initialize() { - AvaloniaXamlLoader.Load(this); - Styles.Insert(0, FluentDark); + + AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index af95e3c356..efc90357ed 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -1,9 +1,7 @@ + x:Class="ControlCatalog.MainView"> diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml index 304782dbf9..392ccb57c3 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml @@ -43,7 +43,7 @@ - + diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index d775eb9635..c39f414b44 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -29,7 +29,8 @@ namespace ControlCatalog.Pages var screens = w.Screens.All; var scaling = ((IRenderRoot)w).RenderScaling; - Pen p = new Pen(Brushes.Black); + var drawBrush = Brushes.Green; + Pen p = new Pen(drawBrush); if (screens != null) foreach (Screen screen in screens) { @@ -53,19 +54,19 @@ namespace ControlCatalog.Pages }; text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}"; - context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height), text); + context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height), text); text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"; - context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text); + context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text); text.Text = $"Scaling: {screen.PixelDensity * 100}%"; - context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text); + context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text); text.Text = $"Primary: {screen.Primary}"; - context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text); + context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text); text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"; - context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text); + context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text); } context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10)); diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml index 4b8edcf98c..4a1c196917 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -12,7 +12,7 @@ @@ -49,7 +49,7 @@ From 037e63d9ab7145893b684b28d2ec62ed614a8a92 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 30 Jul 2020 10:48:29 -0300 Subject: [PATCH 090/110] add unit test for specific scenario where datacontext changes tip before remvoed from visual tree. --- .../ToolTipTests.cs | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index 67df6343af..e52e7a487b 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Disposables; +using Avalonia.Markup.Xaml; using Avalonia.Platform; using Avalonia.Threading; using Avalonia.UnitTests; @@ -66,34 +67,33 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Should_Close_When_Tip_Is_Changed() + public void Should_Close_When_Tip_Is_Opened_And_Detached_From_Visual_Tree() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window(); - - var panel = new Panel(); - - var target = new Decorator() - { - [ToolTip.TipProperty] = "Tip", - [ToolTip.ShowDelayProperty] = 0 - }; + var xaml = @" + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); - panel.Children.Add(target); - - window.Content = panel; - + window.DataContext = new ToolTipViewModel(); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); + var target = window.Find("PART_target"); + var panel = window.Find("PART_panel"); + Assert.True((target as IVisual).IsAttachedToVisualTree); _mouseHelper.Enter(target); Assert.True(ToolTip.GetIsOpen(target)); - - ToolTip.SetTip(target, ""); + + panel.Children.Remove(target); Assert.False(ToolTip.GetIsOpen(target)); } @@ -242,4 +242,9 @@ namespace Avalonia.Controls.UnitTests } } } + + internal class ToolTipViewModel + { + public string Tip => "Tip"; + } } From eb132105e43adef9ce6d2feb4675dbab30b1904d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 30 Jul 2020 10:52:47 -0300 Subject: [PATCH 091/110] restore tooltipservice. failing unit test. --- src/Avalonia.Controls/ToolTipService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index 587bbb6aa6..569697304f 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -29,7 +29,6 @@ namespace Avalonia.Controls control.PointerEnter -= ControlPointerEnter; control.PointerLeave -= ControlPointerLeave; control.DetachedFromVisualTree -= ControlDetaching; - Close(control); } if (e.NewValue != null) From 49e41b959438fa4ccab96fcc4b18cef696b859c9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 30 Jul 2020 13:00:05 -0300 Subject: [PATCH 092/110] prevent tooltipgetting stuck. --- src/Avalonia.Controls/ToolTip.cs | 1 + src/Avalonia.Controls/ToolTipService.cs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index b458b15c64..cf0652247f 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -66,6 +66,7 @@ namespace Avalonia.Controls static ToolTip() { TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged); + IsOpenProperty.Changed.Subscribe(ToolTipService.Instance.TipOpenChanged); IsOpenProperty.Changed.Subscribe(IsOpenChanged); } diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index 569697304f..e2a0f9e50c 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -28,20 +28,33 @@ namespace Avalonia.Controls { control.PointerEnter -= ControlPointerEnter; control.PointerLeave -= ControlPointerLeave; - control.DetachedFromVisualTree -= ControlDetaching; } if (e.NewValue != null) { control.PointerEnter += ControlPointerEnter; control.PointerLeave += ControlPointerLeave; + } + } + + internal void TipOpenChanged(AvaloniaPropertyChangedEventArgs e) + { + var control = (Control)e.Sender; + + if (e.OldValue is false && e.NewValue is true) + { control.DetachedFromVisualTree += ControlDetaching; } + else if(e.OldValue is true && e.NewValue is false) + { + control.DetachedFromVisualTree -= ControlDetaching; + } } private void ControlDetaching(object sender, VisualTreeAttachmentEventArgs e) { var control = (Control)sender; + control.DetachedFromVisualTree -= ControlDetaching; Close(control); } From 151ea1b1819cccd6b3f184b72e7d8c8283192e37 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 30 Jul 2020 23:59:29 +0200 Subject: [PATCH 093/110] Limit amount of reentrant dispatcher calls. --- .../Threading/AvaloniaScheduler.cs | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs index 0bfc713ba0..397826df53 100644 --- a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs +++ b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs @@ -9,6 +9,16 @@ namespace Avalonia.Threading /// public class AvaloniaScheduler : LocalScheduler { + /// + /// Users can schedule actions on the dispatcher thread while being on the correct thread already. + /// We are optimizing this case by invoking user callback immediately which can lead to stack overflows in certain cases. + /// To prevent this we are limiting amount of reentrant calls to before we will + /// schedule on a dispatcher anyway. + /// + private const int MaxReentrantSchedules = 32; + + private int _reentrancyGuard; + /// /// The instance of the . /// @@ -24,31 +34,58 @@ namespace Avalonia.Threading /// public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { - var composite = new CompositeDisposable(2); + IDisposable PostOnDispatcher() + { + var composite = new CompositeDisposable(2); + + var cancellation = new CancellationDisposable(); + + Dispatcher.UIThread.Post(() => + { + if (!cancellation.Token.IsCancellationRequested) + { + composite.Add(action(this, state)); + } + }, DispatcherPriority.DataBind); + + composite.Add(cancellation); + + return composite; + } + if (dueTime == TimeSpan.Zero) { if (!Dispatcher.UIThread.CheckAccess()) { - var cancellation = new CancellationDisposable(); - Dispatcher.UIThread.Post(() => - { - if (!cancellation.Token.IsCancellationRequested) - { - composite.Add(action(this, state)); - } - }, DispatcherPriority.DataBind); - composite.Add(cancellation); + return PostOnDispatcher(); } else { - return action(this, state); + if (_reentrancyGuard >= MaxReentrantSchedules) + { + return PostOnDispatcher(); + } + + try + { + _reentrancyGuard++; + + return action(this, state); + } + finally + { + _reentrancyGuard--; + } } } else { + var composite = new CompositeDisposable(2); + composite.Add(DispatcherTimer.RunOnce(() => composite.Add(action(this, state)), dueTime)); + + return composite; } - return composite; } } } From b941fd675f7c4f4bd7d382f85dd438f1b260b17d Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Thu, 30 Jul 2020 21:19:33 -0400 Subject: [PATCH 094/110] Default theme: Replace DropDown with ComboBox in ManagedFileChooser --- src/Avalonia.Dialogs/ManagedFileChooser.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Dialogs/ManagedFileChooser.xaml b/src/Avalonia.Dialogs/ManagedFileChooser.xaml index ea45e83812..227cc1afc0 100644 --- a/src/Avalonia.Dialogs/ManagedFileChooser.xaml +++ b/src/Avalonia.Dialogs/ManagedFileChooser.xaml @@ -66,7 +66,7 @@
- Date: Thu, 30 Jul 2020 21:42:30 -0400 Subject: [PATCH 095/110] Default theme: Add ToggleSwitch resources --- .../Accents/BaseDark.xaml | 2 + .../Accents/BaseLight.xaml | 2 + src/Avalonia.Themes.Default/TabItem.xaml | 2 +- src/Avalonia.Themes.Default/ToggleSwitch.xaml | 45 ++++++++++++++----- src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 2 - 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index ffe3e92202..4274f7ee4d 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -58,6 +58,8 @@ + + 1,1,1,1 0.5 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index c0e5f47eed..b1c0946d0f 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -61,6 +61,8 @@ + + 1 0.5 diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml index 92482a564c..6e344ce58e 100644 --- a/src/Avalonia.Themes.Default/TabItem.xaml +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -2,7 +2,7 @@ - - @@ -262,10 +289,6 @@ - - diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index 219f6f6742..56249f5986 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -6,8 +6,6 @@ 6 6 154 - 20 - 0
From bf346ff16eb9f52c5529c8f58bb20bca895d9812 Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Thu, 30 Jul 2020 23:10:34 -0400 Subject: [PATCH 096/110] Replace StaticResource with SolidColorBrush+DynamicResource to fix tests --- src/Avalonia.Themes.Default/ToggleSwitch.xaml | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml index 48e92d2283..5c0a3b99f3 100644 --- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml @@ -8,38 +8,38 @@ 154 0 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 376ee843caf68bc986627b75b175b4a57e6fbdd5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 1 Aug 2020 13:55:15 +0300 Subject: [PATCH 097/110] Fixed concurrency issues in RenderLoop --- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 40 ++++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 789d028a3a..26011e4b92 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -18,6 +18,7 @@ namespace Avalonia.Rendering { private readonly IDispatcher _dispatcher; private List _items = new List(); + private List _itemsCopy = new List(); private IRenderTimer _timer; private int _inTick; private int _inUpdate; @@ -63,11 +64,14 @@ namespace Avalonia.Rendering Contract.Requires(i != null); Dispatcher.UIThread.VerifyAccess(); - _items.Add(i); - - if (_items.Count == 1) + lock (_items) { - Timer.Tick += TimerTick; + _items.Add(i); + + if (_items.Count == 1) + { + Timer.Tick += TimerTick; + } } } @@ -76,15 +80,17 @@ namespace Avalonia.Rendering { Contract.Requires(i != null); Dispatcher.UIThread.VerifyAccess(); - - _items.Remove(i); - - if (_items.Count == 0) + lock (_items) { - Timer.Tick -= TimerTick; + _items.Remove(i); + + if (_items.Count == 0) + { + Timer.Tick -= TimerTick; + } } } - + private void TimerTick(TimeSpan time) { if (Interlocked.CompareExchange(ref _inTick, 1, 0) == 0) @@ -129,10 +135,20 @@ namespace Avalonia.Rendering }, DispatcherPriority.Render); } - for(int i = 0; i < _items.Count; i++) + lock (_items) { - _items[i].Render(); + _itemsCopy.Clear(); + foreach (var i in _items) + _itemsCopy.Add(i); } + + for (int i = 0; i < _itemsCopy.Count; i++) + { + _itemsCopy[i].Render(); + } + + _itemsCopy.Clear(); + } catch (Exception ex) { From 475fea4d1d2ae82003cc20e13902081ce8560fd9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 1 Aug 2020 14:04:08 +0300 Subject: [PATCH 098/110] Added SleepLoopRenderTimer because of libnvidia-glcore.so crashes --- .../Rendering/SleepLoopRenderTimer.cs | 33 +++++++++++++++++++ src/Avalonia.X11/X11Platform.cs | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs diff --git a/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs b/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs new file mode 100644 index 0000000000..a44891a888 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs @@ -0,0 +1,33 @@ +using System; +using System.Diagnostics; +using System.Threading; + +namespace Avalonia.Rendering +{ + public class SleepLoopRenderTimer : IRenderTimer + { + public event Action Tick; + + public SleepLoopRenderTimer(int fps) + { + var timeBetweenTicks = TimeSpan.FromSeconds(1d / fps); + new Thread(() => + { + var st = Stopwatch.StartNew(); + var now = st.Elapsed; + var lastTick = now; + + while (true) + { + var timeTillNextTick = lastTick + timeBetweenTicks - now; + if (timeTillNextTick.TotalMilliseconds > 1) + Thread.Sleep(timeTillNextTick); + + + Tick?.Invoke(now); + now = st.Elapsed; + } + }) { IsBackground = true }.Start(); + } + } +} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 9e9c4fd30e..7f3255d4da 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -47,7 +47,7 @@ namespace Avalonia.X11 AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(this) .Bind().ToConstant(new X11PlatformThreading(this)) - .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new SleepLoopRenderTimer(60)) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control)) .Bind().ToFunc(() => KeyboardDevice) From 90aad3b785f4854e768922491bd7ad849fa82a18 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 1 Aug 2020 14:25:50 +0300 Subject: [PATCH 099/110] Stop the loop when there are no subscribers --- .../Rendering/SleepLoopRenderTimer.cs | 65 +++++++++++++++---- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs b/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs index a44891a888..79e029ccd2 100644 --- a/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs @@ -6,28 +6,67 @@ namespace Avalonia.Rendering { public class SleepLoopRenderTimer : IRenderTimer { - public event Action Tick; + private Action _tick; + private int _count; + private readonly object _lock = new object(); + private bool _running; + private readonly Stopwatch _st = Stopwatch.StartNew(); + private readonly TimeSpan _timeBetweenTicks; public SleepLoopRenderTimer(int fps) { - var timeBetweenTicks = TimeSpan.FromSeconds(1d / fps); - new Thread(() => + _timeBetweenTicks = TimeSpan.FromSeconds(1d / fps); + } + + public event Action Tick + { + add { - var st = Stopwatch.StartNew(); - var now = st.Elapsed; - var lastTick = now; + lock (_lock) + { + _tick += value; + _count++; + if (_running) + return; + _running = true; + new Thread(LoopProc) { IsBackground = true }.Start(); + } - while (true) + } + remove + { + lock (_lock) { - var timeTillNextTick = lastTick + timeBetweenTicks - now; - if (timeTillNextTick.TotalMilliseconds > 1) - Thread.Sleep(timeTillNextTick); + _tick -= value; + _count--; + } + } + } + void LoopProc() + { + var now = _st.Elapsed; + var lastTick = now; + + while (true) + { + var timeTillNextTick = lastTick + _timeBetweenTicks - now; + if (timeTillNextTick.TotalMilliseconds > 1) Thread.Sleep(timeTillNextTick); - Tick?.Invoke(now); - now = st.Elapsed; + lock (_lock) + { + if (_count == 0) + { + _running = false; + return; + } } - }) { IsBackground = true }.Start(); + + _tick?.Invoke(now); + now = _st.Elapsed; + } } + + } } From 52d961deed3bff55c46f2d670f26a4ba87068bd2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Aug 2020 11:42:04 -0300 Subject: [PATCH 100/110] remove whitespace. --- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 26011e4b92..b2a6b0da19 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -90,7 +90,7 @@ namespace Avalonia.Rendering } } } - + private void TimerTick(TimeSpan time) { if (Interlocked.CompareExchange(ref _inTick, 1, 0) == 0) From 836203d0337899aacc257eee755b8094aa39fc2d Mon Sep 17 00:00:00 2001 From: amwx Date: Mon, 3 Aug 2020 15:58:34 -0500 Subject: [PATCH 101/110] Fix ArgumentOutOfRange exception --- .../DateTimePickers/DatePickerPresenter.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index 31527ccb16..eec4615736 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -356,10 +356,17 @@ namespace Avalonia.Controls } if (MonthVisible) + { _monthSelector.SelectedValue = dt.Month; - + _monthSelector.FormatDate = dt.Date; + } + if (YearVisible) + { _yearSelector.SelectedValue = dt.Year; + _yearSelector.FormatDate = dt.Date; + } + _suppressUpdateSelection = false; SetInitialFocus(); From 2f39eb16e0d97855be6d80f833478a46dec90e19 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 4 Aug 2020 13:10:44 +0200 Subject: [PATCH 102/110] Stop ItemsSourceView using a cached size. It can mean that `Count` is out-of-date with regards to the inner collection if it's accessed in a collection changed handler before `OnItemsSourceChanged` is fired. It's never been clear to me why ItemsSourceView caches the size; I suspect it's due to the performance characteristics of WinRT and so I think it can be safely removed in Avalonia. Fixes #4409 --- src/Avalonia.Controls/Repeater/ItemsSourceView.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ItemsSourceView.cs b/src/Avalonia.Controls/Repeater/ItemsSourceView.cs index ecf8abc13f..def9301e2d 100644 --- a/src/Avalonia.Controls/Repeater/ItemsSourceView.cs +++ b/src/Avalonia.Controls/Repeater/ItemsSourceView.cs @@ -25,7 +25,6 @@ namespace Avalonia.Controls { private readonly IList _inner; private INotifyCollectionChanged _notifyCollectionChanged; - private int _cachedSize = -1; /// /// Initializes a new instance of the ItemsSourceView class for the specified data source. @@ -54,18 +53,7 @@ namespace Avalonia.Controls /// /// Gets the number of items in the collection. /// - public int Count - { - get - { - if (_cachedSize == -1) - { - _cachedSize = _inner.Count; - } - - return _cachedSize; - } - } + public int Count => _inner.Count; /// /// Gets a value that indicates whether the items source can provide a unique key for each item. @@ -126,7 +114,6 @@ namespace Avalonia.Controls protected void OnItemsSourceChanged(NotifyCollectionChangedEventArgs args) { - _cachedSize = _inner.Count; CollectionChanged?.Invoke(this, args); } From ce0721deb641537cbcbe65c2c89603b19ee2379f Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 4 Aug 2020 23:03:03 -0500 Subject: [PATCH 103/110] Add Default Theme Support --- .../Accents/BaseDark.xaml | 3 + .../Accents/BaseLight.xaml | 3 + src/Avalonia.Themes.Default/DatePicker.xaml | 334 ++++++++++++++++++ src/Avalonia.Themes.Default/DefaultTheme.xaml | 3 + src/Avalonia.Themes.Default/SplitView.xaml | 219 ++++++++++++ src/Avalonia.Themes.Default/TimePicker.xaml | 283 +++++++++++++++ 6 files changed, 845 insertions(+) create mode 100644 src/Avalonia.Themes.Default/DatePicker.xaml create mode 100644 src/Avalonia.Themes.Default/SplitView.xaml create mode 100644 src/Avalonia.Themes.Default/TimePicker.xaml diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index 4274f7ee4d..44dfb9ea48 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -58,6 +58,9 @@ + + + 1,1,1,1 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index b1c0946d0f..9ed3207235 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -61,6 +61,9 @@ + + + 1 diff --git a/src/Avalonia.Themes.Default/DatePicker.xaml b/src/Avalonia.Themes.Default/DatePicker.xaml new file mode 100644 index 0000000000..da878c88e2 --- /dev/null +++ b/src/Avalonia.Themes.Default/DatePicker.xaml @@ -0,0 +1,334 @@ + + + + + 0,0,0,4 + 40 + 40 + 41 + 296 + 456 + 0,3,0,6 + 9,3,0,6 + 0,3,0,6 + 9,3,0,6 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 4e63d1d223..e5b654b490 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -55,4 +55,7 @@ + + + diff --git a/src/Avalonia.Themes.Default/SplitView.xaml b/src/Avalonia.Themes.Default/SplitView.xaml new file mode 100644 index 0000000000..4a9a7be164 --- /dev/null +++ b/src/Avalonia.Themes.Default/SplitView.xaml @@ -0,0 +1,219 @@ + + + + 320 + 48 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/TimePicker.xaml b/src/Avalonia.Themes.Default/TimePicker.xaml new file mode 100644 index 0000000000..6c4d0c97bd --- /dev/null +++ b/src/Avalonia.Themes.Default/TimePicker.xaml @@ -0,0 +1,283 @@ + + + + + 40 + 1 + 1 + 0,0,0,4 + 40 + 41 + 242 + 456 + 0,3,0,6 + 0,3,0,6 + + + + + + + + + + + + + + + + + + + + + + + + + + From 96d1d7358192511886a8e33ea54df17485ce7309 Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 4 Aug 2020 23:14:28 -0500 Subject: [PATCH 104/110] SplitView Default style --- src/Avalonia.Themes.Default/SplitView.xaml | 219 +++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 src/Avalonia.Themes.Default/SplitView.xaml diff --git a/src/Avalonia.Themes.Default/SplitView.xaml b/src/Avalonia.Themes.Default/SplitView.xaml new file mode 100644 index 0000000000..4a9a7be164 --- /dev/null +++ b/src/Avalonia.Themes.Default/SplitView.xaml @@ -0,0 +1,219 @@ + + + + 320 + 48 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 80863f44a9d38731f15c8bb4551d54c153c2b5ca Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 4 Aug 2020 23:15:01 -0500 Subject: [PATCH 105/110] Date/Time Picker default style --- src/Avalonia.Themes.Default/DatePicker.xaml | 334 ++++++++++++++++++++ src/Avalonia.Themes.Default/TimePicker.xaml | 283 +++++++++++++++++ 2 files changed, 617 insertions(+) create mode 100644 src/Avalonia.Themes.Default/DatePicker.xaml create mode 100644 src/Avalonia.Themes.Default/TimePicker.xaml diff --git a/src/Avalonia.Themes.Default/DatePicker.xaml b/src/Avalonia.Themes.Default/DatePicker.xaml new file mode 100644 index 0000000000..da878c88e2 --- /dev/null +++ b/src/Avalonia.Themes.Default/DatePicker.xaml @@ -0,0 +1,334 @@ + + + + + 0,0,0,4 + 40 + 40 + 41 + 296 + 456 + 0,3,0,6 + 9,3,0,6 + 0,3,0,6 + 9,3,0,6 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/TimePicker.xaml b/src/Avalonia.Themes.Default/TimePicker.xaml new file mode 100644 index 0000000000..6c4d0c97bd --- /dev/null +++ b/src/Avalonia.Themes.Default/TimePicker.xaml @@ -0,0 +1,283 @@ + + + + + 40 + 1 + 1 + 0,0,0,4 + 40 + 41 + 242 + 456 + 0,3,0,6 + 0,3,0,6 + + + + + + + + + + + + + + + + + + + + + + + + + + From 7ef5078610930498485f7d578d816418ad838734 Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 4 Aug 2020 23:15:12 -0500 Subject: [PATCH 106/110] Default theme support --- src/Avalonia.Themes.Default/Accents/BaseDark.xaml | 3 +++ src/Avalonia.Themes.Default/Accents/BaseLight.xaml | 3 +++ src/Avalonia.Themes.Default/DefaultTheme.xaml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index 4274f7ee4d..44dfb9ea48 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -58,6 +58,9 @@ + + + 1,1,1,1 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index b1c0946d0f..9ed3207235 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -61,6 +61,9 @@ + + + 1 diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 4e63d1d223..e5b654b490 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -55,4 +55,7 @@ + + + From 4f7a701ab79044ba29a23e65a48446271ff81dfc Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Aug 2020 06:35:37 +0200 Subject: [PATCH 107/110] Reuse the shaped text runs as much as possible --- .../Media/TextFormatting/TextFormatterImpl.cs | 79 +++++++++++++------ .../Media/TextFormatting/TextLayout.cs | 5 +- .../Media/TextFormatting/TextLineImpl.cs | 4 +- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 10 +-- 4 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 9318fcc68e..b116249fd4 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -31,7 +31,8 @@ namespace Avalonia.Media.TextFormatting case TextWrapping.WrapWithOverflow: case TextWrapping.Wrap: { - textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties); + textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties, + nextLineBreak); break; } default: @@ -118,7 +119,7 @@ namespace Avalonia.Media.TextFormatting /// The text run's. /// The length to split at. /// The split text runs. - internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length) + internal static SplitTextRunsResult SplitTextRuns(List textRuns, int length) { var currentLength = 0; @@ -134,13 +135,13 @@ namespace Avalonia.Media.TextFormatting var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i; - var first = new ShapedTextCharacters[firstCount]; + var first = new List(firstCount); if (firstCount > 1) { for (var j = 0; j < i; j++) { - first[j] = textRuns[j]; + first.Add(textRuns[j]); } } @@ -148,7 +149,7 @@ namespace Avalonia.Media.TextFormatting if (currentLength + currentRun.GlyphRun.Characters.Length == length) { - var second = new ShapedTextCharacters[secondCount]; + var second = new List(secondCount); var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0; @@ -156,11 +157,11 @@ namespace Avalonia.Media.TextFormatting { for (var j = 0; j < secondCount; j++) { - second[j] = textRuns[i + j + offset]; + second.Add(textRuns[i + j + offset]); } } - first[i] = currentRun; + first.Add(currentRun); return new SplitTextRunsResult(first, second); } @@ -168,22 +169,22 @@ namespace Avalonia.Media.TextFormatting { secondCount++; - var second = new ShapedTextCharacters[secondCount]; + var second = new List(secondCount); + + var split = currentRun.Split(length - currentLength); + + first.Add(split.First); + + second.Add(split.Second); if (secondCount > 0) { for (var j = 1; j < secondCount; j++) { - second[j] = textRuns[i + j]; + second.Add(textRuns[i + j]); } } - var split = currentRun.Split(length - currentLength); - - first[i] = split.First; - - second[0] = split.Second; - return new SplitTextRunsResult(first, second); } } @@ -201,7 +202,7 @@ namespace Avalonia.Media.TextFormatting /// /// The formatted text runs. /// - private static IReadOnlyList FetchTextRuns(ITextSource textSource, + private static List FetchTextRuns(ITextSource textSource, int firstTextSourceIndex, TextLineBreak previousLineBreak, out TextLineBreak nextLineBreak) { nextLineBreak = default; @@ -212,8 +213,10 @@ namespace Avalonia.Media.TextFormatting if (previousLineBreak != null) { - foreach (var shapedCharacters in previousLineBreak.RemainingCharacters) + for (var index = 0; index < previousLineBreak.RemainingCharacters.Count; index++) { + var shapedCharacters = previousLineBreak.RemainingCharacters[index]; + if (shapedCharacters == null) { continue; @@ -225,6 +228,14 @@ namespace Avalonia.Media.TextFormatting { var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap); + if (++index < previousLineBreak.RemainingCharacters.Count) + { + for (; index < previousLineBreak.RemainingCharacters.Count; index++) + { + splitResult.Second.Add(previousLineBreak.RemainingCharacters[index]); + } + } + nextLineBreak = new TextLineBreak(splitResult.Second); return splitResult.First; @@ -323,9 +334,10 @@ namespace Avalonia.Media.TextFormatting /// The text range that is covered by the text runs. /// The paragraph width. /// The text paragraph properties. + /// The current line break if the line was explicitly broken. /// The wrapped text line. - private static TextLine PerformTextWrapping(IReadOnlyList textRuns, TextRange textRange, - double paragraphWidth, TextParagraphProperties paragraphProperties) + private static TextLine PerformTextWrapping(List textRuns, TextRange textRange, + double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak currentLineBreak) { var availableWidth = paragraphWidth; var currentWidth = 0.0; @@ -388,8 +400,22 @@ namespace Avalonia.Media.TextFormatting var textLineMetrics = TextLineMetrics.Create(splitResult.First, new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties); - var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ? - new TextLineBreak(splitResult.Second) : + var remainingCharacters = splitResult.Second; + + if (currentLineBreak?.RemainingCharacters != null) + { + if (remainingCharacters != null) + { + remainingCharacters.AddRange(currentLineBreak.RemainingCharacters); + } + else + { + remainingCharacters = new List(currentLineBreak.RemainingCharacters); + } + } + + var lineBreak = remainingCharacters != null && remainingCharacters.Count > 0 ? + new TextLineBreak(remainingCharacters) : null; return new TextLineImpl(splitResult.First, textLineMetrics, lineBreak); @@ -403,7 +429,10 @@ namespace Avalonia.Media.TextFormatting } return new TextLineImpl(textRuns, - TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)); + TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties), + currentLineBreak?.RemainingCharacters != null ? + new TextLineBreak(currentLineBreak.RemainingCharacters) : + null); } /// @@ -434,7 +463,7 @@ namespace Avalonia.Media.TextFormatting internal readonly struct SplitTextRunsResult { - public SplitTextRunsResult(IReadOnlyList first, IReadOnlyList second) + public SplitTextRunsResult(List first, List second) { First = first; @@ -447,7 +476,7 @@ namespace Avalonia.Media.TextFormatting /// /// The first text runs. /// - public IReadOnlyList First { get; } + public List First { get; } /// /// Gets the second text runs. @@ -455,7 +484,7 @@ namespace Avalonia.Media.TextFormatting /// /// The second text runs. /// - public IReadOnlyList Second { get; } + public List Second { get; } } private struct TextRunEnumerator diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index 14602a2560..fa7d6cb4bf 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -183,7 +183,10 @@ namespace Avalonia.Media.TextFormatting var glyphRun = TextShaper.Current.ShapeText(new ReadOnlySlice(s_empty, startingIndex, 1), properties.Typeface, properties.FontRenderingEmSize, properties.CultureInfo); - var textRuns = new[] { new ShapedTextCharacters(glyphRun, _paragraphProperties.DefaultTextRunProperties) }; + var textRuns = new List + { + new ShapedTextCharacters(glyphRun, _paragraphProperties.DefaultTextRunProperties) + }; return new TextLineImpl(textRuns, TextLineMetrics.Create(textRuns, new TextRange(startingIndex, 1), MaxWidth, _paragraphProperties)); diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 435752160e..8b44e32c48 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -6,9 +6,9 @@ namespace Avalonia.Media.TextFormatting { internal class TextLineImpl : TextLine { - private readonly IReadOnlyList _textRuns; + private readonly List _textRuns; - public TextLineImpl(IReadOnlyList textRuns, TextLineMetrics lineMetrics, + public TextLineImpl(List textRuns, TextLineMetrics lineMetrics, TextLineBreak lineBreak = null, bool hasCollapsed = false) { _textRuns = textRuns; diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index b0384a1fdf..61075171fe 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -123,10 +123,7 @@ namespace Avalonia.Skia return; } - if (offsetBuffer == null) - { - offsetBuffer = new Vector[glyphPositions.Length]; - } + offsetBuffer ??= new Vector[glyphPositions.Length]; var offsetX = position.XOffset * textScale; @@ -138,10 +135,7 @@ namespace Avalonia.Skia private static void SetAdvance(ReadOnlySpan glyphPositions, int index, double textScale, ref double[] advanceBuffer) { - if (advanceBuffer == null) - { - advanceBuffer = new double[glyphPositions.Length]; - } + advanceBuffer ??= new double[glyphPositions.Length]; // Depends on direction of layout // advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale; From a91d6322112c679e96dc8968280bf62832139b9e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 5 Aug 2020 16:33:09 +0200 Subject: [PATCH 108/110] Move ListBox page view model to its own file. Also remove `SelectedItem` and `SelectedItems` in favor of `SelectionModel`. --- samples/ControlCatalog/Pages/ListBoxPage.xaml | 3 +- .../ControlCatalog/Pages/ListBoxPage.xaml.cs | 68 +------------------ .../ViewModels/ListBoxPageViewModel.cs | 65 ++++++++++++++++++ 3 files changed, 68 insertions(+), 68 deletions(-) create mode 100644 samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index f4d81418ac..edf3d41bf5 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -11,8 +11,7 @@ Spacing="16"> (Enumerable.Range(1, 10000).Select(i => GenerateItem())); - SelectedItems = new ObservableCollection(); - - AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem())); - - RemoveItemCommand = ReactiveCommand.Create(() => - { - while (SelectedItems.Count > 0) - { - Items.Remove(SelectedItems[0]); - } - }); - - SelectRandomItemCommand = ReactiveCommand.Create(() => - { - var random = new Random(); - - SelectedItem = Items[random.Next(Items.Count - 1)]; - }); - } - - public ObservableCollection Items { get; } - - private string _selectedItem; - - public string SelectedItem - { - get { return _selectedItem; } - set { this.RaiseAndSetIfChanged(ref _selectedItem, value); } - } - - - public ObservableCollection SelectedItems { get; } - - public ReactiveCommand AddItemCommand { get; } - - public ReactiveCommand RemoveItemCommand { get; } - - public ReactiveCommand SelectRandomItemCommand { get; } - - public SelectionMode SelectionMode - { - get => _selectionMode; - set - { - SelectedItems.Clear(); - this.RaiseAndSetIfChanged(ref _selectionMode, value); - } - } - - private string GenerateItem() => $"Item {_counter++.ToString()}"; - } } } diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs new file mode 100644 index 0000000000..6bdb5c0103 --- /dev/null +++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive; +using Avalonia.Controls; +using ReactiveUI; + +namespace ControlCatalog.ViewModels +{ + public class ListBoxPageViewModel : ReactiveObject + { + private int _counter; + private SelectionMode _selectionMode; + + public ListBoxPageViewModel() + { + Items = new ObservableCollection(Enumerable.Range(1, 10000).Select(i => GenerateItem())); + Selection = new SelectionModel(); + Selection.Select(1); + + AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem())); + + RemoveItemCommand = ReactiveCommand.Create(() => + { + while (Selection.SelectedItems.Count > 0) + { + Items.Remove((string)Selection.SelectedItems.First()); + } + }); + + SelectRandomItemCommand = ReactiveCommand.Create(() => + { + var random = new Random(); + + using (Selection.Update()) + { + Selection.ClearSelection(); + Selection.Select(random.Next(Items.Count - 1)); + } + }); + } + + public ObservableCollection Items { get; } + + public SelectionModel Selection { get; } + + public ReactiveCommand AddItemCommand { get; } + + public ReactiveCommand RemoveItemCommand { get; } + + public ReactiveCommand SelectRandomItemCommand { get; } + + public SelectionMode SelectionMode + { + get => _selectionMode; + set + { + Selection.ClearSelection(); + this.RaiseAndSetIfChanged(ref _selectionMode, value); + } + } + + private string GenerateItem() => $"Item {_counter++.ToString()}"; + } +} From d2af884aedf68f4534e2afa20e4c282cc4e7eba2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 5 Aug 2020 17:13:02 +0200 Subject: [PATCH 109/110] Added a failing test for #4272. --- .../Utils/SelectedItemsSyncTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs index 3ab5950974..4aa7e24aa7 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs @@ -208,6 +208,20 @@ namespace Avalonia.Controls.UnitTests.Utils target.SetItems(new[] { "foo", "bar", "baz" })); } + [Fact] + public void Selected_Items_Can_Be_Set_Before_SelectionModel_Source() + { + var model = new SelectionModel(); + var target = new SelectedItemsSync(model); + var items = new AvaloniaList { "foo", "bar", "baz" }; + var selectedItems = new AvaloniaList { "bar" }; + + target.SetItems(selectedItems); + model.Source = items; + + Assert.Equal(new IndexPath(1), model.SelectedIndex); + } + private static SelectedItemsSync CreateTarget( IEnumerable items = null) { From 0f9ac73b4fc43dde5384e7eb4825a87e0266446f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 5 Aug 2020 17:13:18 +0200 Subject: [PATCH 110/110] Handle uninitialized Source in SelectedItemsSync. If the `SelectionModel` passed to `SelectedItemsSync` has not yet had a `Source` assigned, set a flag to write the selected items to the `SelectionModel` when `Source` is set. Fixes #4272 --- .../Utils/SelectedItemsSync.cs | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Utils/SelectedItemsSync.cs b/src/Avalonia.Controls/Utils/SelectedItemsSync.cs index c127771990..91cef9fe64 100644 --- a/src/Avalonia.Controls/Utils/SelectedItemsSync.cs +++ b/src/Avalonia.Controls/Utils/SelectedItemsSync.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using Avalonia.Collections; @@ -16,6 +17,7 @@ namespace Avalonia.Controls.Utils private IList? _items; private bool _updatingItems; private bool _updatingModel; + private bool _initializeOnSourceAssignment; public SelectedItemsSync(ISelectionModel model) { @@ -63,10 +65,18 @@ namespace Avalonia.Controls.Utils _updatingModel = true; _items = items; - using (Model.Update()) + if (Model.Source is object) { - Model.ClearSelection(); - Add(items); + using (Model.Update()) + { + Model.ClearSelection(); + Add(items); + } + } + else if (!_initializeOnSourceAssignment) + { + Model.PropertyChanged += SelectionModelPropertyChanged; + _initializeOnSourceAssignment = true; } if (_items is INotifyCollectionChanged incc2) @@ -86,9 +96,11 @@ namespace Avalonia.Controls.Utils if (_items != null) { + Model.PropertyChanged -= SelectionModelPropertyChanged; Model.SelectionChanged -= SelectionModelSelectionChanged; Model = model; Model.SelectionChanged += SelectionModelSelectionChanged; + _initializeOnSourceAssignment = false; try { @@ -175,6 +187,25 @@ namespace Avalonia.Controls.Utils } } + private void SelectionModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_initializeOnSourceAssignment && + _items != null && + e.PropertyName == nameof(SelectionModel.Source)) + { + try + { + _updatingModel = true; + Add(_items); + _initializeOnSourceAssignment = false; + } + finally + { + _updatingModel = false; + } + } + } + private void SelectionModelSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) { if (_updatingModel)