using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using CommonMark;
using CommonMark.Syntax;
using UnityEngine.UI;
using TMPro;
using CustomUI.BeatSaber;
using IPA.Utilities;
using System.Reflection;
using UnityEngine.EventSystems;
using System.Diagnostics;
using System.Collections;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace BSIPA_ModList.UI.ViewControllers
{
///
/// A UI component that renders CommonMark Markdown in-game.
///
[RequireComponent(typeof(RectTransform))]
public class MarkdownView : MonoBehaviour
{
private class TagTypeComponent : MonoBehaviour
{
internal BlockTag Tag;
internal HeadingData hData;
internal ListData lData;
internal int listCount;
internal int listIndex;
}
private string markdown = "";
private bool mdDirty = false;
///
/// The text to be rendered.
///
///
/// When this is assigned, the object is marked dirty. It will re-render on the next Update tick.
///
/// the text to render as Markdown
public string Markdown
{
get => markdown;
set
{
markdown = value;
mdDirty = true;
}
}
///
/// A convenience property to access the on the this is on.
///
/// the associated with this component
public RectTransform rectTransform => GetComponent();
private ScrollView scrView;
private RectTransform content;
private RectTransform viewport;
private CommonMarkSettings settings;
///
/// Creates a new . Should never be called directly. Instead, use .
///
public MarkdownView()
{
settings = CommonMarkSettings.Default.Clone();
settings.AdditionalFeatures = CommonMarkAdditionalFeatures.All;
settings.RenderSoftLineBreaksAsLineBreaks = false;
settings.UriResolver = ResolveUri;
}
///
/// This function will be called whenever attempting to resolve an image URI, to ensure that the image exists in the embedded assembly.
///
/// a delegate for the function to call
public Func 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;
}
private static string GetLinkUri(string uri)
{
if (uri[0] == '!')
{
Logger.md.Error($"Cannot link to embedded resource in mod description");
return null;
}
else
return uri.Substring(3);
}
private static Stream ConsolasAssetBundleFontStream => Assembly.GetExecutingAssembly().GetManifestResourceStream("BSIPA_ModList.Bundles.consolas.font");
private static AssetBundleCreateRequest _bundleRequest;
private static AssetBundle _bundle;
private static AssetBundle Bundle
{
get
{
if (_bundle == null && _bundleRequest != null)
throw new InvalidOperationException("Asset bundle is being loaded asynchronously; please wait for that to complete");
if (_bundle == null)
_bundle = AssetBundle.LoadFromStream(ConsolasAssetBundleFontStream);
return _bundle;
}
}
private static AssetBundleRequest _consolasRequest;
private static TMP_FontAsset _unsetConsolas;
private static TMP_FontAsset _consolas;
private static TMP_FontAsset Consolas
{
get
{
if (_unsetConsolas == null && _consolasRequest != null)
throw new InvalidOperationException("Asset is being loaded asynchronously; please wait for that to complete");
if (_unsetConsolas == null)
_unsetConsolas = Bundle?.LoadAsset("CONSOLAS");
if (_consolas == null && _unsetConsolas != null)
_consolas = SetupFont(_unsetConsolas);
return _consolas;
}
}
private static TMP_FontAsset SetupFont(TMP_FontAsset f)
{
var originalFont = Resources.FindObjectsOfTypeAll().Last(f2 => f2.name == "Teko-Medium SDF No Glow");
var matCopy = Instantiate(originalFont.material);
matCopy.mainTexture = f.material.mainTexture;
matCopy.mainTextureOffset = f.material.mainTextureOffset;
matCopy.mainTextureScale = f.material.mainTextureScale;
f.material = matCopy;
f = Instantiate(f);
MaterialReferenceManager.AddFontAsset(f);
return f;
}
internal static void StartLoadResourcesAsync()
{
SharedCoroutineStarter.instance.StartCoroutine(LoadResourcesAsync());
}
private static IEnumerator LoadResourcesAsync()
{
Logger.md.Debug("Starting to load resources");
_bundleRequest = AssetBundle.LoadFromStreamAsync(ConsolasAssetBundleFontStream);
yield return _bundleRequest;
_bundle = _bundleRequest.assetBundle;
Logger.md.Debug("Bundle loaded");
_consolasRequest = _bundle.LoadAssetAsync("CONSOLAS");
yield return _consolasRequest;
_unsetConsolas = _consolasRequest.asset as TMP_FontAsset;
Logger.md.Debug("Font loaded");
}
internal void Awake()
{
if (Consolas == null)
Logger.md.Error($"Loading of Consolas font failed");
gameObject.SetActive(false);
var vpgo = new GameObject("Viewport");
viewport = vpgo.AddComponent();
viewport.SetParent(transform);
viewport.localPosition = Vector2.zero;
viewport.anchorMin = Vector2.zero;
viewport.anchorMax = Vector2.one;
viewport.anchoredPosition = new Vector2(.5f, .5f);
viewport.sizeDelta = Vector2.zero;
var vpmask = vpgo.AddComponent();
var vpim = vpgo.AddComponent(); // supposedly Mask needs an Image?
vpmask.showMaskGraphic = false;
vpim.color = Color.white;
vpim.sprite = WhitePixel;
vpim.material = CustomUI.Utilities.UIUtilities.NoGlowMaterial;
content = new GameObject("Content Wrapper").AddComponent();
content.SetParent(viewport);
content.gameObject.AddComponent();
content.localPosition = Vector2.zero;
content.anchorMin = new Vector2(0f, 1f);
content.anchorMax = new Vector2(1f, 1f);
content.anchoredPosition = Vector2.zero;
var contentLayout = content.gameObject.AddComponent();
var contentFitter = content.gameObject.AddComponent();
contentFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
contentLayout.childControlHeight = true;
contentLayout.childControlWidth = false;
contentLayout.childForceExpandHeight = false;
contentLayout.childForceExpandWidth = true;
contentLayout.childAlignment = TextAnchor.UpperCenter;
contentLayout.spacing = 0f;
var pageUp = Instantiate(Resources.FindObjectsOfTypeAll