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.
 
 
 
 

212 lines
8.4 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using CommonMark;
using CommonMark.Syntax;
using UnityEngine.UI;
using TMPro;
using CustomUI.BeatSaber;
namespace BSIPA_ModList.UI.ViewControllers
{
[RequireComponent(/*typeof(ScrollRect),*/ typeof(RectTransform))]
public class MarkdownView : MonoBehaviour
{
private class TagTypeComponent : MonoBehaviour
{
internal BlockTag Tag;
}
private string markdown = "";
public string Markdown
{
get => markdown;
set
{
markdown = value;
UpdateMd();
}
}
public RectTransform rectTransform => GetComponent<RectTransform>();
//private ScrollRect view;
private RectTransform content;
private CommonMarkSettings settings;
public MarkdownView()
{
settings = CommonMarkSettings.Default.Clone();
settings.AdditionalFeatures = CommonMarkAdditionalFeatures.All;
settings.RenderSoftLineBreaksAsLineBreaks = false;
settings.UriResolver = ResolveUri;
}
public Func<string, bool> HasEmbeddedImage;
private string ResolveUri(string arg)
{
var name = arg.Substring(3);
if (!arg.StartsWith("!::") && !arg.StartsWith("w::"))
{ // !:: means embedded, w:: means web
// this block is for when neither is specified
Logger.md.Debug($"Resolving nonspecific URI {arg}");
// check if its embedded
if (HasEmbeddedImage != null && HasEmbeddedImage(arg))
return "!::" + arg;
else
return "w::" + arg;
}
Logger.md.Debug($"Resolved specific URI {arg}");
return arg;
}
protected void Awake()
{
content = new GameObject("Content Wrapper").AddComponent<RectTransform>();
content.SetParent(transform);
content.localPosition = Vector2.zero;
content.anchorMin = Vector2.zero;
content.anchorMax = Vector2.one;
var contentLayout = content.gameObject.AddComponent<LayoutElement>();
var contentFitter = content.gameObject.AddComponent<ContentSizeFitter>();
contentFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
contentFitter.verticalFit = ContentSizeFitter.FitMode.Unconstrained;
contentLayout.preferredWidth = 100f; // to be adjusted
content.sizeDelta = new Vector2(100f,100f);
content.gameObject.AddComponent<TagTypeComponent>();
/*view = GetComponent<ScrollRect>();
view.content = content;
view.vertical = true;
view.horizontal = false;
view.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;
view.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;*/
}
private void UpdateMd()
{
Clear();
var doc = CommonMarkConverter.Parse(markdown, settings);
Stack<RectTransform> layout = new Stack<RectTransform>();
layout.Push(content);
TextMeshProUGUI currentText = null;
foreach (var node in doc.AsEnumerable())
{
Logger.md.Debug($"node {node}");
if (node.Block != null)
{
var block = node.Block;
void BlockNode(string name, float spacing, bool isVertical, bool isDoc = false)
{
var type = isVertical ? typeof(VerticalLayoutGroup) : typeof(HorizontalLayoutGroup);
if (node.IsOpening)
{
Logger.md.Debug($"Creating block container {name}");
currentText = null;
var go = new GameObject(name, typeof(RectTransform), type);
var vlayout = go.GetComponent<RectTransform>();
vlayout.SetParent(layout.Peek());
//if (isDoc)
vlayout.anchoredPosition = Vector2.zero;
go.AddComponent<TagTypeComponent>().Tag = block.Tag;
layout.Push(vlayout);
if (isVertical)
{
var vl = go.GetComponent<VerticalLayoutGroup>();
vl.childControlHeight = vl.childControlWidth =
vl.childForceExpandHeight = vl.childForceExpandWidth = false;
vl.spacing = spacing;
}
else
{
var hl = go.GetComponent<HorizontalLayoutGroup>();
hl.childControlHeight = hl.childControlWidth =
hl.childForceExpandHeight = hl.childForceExpandWidth = false;
hl.spacing = spacing;
}
}
else if (node.IsClosing)
{
currentText = null;
layout.Pop();
}
}
switch (block.Tag)
{
case BlockTag.Document:
BlockNode("DocumentRoot", .2f, true, true);
break;
case BlockTag.SetextHeading:
BlockNode("Heading1", .1f, false);
break;
case BlockTag.AtxHeading:
BlockNode("Heading2", .1f, false);
break;
case BlockTag.Paragraph:
BlockNode("Paragraph", .1f, false);
break;
// TODO: add the rest of the tag types
}
}
else if (node.Inline != null)
{ // inline element
var inl = node.Inline;
switch (inl.Tag)
{
case InlineTag.String:
if (currentText == null)
{
Logger.md.Debug($"Adding new text element");
var btt = layout.Peek().gameObject.GetComponent<TagTypeComponent>().Tag;
currentText = BeatSaberUI.CreateText(layout.Peek(), "", Vector2.zero);
//var le = currentText.gameObject.AddComponent<LayoutElement>();
switch (btt)
{
case BlockTag.List:
case BlockTag.ListItem:
case BlockTag.Paragraph:
currentText.fontSize = 3.5f;
currentText.enableWordWrapping = true;
break;
case BlockTag.AtxHeading:
currentText.fontSize = 4f;
currentText.enableWordWrapping = true;
break;
case BlockTag.SetextHeading:
currentText.fontSize = 4.5f;
currentText.enableWordWrapping = true;
break;
// TODO: add other relevant types
}
}
Logger.md.Debug($"Appending '{inl.LiteralContent}' to current element");
currentText.text += inl.LiteralContent;
break;
}
}
}
}
private void Clear()
{
foreach (Transform child in content)
Destroy(child.gameObject);
}
}
}