From c3f01b696b5770e68e443fccad436e893c9c8010 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 21 Jul 2020 16:22:35 +0200 Subject: [PATCH 01/43] Make sure we always hit the outer sides at the start/end --- src/Avalonia.Visuals/Media/CharacterHit.cs | 2 + .../Media/TextFormatting/TextLineImpl.cs | 18 ++++- .../Media/TextFormatting/TextLineTests.cs | 75 ++++++++++++++++++- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Visuals/Media/CharacterHit.cs b/src/Avalonia.Visuals/Media/CharacterHit.cs index ba691dad6e..f018b2d8a9 100644 --- a/src/Avalonia.Visuals/Media/CharacterHit.cs +++ b/src/Avalonia.Visuals/Media/CharacterHit.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; namespace Avalonia.Media { @@ -9,6 +10,7 @@ namespace Avalonia.Media /// The CharacterHit structure provides information about the index of the first /// character that got hit as well as information about leading or trailing edge. /// + [DebuggerDisplay("CharacterHit({FirstCharacterIndex}, {TrailingLength})")] public readonly struct CharacterHit : IEquatable { /// diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index f73a7be759..435752160e 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -236,7 +236,7 @@ namespace Avalonia.Media.TextFormatting var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - if (codepointIndex >= TextRange.Start + TextRange.Length) + if (codepointIndex > TextRange.End) { return false; // Cannot go forward anymore } @@ -249,11 +249,14 @@ namespace Avalonia.Media.TextFormatting var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); - nextCharacterHit = characterHit.TrailingLength != 0 ? + var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength == + TextRange.Length; + + nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ? foundCharacterHit : new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength); - if (nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex) + if (isAtEnd || nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex) { return true; } @@ -272,6 +275,13 @@ namespace Avalonia.Media.TextFormatting /// private bool TryFindPreviousCharacterHit(CharacterHit characterHit, out CharacterHit previousCharacterHit) { + if (characterHit.FirstCharacterIndex == TextRange.Start) + { + previousCharacterHit = new CharacterHit(TextRange.Start); + + return true; + } + previousCharacterHit = characterHit; var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; @@ -354,7 +364,7 @@ namespace Avalonia.Media.TextFormatting return new ShapedTextCharacters(glyphRun, textRun.Properties); } - + /// /// Gets the shaped width of specified shaped text characters. /// diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 09cbf3bf08..575a58f337 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Avalonia.Media; using Avalonia.Media.TextFormatting; @@ -101,7 +102,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex); - Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength); + Assert.Equal(0, previousCharacterHit.TrailingLength); previousCharacterHit = new CharacterHit(clusters[^1], text.Length - clusters[^1]); @@ -119,7 +120,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex); - Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength); + Assert.Equal(0, previousCharacterHit.TrailingLength); } } @@ -272,6 +273,76 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void TestNext() + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + + var textSource = new SingleBufferTextSource("Text from memory", defaultProperties); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + var characterHits = new List(); + + var currentCharacterHit = new CharacterHit(0); + + characterHits.Add(currentCharacterHit); + + var nextCharacterHit = textLine.GetNextCaretCharacterHit(currentCharacterHit); + + while (nextCharacterHit != currentCharacterHit) + { + currentCharacterHit = nextCharacterHit; + + characterHits.Add(currentCharacterHit); + + nextCharacterHit = textLine.GetNextCaretCharacterHit(currentCharacterHit); + } + } + } + + [Fact] + public void TestPrevious() + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + + var text = "Text from memory"; + + var textSource = new SingleBufferTextSource(text, defaultProperties); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + var characterHits = new List(); + + var currentCharacterHit = new CharacterHit(text.Length); + + characterHits.Add(currentCharacterHit); + + var nextCharacterHit = textLine.GetPreviousCaretCharacterHit(currentCharacterHit); + + while (nextCharacterHit != currentCharacterHit) + { + currentCharacterHit = nextCharacterHit; + + characterHits.Add(currentCharacterHit); + + nextCharacterHit = textLine.GetPreviousCaretCharacterHit(currentCharacterHit); + } + } + } + private static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface From f00219379cba4717f2abda16fbc8d9a7ed94481b Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Tue, 21 Jul 2020 19:48:48 -0400 Subject: [PATCH 02/43] Detailed MenuPage examples --- samples/ControlCatalog/Pages/MenuPage.xaml | 31 +++++++++++++++- .../ViewModels/MenuPageViewModel.cs | 36 ++++++++++--------- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index de9ea34e80..e9d2301e89 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -20,7 +20,9 @@ - + + + @@ -52,6 +54,33 @@ + + + + Mixed + + + + + + + + + + + + + + + + + + + + + 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 03/43] 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 04/43] 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 05/43] 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 @@ + + - + - + - - - - - - - - - - - - - - - - - - - - - -