nanoModalMenu

 nanoModalMenu 1.0.0

nanoModalMenu — это ядро меню, которое предоставляет подключаемым плагинам единую оболочку интерфейса, общие темы, цвета, слои, заголовок, контентную область и обработку обновления UI.

Документация нужна для разработчиков, которые хотят встроить свой плагин в меню и сохранить общий стиль системы.
В самом конце документации присутствует плагин-пример с уже выполненным подключением и базовой отрисовкой модуля.
Вы можете использовать его как пример и проверку на работу с UI.

0. Поддерживаемые хуки

Код:
void OnInitializedCore()
/// Вызывается при инициализации ядра, обязательно используйте данный хук, чтобы подключиться к перезагрузке системы

void OnUnloadedCore()
/// Вызывается при выгрузке ядра
/// Используйте для очистки кэша и прочих данных

void OnModalMenuClose(BasePlayer player)
/// Вызывается когда игрок закрывает модальное меню

void OnModalMenuChangePage(BasePlayer player)
/// Вызывается когда игрок меняет вкладку в меню

Boolean OnOpenModalMenu(BasePlayer player, String pluginName)
/// Вызывается при открытии модального меню и/или страницы плагина в нём
/// При возврате false - не позволяет открыть данную вкладку

1. Основная идея интеграции​

Подключаемый модуль не должен рисовать отдельное меню поверх системы. Он должен использовать готовую оболочку nanoModalMenu:
  • ядро создаёт основную панель меню;
  • модуль создаёт только свой заголовок и контент;
  • цвета берутся из темы ядра;
  • при перезагрузке ядра модуль заново получает темы;
  • при наличии nanoSettingModule модуль может использовать личную тему и настройки игрока.
Код:
nanoModalMenu
 ├─ основная панель меню
 ├─ список тем
 ├─ стандартная тема
 ├─ кнопки модулей
 └─ хуки обновления

Подключаемый модуль
 ├─ получает темы
 ├─ получает стандартную тему
 ├─ рисует заголовок
 ├─ рисует контент
 └─ использует цвета из выбранной темы

2. Подключение ядра​

В модуле необходимо добавить ссылку на ядро:
C#:
[PluginReference] private Plugin nanoModalMenu;

Если модуль использует настройки игрока:
C#:
[PluginReference] private Plugin nanoModalMenu, nanoSettingModule;

Перед вызовом API всегда проверяйте наличие ядра:
C#:
if (nanoModalMenu == null)
{
    PrintError("Не найдено ядро меню. Загрузите nanoModalMenu совместимой версии.");
    return;
}

3. Основные слои интерфейса​

Для правильной интеграции модуль должен использовать общие слои меню:

C#:
private const String layerMenuPanel = "nanoModalMenu_UI_Panel";
private const String moduleHeader = "nanoModalMenu_UI_Module_Header";
private const String moduleContent = "nanoModalMenu_UI_Module_Content";

СлойНазначение
layerMenuPanelРодитель для последующих элементов [Создается nanoModalMenu]
moduleHeaderОбласть заголовка модуля. Здесь размещается название, описание и декоративные элементы.
moduleContentОсновная область модуля. Здесь размещаются кнопки, списки, категории, страницы и другой ваш контент.

Как использовать:
C#:
elements.Add(new CuiElement
{
    Name = moduleHeader,
    Parent = layerMenuPanel,
    DestroyUi = moduleHeader,
    Components =
    {
        new CuiImageComponent { Color = theme.colorPanels },
        new CuiRectTransformComponent
        {
            AnchorMin = "0.22 0.78",
            AnchorMax = "0.98 0.96"
        }
    }
});

Важное правило: перед созданием нового заголовка или контента указывайте DestroyUi.
Это удалит старый слой и не позволит UI накладываться друг на друга.

C#:
DestroyUi = moduleHeader;
DestroyUi = moduleContent;

Рекомендуемая структура:
Код:
nanoModalMenu_UI_Panel
 ├─ nanoModalMenu_UI_Module_Header
 │   ├─ заголовок
 │   ├─ описание
 │   └─ декор
 │
 └─ nanoModalMenu_UI_Module_Content
     ├─ кнопки
     ├─ категории
     ├─ списки
     └─ основной контент

