A game framework written with osu! in mind.
at master 189 lines 7.1 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6using System.Linq; 7using Markdig.Syntax.Inlines; 8using osu.Framework.Allocation; 9using osu.Framework.Extensions.IEnumerableExtensions; 10using osu.Framework.Graphics.Sprites; 11using osuTK.Graphics; 12 13namespace osu.Framework.Graphics.Containers.Markdown 14{ 15 /// <summary> 16 /// Markdown text flow container. 17 /// </summary> 18 public class MarkdownTextFlowContainer : CustomizableTextContainer, IMarkdownTextComponent 19 { 20 public float TotalTextWidth => Padding.TotalHorizontal + FlowingChildren.Sum(x => x.BoundingBox.Size.X); 21 22 [Resolved] 23 private IMarkdownTextComponent parentTextComponent { get; set; } 24 25 public MarkdownTextFlowContainer() 26 { 27 RelativeSizeAxes = Axes.X; 28 AutoSizeAxes = Axes.Y; 29 } 30 31 protected void AddDrawable(Drawable drawable) 32 => base.AddText("[" + AddPlaceholder(drawable) + "]"); 33 34 public new void AddText(string text, Action<SpriteText> creationParameters = null) 35 => base.AddText(Escape(text), creationParameters); 36 37 public new IEnumerable<Drawable> AddParagraph(string text, Action<SpriteText> creationParameters = null) 38 => base.AddParagraph(Escape(text), creationParameters); 39 40 public void AddInlineText(ContainerInline container) 41 { 42 foreach (var single in container) 43 { 44 switch (single) 45 { 46 case LiteralInline literal: 47 var text = literal.Content.ToString(); 48 49 if (container.GetPrevious(literal) is HtmlInline && container.GetNext(literal) is HtmlInline) 50 AddHtmlInLineText(text, literal); 51 else if (container.GetNext(literal) is HtmlEntityInline entityInLine) 52 AddHtmlEntityInlineText(text, entityInLine); 53 else 54 { 55 switch (literal.Parent) 56 { 57 case EmphasisInline _: 58 var parent = literal.Parent; 59 60 var emphases = new List<string>(); 61 62 while (parent is EmphasisInline e) 63 { 64 emphases.Add(e.DelimiterCount == 2 ? new string(e.DelimiterChar, 2) : e.DelimiterChar.ToString()); 65 parent = parent.Parent; 66 } 67 68 addEmphasis(text, emphases); 69 70 break; 71 72 case LinkInline linkInline: 73 { 74 if (!linkInline.IsImage) 75 AddLinkText(text, linkInline); 76 break; 77 } 78 79 default: 80 AddText(text); 81 break; 82 } 83 } 84 85 break; 86 87 case CodeInline codeInline: 88 AddCodeInLine(codeInline); 89 break; 90 91 case LinkInline linkInline when linkInline.IsImage: 92 AddImage(linkInline); 93 break; 94 95 case HtmlInline _: 96 case HtmlEntityInline _: 97 // Handled by the next literal 98 break; 99 100 case LineBreakInline lineBreak: 101 if (lineBreak.IsHard) 102 NewParagraph(); 103 else 104 NewLine(); 105 break; 106 107 case ContainerInline innerContainer: 108 AddInlineText(innerContainer); 109 break; 110 111 case AutolinkInline autoLink: 112 AddAutoLink(autoLink); 113 break; 114 115 default: 116 AddNotImplementedInlineText(single); 117 break; 118 } 119 } 120 } 121 122 protected virtual void AddHtmlInLineText(string text, LiteralInline literalInline) 123 => AddText(text, t => t.Colour = Color4.MediumPurple); 124 125 protected virtual void AddHtmlEntityInlineText(string text, HtmlEntityInline entityInLine) 126 => AddText(text, t => t.Colour = Color4.GreenYellow); 127 128 protected virtual void AddLinkText(string text, LinkInline linkInline) 129 => AddDrawable(new MarkdownLinkText(text, linkInline)); 130 131 protected virtual void AddAutoLink(AutolinkInline autolinkInline) 132 => AddDrawable(new MarkdownLinkText(autolinkInline)); 133 134 protected virtual void AddCodeInLine(CodeInline codeInline) 135 => AddText(codeInline.Content, t => { t.Colour = Color4.Orange; }); 136 137 protected virtual void AddImage(LinkInline linkInline) 138 => AddDrawable(new MarkdownImage(linkInline.Url)); 139 140 protected virtual void AddNotImplementedInlineText(Inline inline) 141 => AddText(inline.GetType() + " not implemented.", t => t.Colour = Color4.Red); 142 143 private void addEmphasis(string text, List<string> emphases) 144 { 145 bool hasItalic = false; 146 bool hasBold = false; 147 148 foreach (var e in emphases) 149 { 150 switch (e) 151 { 152 case "*": 153 case "_": 154 hasItalic = true; 155 break; 156 157 case "**": 158 case "__": 159 hasBold = true; 160 break; 161 } 162 } 163 164 var textDrawable = CreateEmphasisedSpriteText(hasBold, hasItalic); 165 textDrawable.Text = text; 166 167 AddDrawable(textDrawable); 168 } 169 170 protected override SpriteText CreateSpriteText() => parentTextComponent.CreateSpriteText(); 171 172 /// <summary> 173 /// Creates an emphasised <see cref="SpriteText"/>. 174 /// </summary> 175 /// <param name="bold">Whether the text should be emboldened.</param> 176 /// <param name="italic">Whether the text should be italicised.</param> 177 /// <returns>The <see cref="SpriteText"/> with emphases applied.</returns> 178 protected virtual SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) 179 { 180 var textDrawable = CreateSpriteText(); 181 182 textDrawable.Font = textDrawable.Font.With(weight: bold ? "Bold" : null, italics: italic); 183 184 return textDrawable; 185 } 186 187 SpriteText IMarkdownTextComponent.CreateSpriteText() => CreateSpriteText(); 188 } 189}