Гипертекст

Published
В редакторе триггеров мне часто доводится использовать гипертекст - когда нажатие на определенное слово приводит к каким-либо действиям. Увы в стандартном арсенале движка нет инструмента для работы с подобным, потому пришлось писать его с нуля.
Хочу заметить, что это немного не тот гипертекст, который вы привыкли видеть в html - это не открытие страниц в браузере, а именно что совершение определенных действий.
Для того чтобы работать с гипертекстом добавьте следующий класс:
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class Hypertext
{
    public string link_null = "<null>";
    public string color_normal = "black", 
                  color_link = "blue";
    public GUIStyle style = new GUIStyle("label") { richText = true, fontSize = 15, fontStyle = FontStyle.Bold };

    private readonly List<Text> phrases = new List<Text>();

    private struct Text
    {
        public Action action;
        public string text;
        public int index;
    }

    private int GetIndexForLast(string str)
        { return (phrases.Count == 0 ? 0 : phrases[phrases.Count - 1].index) + str.Length; }

    public void Add(string str, Action action = null)
        { phrases.Add(new Text { text = str, index = GetIndexForLast(str), action = action}); }

    public void AddFormat(string str, params Action[] actions)
    {
        const char lBracket = '{', 
                   rBracket = '}';
        int currAction = 0;
        var cursor = 0;
        while (true)
        {
            var rIndex = str.IndexOf(rBracket, cursor);
            if (rIndex == -1) break;
            var lIndex = str.LastIndexOf(lBracket, rIndex);
            if (lIndex == -1) break;
            
            var l1 = lIndex - cursor;
            if (l1 != 0) Add(str.Substring(cursor, l1));

            var l2 = rIndex - lIndex - 1;
            var linktext = (l2 != 0) ? str.Substring(lIndex + 1, l2) : link_null;
            if (actions != null && currAction < actions.Length)
                Add(linktext, actions[currAction++]);
            else
                Add(linktext);

            cursor = rIndex + 1;
        }
        if (cursor != str.Length)
            Add(str.Substring(cursor, str.Length - cursor));
    }

    private static string Color(string color, string content)
        { return "<color=" + color + ">" + content + "</color>"; }

    private string GetSimpleString()
        { return phrases.Aggregate("", (str, p) => str + (p.action == null || !string.IsNullOrEmpty(p.text) ? p.text : link_null)); }

    private string GetRichString()
    {
        return phrases.Aggregate("", (str, p) => str + 
            (p.action == null
                ? Color(color_normal, p.text)
                : !string.IsNullOrEmpty(p.text)
                    ? Color(color_link, p.text)
                    : Color(color_link, link_null)));

    }

    private Text GetTextFromCursor(int index)
        { return phrases.FirstOrDefault(x => index < x.index); }

    public static void DrawLayout(GUIStyle style, string str, params Action[] actions)
    {
        var h = new Hypertext {style = new GUIStyle(style) { richText = true }};
        h.AddFormat(str, actions);
        h.DoDrawLayout();
    }

    public static void DrawLayout(string str, params Action[] actions)
    {
        var h = new Hypertext();
        h.AddFormat(str, actions);
        h.DoDrawLayout();
    }

    public void DoDrawLayout()
    {
        var textRich = GetRichString();
        var textSimple = GetSimpleString();
        var rect = GUILayoutUtility.GetRect(new GUIContent(textSimple), style);
        var index = style.GetCursorStringIndex(rect, new GUIContent(textSimple), Event.current.mousePosition - new Vector2(3, 0));
        var focusText = GetTextFromCursor(index);
        
        GUI.Label(rect, textRich, style);

        if (Event.current.type == EventType.MouseDown && focusText.action != null)
        {
            focusText.action.Invoke();
            Event.current.Use();
        }
    }
}

Примеры использования

Есть несколько способов использовать гипертекст с помощью этого класса - каждый из способов может быть удобен в определенных ситуациях.
  1. Формирование гипертекста "частями"
var a = new Hypertext(); 
a.Add("Мама ");
a.Add("мыла", () => Debug.Log("click"));
a.Add(" раму");
a.DoDrawLayout();
В этом случае ключевым является метод Add, и если после текста мы вводим функтор () => { /*Наши действия*/ }, то это слово считается за ссылку.
  1. Формирование гипертекста в одной строке
var a = new Hypertext();
a.AddFormat("Мама {мыла} раму", () => Debug.Log("click"));
a.DoDrawLayout();
В этом случае ключевым является метод AddFormat, в котором все ссылки мы обособляем фигурными скобками, а после, через запятые, указываем функтор-действие на каждую ссылку.
  1. Статическим методом
Hypertext.DrawLayout("Мама {мыла} {раму}", 
                     () => Debug.Log("нажато 'мыла'"), 
                     () => Debug.Log("нажато 'раму'"));
Этот метод по сути работает так же как и второй способ, но позволяет сэкономить в писанине.
Скриншот как пример работы 2 способа:
Кроме того в классе есть несколько дополнительных полей, которые позволяют немного разнообразить ссылки:
style - отвечает за стиль, используемый для рисования надписи
link_null - позволяет указать текст, который будет выводиться если ссылка не имеет названия (должна же оставаться возможность нажимать на нее, верно?)
color_normal - указывает цвет обычного текста (в виде строки)
color_link - указывает цвет ссылок (в виде строки)
Указание цвета в виде строки должно соответствовать правилам указанным вот здесь


Views: 2 770

» Лучшие комментарии


Raised #1 - 7 years ago 0
Голосов: +0 / -0
Есть какие-то идеи как это можно использовать? Тоесть кроме как для работы в консоли применения ему я придумать не могу. Или это скорее для неигровых приложений?
Devion #2 - 7 years ago 0
Голосов: +0 / -0
RiseD_Konst, в редакторах, например. У меня за год было уже 3 таких случая, когда текст вроде слитный, но и вроде должен содержать в себе какой-то функционал. А в целом применение широкое его было как я написал в редакторе триггеров, который я пишу как аналог редактору триггеров в варкрафте, где как раз ссылки и используются.
П4ела #3 - 7 years ago 0
Голосов: +0 / -0
В еve почти все в этих гиперссылках...
alexprey #4 - 7 years ago (изм. ) 3
Голосов: +3 / -0
RiseD_Konst, в диалоговой системе. Когда в тексте подсвечиваются слова темы, кликая на которые персонаж рассказывает об этом. Как в морровинде
MF #6 - 7 years ago (изм. ) 1
Голосов: +1 / -0
Нот бэд! На этом реально будет замутить систему диалогов как в морровинде, насколько я понимаю? Вот там была реально шикарная система развития диалогов.
alexprey #8 - 7 years ago 0
Голосов: +0 / -0
MF, в точку) Я это ведь в предыдущем коменте и написал
MF #9 - 7 years ago 1
Голосов: +1 / -0
alexprey, я только потом заметил, когда отредактировать уже не мог =)