4. API ядра​

4.1 Получение списка тем​

C#:
Object API_GetThemesJson()
Возвращает JSON-строку со всеми темами и их параметрами.

C#:
String json = nanoModalMenu.Call("API_GetThemesJson") as String;
Этот API обязателен для подключаемых модулей, так как через него модуль получает цвета для построения интерфейса.

4.2 Получение стандартной темы​

C#:
String API_GetDefaultThemeKey()
Возвращает ключ стандартной темы из конфигурации ядра.

C#:
defaultKeyTheme = nanoModalMenu.Call<String>("API_GetDefaultThemeKey");
Используется как запасной вариант, если у игрока нет выбранной темы или отсутствует nanoSettingModule.

4.3 Обновление меню игрока​

C#:
void API_UpdateMenuTheme(BasePlayer player, String pluginName)
Перегенерирует меню игрока и возвращает его на страницу указанного модуля.

C#:
nanoModalMenu.Call("API_UpdateMenuTheme", player, Name);
Используется редко.
Обычно достаточно обновлять данные через хук OnInitializedCore.

5. Структура темы​

Модуль должен иметь локальный класс темы с теми же полями, что и ядро:
C#:
private class Themes
{
    public String colorPanels;

    public String colorButton;
    public String colorSelectedButton;
    public String colorDisableButton;
    public String colorActionButton;
    public String colorSelectedActionButton;

    public String colorTitle;
    public String colorDescription;
    public String colorTitleSelectedButton;
    public String colorDescriptionSelectedButton;
    public String colorTitleDisabledButton;
    public String colorDescriptionDisabledButton;

    public String colorSelectedLine;
    public String colorDisableIconButton;
    public String colorSelectedIconButton;

    public String colorProgressBar;
    public String colorProgressLine;

    public String colorStatusActive;
    public String colorStatusInActive;

    public String colorCooldown;
}

Хранение тем в модуле:
C#:
private Dictionary<String, Themes> themeList = new Dictionary<String, Themes>();
private String defaultKeyTheme;

6. Загрузка тем из ядра​

Рекомендуется загружать темы безопасно: проверять пустой JSON, ошибки парсинга, неизвестные и отсутствующие поля.
C#:
private sealed class SafeParseResult<TValue>
{
    public Boolean success;
    public String fatalError;
    public readonly Dictionary<String, TValue> parsed = new Dictionary<String, TValue>();
    public readonly List<String> warnings = new List<String>();
}

Пример загрузки:
C#:
private void LoadThemesFromCore(Boolean isRebuild = false)
{
    if (nanoModalMenu == null)
    {
        PrintError("Не найдено ядро меню. Загрузите nanoModalMenu совместимой версии.");
        return;
    }

    String json = nanoModalMenu.Call("API_GetThemesJson") as String;
    SafeParseResult<Themes> result = TryDeserializeDictionarySafe<Themes>(json);

    foreach (String warning in result.warnings)
        PrintWarning(warning);

    if (!result.success)
    {
        PrintError($"Не удалось загрузить темы. Ошибка: {result.fatalError}");
        return;
    }

    themeList = result.parsed;

    if (!isRebuild)
    {
        defaultKeyTheme = nanoModalMenu.Call<String>("API_GetDefaultThemeKey");
        Puts($"Получен ключ стандартной темы: {defaultKeyTheme}");
    }

    Puts(isRebuild
        ? "Ядро было перезагружено, пересобираем данные о темах."
        : $"Загружено тем из ядра меню: {themeList.Count}");

    RebuildModuleUiCache();
}

7. Хуки загрузки и обновления​

При обычной загрузке модуля:
C#:
private void OnServerInitialized()
{
    LoadThemesFromCore();
}

При перезагрузке ядра:
C#:
private void OnInitializedCore() => NextTick(() =>
{
    LoadThemesFromCore(true);
});

