You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

206 lines
8.1 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using UnityEngine;
  7. using CommonMark;
  8. using CommonMark.Syntax;
  9. using UnityEngine.UI;
  10. using TMPro;
  11. using CustomUI.BeatSaber;
  12. namespace BSIPA_ModList.UI.ViewControllers
  13. {
  14. [RequireComponent(/*typeof(ScrollRect),*/ typeof(RectTransform))]
  15. public class MarkdownView : MonoBehaviour
  16. {
  17. private class TagTypeComponent : MonoBehaviour
  18. {
  19. internal BlockTag Tag;
  20. }
  21. private string markdown = "";
  22. public string Markdown
  23. {
  24. get => markdown;
  25. set
  26. {
  27. markdown = value;
  28. UpdateMd();
  29. }
  30. }
  31. public RectTransform rectTransform => GetComponent<RectTransform>();
  32. //private ScrollRect view;
  33. private RectTransform content;
  34. private CommonMarkSettings settings;
  35. public MarkdownView()
  36. {
  37. settings = CommonMarkSettings.Default.Clone();
  38. settings.AdditionalFeatures = CommonMarkAdditionalFeatures.All;
  39. settings.RenderSoftLineBreaksAsLineBreaks = false;
  40. settings.UriResolver = ResolveUri;
  41. }
  42. public Func<string, bool> HasEmbeddedImage;
  43. private string ResolveUri(string arg)
  44. {
  45. var name = arg.Substring(3);
  46. if (!arg.StartsWith("!::") && !arg.StartsWith("w::"))
  47. { // !:: means embedded, w:: means web
  48. // this block is for when neither is specified
  49. Logger.md.Debug($"Resolving nonspecific URI {arg}");
  50. // check if its embedded
  51. if (HasEmbeddedImage != null && HasEmbeddedImage(arg))
  52. return "!::" + arg;
  53. else
  54. return "w::" + arg;
  55. }
  56. Logger.md.Debug($"Resolved specific URI {arg}");
  57. return arg;
  58. }
  59. protected void Awake()
  60. {
  61. content = new GameObject("Content Wrapper").AddComponent<RectTransform>();
  62. content.SetParent(transform);
  63. var contentLayout = content.gameObject.AddComponent<LayoutElement>();
  64. var contentFitter = content.gameObject.AddComponent<ContentSizeFitter>();
  65. contentFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
  66. contentFitter.verticalFit = ContentSizeFitter.FitMode.Unconstrained;
  67. contentLayout.preferredWidth = 100f; // to be adjusted
  68. content.sizeDelta = new Vector2(100f,100f);
  69. /*view = GetComponent<ScrollRect>();
  70. view.content = content;
  71. view.vertical = true;
  72. view.horizontal = false;
  73. view.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;
  74. view.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;*/
  75. }
  76. private void UpdateMd()
  77. {
  78. Clear();
  79. var doc = CommonMarkConverter.Parse(markdown, settings);
  80. Stack<RectTransform> layout = new Stack<RectTransform>();
  81. layout.Push(content);
  82. TextMeshProUGUI currentText = null;
  83. foreach (var node in doc.AsEnumerable())
  84. {
  85. Logger.md.Debug($"node {node}");
  86. if (node.Block != null)
  87. {
  88. var block = node.Block;
  89. void BlockNode(string name, float spacing, bool isVertical)
  90. {
  91. var type = isVertical ? typeof(VerticalLayoutGroup) : typeof(HorizontalLayoutGroup);
  92. if (node.IsOpening)
  93. {
  94. Logger.md.Debug($"Creating block container {name}");
  95. currentText = null;
  96. var go = new GameObject(name, typeof(RectTransform), type);
  97. var vlayout = go.GetComponent<RectTransform>();
  98. vlayout.SetParent(layout.Peek());
  99. go.AddComponent<TagTypeComponent>().Tag = block.Tag;
  100. layout.Push(vlayout);
  101. if (isVertical)
  102. {
  103. var vl = go.GetComponent<VerticalLayoutGroup>();
  104. vl.childControlHeight = vl.childControlWidth =
  105. vl.childForceExpandHeight = vl.childForceExpandWidth = false;
  106. vl.spacing = spacing;
  107. }
  108. else
  109. {
  110. var hl = go.GetComponent<HorizontalLayoutGroup>();
  111. hl.childControlHeight = hl.childControlWidth =
  112. hl.childForceExpandHeight = hl.childForceExpandWidth = false;
  113. hl.spacing = spacing;
  114. }
  115. }
  116. else if (node.IsClosing)
  117. {
  118. currentText = null;
  119. layout.Pop();
  120. }
  121. }
  122. switch (block.Tag)
  123. {
  124. case BlockTag.Document:
  125. BlockNode("DocumentRoot", 10f, true);
  126. break;
  127. case BlockTag.SetextHeading:
  128. BlockNode("Heading1", .1f, false);
  129. break;
  130. case BlockTag.AtxHeading:
  131. BlockNode("Heading2", .1f, false);
  132. break;
  133. case BlockTag.Paragraph:
  134. BlockNode("Paragraph", .1f, false);
  135. break;
  136. // TODO: add the rest of the tag types
  137. }
  138. }
  139. else if (node.Inline != null)
  140. { // inline element
  141. var inl = node.Inline;
  142. switch (inl.Tag)
  143. {
  144. case InlineTag.String:
  145. if (currentText == null)
  146. {
  147. Logger.md.Debug($"Adding new text element");
  148. var btt = layout.Peek().gameObject.GetComponent<TagTypeComponent>().Tag;
  149. currentText = BeatSaberUI.CreateText(layout.Peek(), "", Vector2.zero);
  150. //var le = currentText.gameObject.AddComponent<LayoutElement>();
  151. switch (btt)
  152. {
  153. case BlockTag.List:
  154. case BlockTag.ListItem:
  155. case BlockTag.Paragraph:
  156. currentText.fontSize = 3.5f;
  157. currentText.enableWordWrapping = true;
  158. break;
  159. case BlockTag.AtxHeading:
  160. currentText.fontSize = 4f;
  161. currentText.enableWordWrapping = true;
  162. break;
  163. case BlockTag.SetextHeading:
  164. currentText.fontSize = 4.5f;
  165. currentText.enableWordWrapping = true;
  166. break;
  167. // TODO: add other relevant types
  168. }
  169. }
  170. Logger.md.Debug($"Appending '{inl.LiteralContent}' to current element");
  171. currentText.text += inl.LiteralContent;
  172. break;
  173. }
  174. }
  175. }
  176. }
  177. private void Clear()
  178. {
  179. foreach (Transform child in content)
  180. Destroy(child.gameObject);
  181. }
  182. }
  183. }