diff --git a/components/MarkdownTextBlock/src/MarkdownTextBlock.xaml.cs b/components/MarkdownTextBlock/src/MarkdownTextBlock.xaml.cs index 9996fb6b2..ee550f025 100644 --- a/components/MarkdownTextBlock/src/MarkdownTextBlock.xaml.cs +++ b/components/MarkdownTextBlock/src/MarkdownTextBlock.xaml.cs @@ -106,7 +106,8 @@ private void Build() // Default block renderers _renderer.ObjectRenderers.Add(new CodeBlockRenderer()); - _renderer.ObjectRenderers.Add(new ListRenderer()); + _renderer.ObjectRenderers.Add(new ListRenderer()); + _renderer.ObjectRenderers.Add(new ListItemRenderer()); _renderer.ObjectRenderers.Add(new HeadingRenderer()); _renderer.ObjectRenderers.Add(new ParagraphRenderer()); _renderer.ObjectRenderers.Add(new QuoteBlockRenderer()); diff --git a/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ListItemRenderer.cs b/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ListItemRenderer.cs new file mode 100644 index 000000000..609408a0a --- /dev/null +++ b/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ListItemRenderer.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Markdig.Syntax; + +namespace CommunityToolkit.Labs.WinUI.MarkdownTextBlock.Renderers.ObjectRenderers; + +internal class ListItemRenderer : UWPObjectRenderer +{ + protected override void Write(WinUIRenderer renderer, ListItemBlock listItem) + { + if (renderer == null) throw new ArgumentNullException(nameof(renderer)); + if (listItem == null) throw new ArgumentNullException(nameof(listItem)); + + renderer.WriteChildren(listItem); + } +} diff --git a/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ListRenderer.cs b/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ListRenderer.cs index aa8034bb5..c48cdd6c2 100644 --- a/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ListRenderer.cs +++ b/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ListRenderer.cs @@ -2,31 +2,73 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Markdig.Syntax; +using System.Globalization; using CommunityToolkit.Labs.WinUI.MarkdownTextBlock.TextElements; +using Markdig.Syntax; +using RomanNumerals; namespace CommunityToolkit.Labs.WinUI.MarkdownTextBlock.Renderers.ObjectRenderers; internal class ListRenderer : UWPObjectRenderer { + public const string UnorderedListDot = "• "; + protected override void Write(WinUIRenderer renderer, ListBlock listBlock) { - if (renderer == null) throw new ArgumentNullException(nameof(renderer)); - if (listBlock == null) throw new ArgumentNullException(nameof(listBlock)); - - var list = new MyList(listBlock); + int index = 1; + bool isOrdered = false; + BulletType bulletType = BulletType.Circle; + if (listBlock.IsOrdered) + { + isOrdered = true; + bulletType = ToOrderedBulletType(listBlock.BulletType); - renderer.Push(list); + if (listBlock.OrderedStart != null && listBlock.DefaultOrderedStart != listBlock.OrderedStart) + { + int.TryParse(listBlock.OrderedStart, NumberStyles.Number, NumberFormatInfo.InvariantInfo, out index); + } + } - foreach (var item in listBlock) + foreach (var listItem in listBlock) { - var listItemBlock = (ListItemBlock)item; - var listItem = new MyBlockContainer(listItemBlock); - renderer.Push(listItem); - renderer.WriteChildren(listItemBlock); - renderer.Pop(); + renderer.PushListBullet(GetBulletString(isOrdered, bulletType, index)); + renderer.Write(listItem); + renderer.PopListBullet(); + index++; } + } + + internal static BulletType ToOrderedBulletType(char bullet) + { + return bullet switch + { + '1' => BulletType.Number, + 'a' => BulletType.LowerAlpha, + 'A' => BulletType.UpperAlpha, + 'i' => BulletType.LowerRoman, + 'I' => BulletType.UpperRoman, + _ => BulletType.Number, + }; + } - renderer.Pop(); + private static string GetBulletString(bool isOrdered, BulletType bulletType, int index) + { + if (isOrdered) + { + return bulletType switch + { + BulletType.Number => $"{index}. ", + BulletType.LowerAlpha => $"{index.ToAlphabetical()}. ", + BulletType.UpperAlpha => $"{index.ToAlphabetical().ToUpper(CultureInfo.CurrentCulture)}. ", + BulletType.LowerRoman => $"{index.ToRomanNumerals().ToLower(CultureInfo.CurrentCulture)} ", + BulletType.UpperRoman => $"{index.ToRomanNumerals().ToUpper(CultureInfo.CurrentCulture)} ", + BulletType.Circle => UnorderedListDot, + _ => $"{index}. " + }; + } + else + { + return UnorderedListDot; + } } } diff --git a/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ParagraphRenderer.cs b/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ParagraphRenderer.cs index b4f9f20f6..95f47fe3c 100644 --- a/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ParagraphRenderer.cs +++ b/components/MarkdownTextBlock/src/Renderers/ObjectRenderers/ParagraphRenderer.cs @@ -14,7 +14,7 @@ protected override void Write(WinUIRenderer renderer, ParagraphBlock obj) if (renderer == null) throw new ArgumentNullException(nameof(renderer)); if (obj == null) throw new ArgumentNullException(nameof(obj)); - var paragraph = new MyParagraph(obj); + var paragraph = new MyParagraph(obj, renderer); // set style renderer.Push(paragraph); renderer.WriteLeafInline(obj); diff --git a/components/MarkdownTextBlock/src/Renderers/WinUIRenderer.cs b/components/MarkdownTextBlock/src/Renderers/WinUIRenderer.cs index c4e00543b..324672c0c 100644 --- a/components/MarkdownTextBlock/src/Renderers/WinUIRenderer.cs +++ b/components/MarkdownTextBlock/src/Renderers/WinUIRenderer.cs @@ -17,6 +17,8 @@ public class WinUIRenderer : RendererBase private readonly Stack _stack = new Stack(); private char[] _buffer; private MarkdownConfig _config = MarkdownConfig.Default; + private readonly Stack _listBullets = new(); + public MyFlowDocument FlowDocument { get; private set; } public MarkdownConfig Config { @@ -134,6 +136,29 @@ public void WriteText(string? text, int offset, int length) } } + public void PushListBullet(string bullet) + { + _listBullets.Push(bullet); + } + + public string PeekListBullet() + { + return _listBullets.Count > 0 ? _listBullets.Peek() : string.Empty; + } + + public int GetListBulletCount() + { + return _listBullets.Count; + } + + public void PopListBullet() + { + if (_listBullets.Count > 0) + { + _listBullets.Pop(); + } + } + private static void AddInline(IAddChild parent, IAddChild inline) { parent.AddChild(inline); diff --git a/components/MarkdownTextBlock/src/TextElements/BulletType.cs b/components/MarkdownTextBlock/src/TextElements/BulletType.cs new file mode 100644 index 000000000..f04971840 --- /dev/null +++ b/components/MarkdownTextBlock/src/TextElements/BulletType.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace CommunityToolkit.Labs.WinUI.MarkdownTextBlock.TextElements; + +internal enum BulletType +{ + Circle, + Number, + LowerAlpha, + UpperAlpha, + LowerRoman, + UpperRoman +} diff --git a/components/MarkdownTextBlock/src/TextElements/MyList.cs b/components/MarkdownTextBlock/src/TextElements/MyList.cs deleted file mode 100644 index e1e9b2ab8..000000000 --- a/components/MarkdownTextBlock/src/TextElements/MyList.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Markdig.Syntax; -using RomanNumerals; -using System.Globalization; - -namespace CommunityToolkit.Labs.WinUI.MarkdownTextBlock.TextElements; - -internal class MyList : IAddChild -{ - private Paragraph _paragraph; - private InlineUIContainer _container; - private StackPanel _stackPanel; - private ListBlock _listBlock; - private BulletType _bulletType; - private bool _isOrdered; - private int _startIndex = 1; - private int _index = 1; - private const string _dot = "• "; - - public TextElement TextElement - { - get => _paragraph; - } - - public MyList(ListBlock listBlock) - { - _paragraph = new Paragraph(); - _container = new InlineUIContainer(); - _stackPanel = new StackPanel(); - _listBlock = listBlock; - - if (listBlock.IsOrdered) - { - _isOrdered = true; - _bulletType = ToBulletType(listBlock.BulletType); - - if (listBlock.OrderedStart != null && (listBlock.DefaultOrderedStart != listBlock.OrderedStart)) - { - _startIndex = int.Parse(listBlock.OrderedStart, NumberFormatInfo.InvariantInfo); - _index = _startIndex; - } - } - - _stackPanel.Orientation = Orientation.Vertical; - _stackPanel.Margin = new Thickness(left: 0, top: 8, right: 0, bottom: 8); - _container.Child = _stackPanel; - _paragraph.Inlines.Add(_container); - } - - public void AddChild(IAddChild child) - { - var grid = new Grid(); - grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(20, GridUnitType.Pixel) }); - grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(10, GridUnitType.Pixel) }); - grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); - string bullet; - if (_isOrdered) - { - bullet = _bulletType switch - { - BulletType.Number => $"{_index}. ", - BulletType.LowerAlpha => $"{_index.ToAlphabetical()}. ", - BulletType.UpperAlpha => $"{_index.ToAlphabetical().ToUpper()}. ", - BulletType.LowerRoman => $"{_index.ToRomanNumerals().ToLower()} ", - BulletType.UpperRoman => $"{_index.ToRomanNumerals().ToUpper()} ", - BulletType.Circle => _dot, - _ => _dot - }; - _index++; - } - else - { - bullet = _dot; - } - var textBlock = new TextBlock() - { - Text = bullet, - }; - textBlock.SetValue(Grid.ColumnProperty, 0); - textBlock.VerticalAlignment = VerticalAlignment.Top; - textBlock.TextAlignment = TextAlignment.Right; - // Mark this Raw so narrator users will not set focus on it - AutomationProperties.SetAccessibilityView(textBlock, AccessibilityView.Raw); - grid.Children.Add(textBlock); - var flowDoc = new MyFlowDocument(); - flowDoc.AddChild(child); - - flowDoc.RichTextBlock.SetValue(Grid.ColumnProperty, 2); - flowDoc.RichTextBlock.Padding = new Thickness(0); - flowDoc.RichTextBlock.VerticalAlignment = VerticalAlignment.Top; - grid.Children.Add(flowDoc.RichTextBlock); - - _stackPanel.Children.Add(grid); - } - - private BulletType ToBulletType(char bullet) - { - // Gets or sets the type of the bullet (e.g: '1', 'a', 'A', 'i', 'I'). - switch (bullet) - { - case '1': - return BulletType.Number; - case 'a': - return BulletType.LowerAlpha; - case 'A': - return BulletType.UpperAlpha; - case 'i': - return BulletType.LowerRoman; - case 'I': - return BulletType.UpperRoman; - default: - return BulletType.Circle; - } - } -} - -internal enum BulletType -{ - Circle, - Number, - LowerAlpha, - UpperAlpha, - LowerRoman, - UpperRoman -} diff --git a/components/MarkdownTextBlock/src/TextElements/MyParagraph.cs b/components/MarkdownTextBlock/src/TextElements/MyParagraph.cs index c3b47c0ef..26b6ea9f2 100644 --- a/components/MarkdownTextBlock/src/TextElements/MyParagraph.cs +++ b/components/MarkdownTextBlock/src/TextElements/MyParagraph.cs @@ -2,12 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using CommunityToolkit.Labs.WinUI.MarkdownTextBlock.Renderers; using Markdig.Syntax; namespace CommunityToolkit.Labs.WinUI.MarkdownTextBlock.TextElements; internal class MyParagraph : IAddChild { + private readonly WinUIRenderer _renderer; private ParagraphBlock _paragraphBlock; private Paragraph _paragraph; @@ -16,10 +18,27 @@ public TextElement TextElement get => _paragraph; } - public MyParagraph(ParagraphBlock paragraphBlock) + public MyParagraph(ParagraphBlock paragraphBlock, WinUIRenderer renderer) { _paragraphBlock = paragraphBlock; _paragraph = new Paragraph(); + _renderer = renderer; + + // Lists are plain Paragraph_s, one per item. + // This is so that you can select across list items. + Thickness margin = new Thickness(0, 8, 0, 8); // renderer.Config.Themes.BlockMargin; + int bulletCount = renderer.GetListBulletCount(); + margin.Left += 30 * bulletCount; + _paragraph.Margin = margin; + + if (bulletCount != 0) + { + string bullet = renderer.PeekListBullet(); + Run bulletRun = new Run { Text = bullet + "\t" }; + + _paragraph.Inlines.Add(bulletRun); + _paragraph.TextIndent = -30; + } } public void AddChild(IAddChild child)