При выгрузке ядра:
C#:
private void OnUnloadedCore()
{
    themeList.Clear();
    cachedStaticUi.Clear();
    cachedPagesUi.Clear();
}

8. Получение темы игрока​

Если установлен nanoSettingModule, можно получить личную тему игрока:
C#:
private String GetThemePlayerKey(BasePlayer player)
{
    String themePlayer = defaultKeyTheme;

    if (nanoSettingModule)
        themePlayer = nanoSettingModule.Call<String>("API_GetSelectedThemePlayer", player);

    return themePlayer;
}

Безопасное получение темы:
C#:
private Themes GetTheme(BasePlayer player)
{
    String themeKey = GetThemePlayerKey(player);

    if (!String.IsNullOrWhiteSpace(themeKey) && themeList.TryGetValue(themeKey, out Themes theme))
        return theme;

    if (!String.IsNullOrWhiteSpace(defaultKeyTheme) && themeList.TryGetValue(defaultKeyTheme, out Themes defaultTheme))
        return defaultTheme;

    return null;
}

9. Использование звуков интерфейса​

Звук стоит вызывать только при действиях игрока: нажатие кнопки, выбор категории, переключение страницы.
C#:
private void RunEffect(BasePlayer player)
{
    if (!nanoSettingModule) return;

    Boolean isUseSounds = nanoSettingModule.Call<Boolean>("API_GetUseSound", player);
    if (!isUseSounds) return;

    EffectNetwork.Send(
        new Effect(fire_select, (BaseEntity)player, 0U, new Vector3(), new Vector3()),
        player.Connection
    );
}

10. Заголовок модуля​

Заголовок нужен, чтобы игрок сразу понимал, в каком разделе находится и что делает данный модуль.
В заголовке рекомендуется размещать:
  • название модуля;
  • короткое описание;
  • декоративные предметы или иконки;
  • цвета из текущей темы.
Пример настройки заголовка:
C#:
public class HeaderMenu
{
    [JsonProperty("Мультиязычный перевод заголовка меню")]
    public Dictionary<String, String> languageTitle;

    [JsonProperty("Мультиязычный перевод описания меню")]
    public Dictionary<String, String> languageDescription;

    [JsonProperty("Список предметов для декорации меню [Shortname] (не более 9-ти штук)")]
    public List<String> decorationShortnames = new List<String>();

    [JsonIgnore] public List<Int32> itemsIdsIcons;
}

Пример заполнения:
C#:
headerMenu = new HeaderMenu()
{
    languageTitle = new Dictionary<String, String>()
    {
        ["ru"] = "ИНФОРМАЦИЯ",
        ["en"] = "INFORMATION"
    },
    languageDescription = new Dictionary<String, String>()
    {
        ["ru"] = "Здесь собрана важная информация о сервере, правилах и возможностях.",
        ["en"] = "Here you can find important information about the server, rules and features."
    },
    decorationShortnames = new List<String>()
    {
        "spraycan",
        "documents",
        "tool.binoculars",
        "building.planner",
        "map",
        "note"
    }
};

11. Общий стиль модулей​

Чтобы все модули выглядели частью одной системы, соблюдайте несколько правил.

Используйте цвета темы:
C#:
Color = theme.colorTitle;
Color = theme.colorDescription;
Color = theme.colorButton;
Color = theme.colorPanels;

Не рекомендуется без необходимости задавать цвета вручную:
C#:
Color = "1 0 0 1";

Разделяйте заголовок и контент:
Код:
Заголовок:
 ├─ название
 ├─ описание
 └─ декор

Контент:
 ├─ кнопки
 ├─ категории
 ├─ страницы
 └─ основной интерфейс

Поддерживайте мультиязычность:
C#:
public Dictionary<String, String> languageTitle;
public Dictionary<String, String> languageDescription;

Пример получения текста:
C#:
private static String GetTextOne(String languagePlayer, ref Dictionary<String, String> languageText)
{
    if (languageText.TryGetValue(languagePlayer, out String playerText))
        return playerText;

    if (languageText.TryGetValue("en", out String englishText))
        return englishText;

    return languageText.FirstOrDefault().Value ?? "NONE LANGUAGE";
}

12. Минимальный пример построения заголовка​

C#:
private void DrawHeader(BasePlayer player)
{
    Themes theme = GetTheme(player);
    if (theme == null) return;

    CuiElementContainer container = new CuiElementContainer();

    container.Add(new CuiElement
    {
        Name = moduleHeader,
        Parent = layerMenuPanel,
        DestroyUi = moduleHeader,
        Components =
        {
            new CuiImageComponent { Color = theme.colorPanels },
            new CuiRectTransformComponent
            {
                AnchorMin = "0.22 0.78",
                AnchorMax = "0.98 0.96"
            }
        }
    });

    container.Add(new CuiElement
    {
        Parent = moduleHeader,
        Components =
        {
            new CuiTextComponent
            {
                Text = "ПРИМЕР МОДУЛЯ",
                Color = theme.colorTitle,
                Font = "robotocondensed-bold.ttf",
                FontSize = 28,
                Align = TextAnchor.UpperLeft
            },
            new CuiRectTransformComponent
            {
                AnchorMin = "0.03 0.50",
                AnchorMax = "0.95 0.90"
            }
        }
    });

    container.Add(new CuiElement
    {
        Parent = moduleHeader,
        Components =
        {
            new CuiTextComponent
            {
                Text = "Короткое описание возможностей модуля.",
                Color = theme.colorDescription,
                Font = "robotocondensed-regular.ttf",
                FontSize = 14,
                Align = TextAnchor.UpperLeft
            },
            new CuiRectTransformComponent
            {
                AnchorMin = "0.03 0.18",
                AnchorMax = "0.95 0.48"
            }
        }
    });

    CuiHelper.AddUi(player, container);
}

13. Минимальный пример построения контента​

C#:
private void DrawContent(BasePlayer player)
{
    Themes theme = GetTheme(player);
    if (theme == null) return;

    CuiElementContainer container = new CuiElementContainer();

    container.Add(new CuiElement
    {
        Name = moduleContent,
        Parent = layerMenuPanel,
        DestroyUi = moduleContent,
        Components =
        {
            new CuiImageComponent { Color = "0 0 0 0" },
            new CuiRectTransformComponent
            {
                AnchorMin = "0.22 0.08",
                AnchorMax = "0.98 0.76"
            }
        }
    });

    container.Add(new CuiElement
    {
        Parent = moduleContent,
        Components =
        {
            new CuiImageComponent { Color = theme.colorButton },
            new CuiRectTransformComponent
            {
                AnchorMin = "0.02 0.82",
                AnchorMax = "0.98 0.98"
            }
        }
    });

    container.Add(new CuiElement
    {
        Parent = moduleContent,
        Components =
        {
            new CuiTextComponent
            {
                Text = "Контент вашего модуля",
                Color = theme.colorTitle,
                Font = "robotocondensed-bold.ttf",
                FontSize = 18,
                Align = TextAnchor.MiddleLeft
            },
            new CuiRectTransformComponent
            {
                AnchorMin = "0.04 0.82",
                AnchorMax = "0.95 0.98"
            }
        }
    });

    CuiHelper.AddUi(player, container);
}

14. Пример добавления модуля в конфигурацию ядра​

В конфигурации nanoModalMenu добавьте кнопку модуля в список кнопок:
JSON:
"example": {
  "Название плагина [Который поддерживает данное меню]": "nanoExampleModule",
  "Название иконки из ..data/nanoSystem/nanoModalMenu/Images": "EXAMPLE_ICON_BUTTON",
  "Мультиязычное название кнопки": {
    "ru": "Пример",
    "en": "Example"
  },
  "Мультиязычное описание кнопки": {
    "ru": "Демонстрационный модуль",
    "en": "Demo module"
  },
  "Активна ли данная кнопка": true
}

Готовый тестовый плагин :
Используйте команду nano.test при открытом модальном меню, чтобы вызвать вкладку с тестовым интерфейсом.

Пример кода (кликабельно)
Назад
Сверху Снизу