Исходник Гайд mimgui — Dear ImGui for MoonLoader (Update v1.92.8)

Добро пожаловать в гайд по mimgui.

mimgui — графическая библиотека для MoonLoader на основе Dear ImGui. Этот гайд написан под обновлённую сборку mimgui 1.92.8 (Dear ImGui v1.92.8): предыдущая статья опиралась на релиз с Dear ImGui v1.72, а исходный Moon ImGui — ещё на v1.52. Библиотека по-прежнему закрывает основные возможности фреймворка, а Lua API максимально приближен к оригинальному C++ API ImGui.

Содержание:


GitHub ImGui:
GitHub mimgui:

mimgui v1.72

Благодарности:
@FYP, @#Northn за существование исходников
@kyrtion за помощь в тестировании

Гайд будет дорабатываться (я надеюсь на это).
Буду рад любому фитбеку насчет библиотеки. Активно будут исправляться баги.


Скачать mimgui
Установка: переместить папку mimgui из архива в папку «*Корневая папка с игрой*/moonloader/lib» с заменой


Основная информация

В этой сборке mimgui используется Dear ImGui 1.92.8. Предыдущий гайд опирался на ImGui 1.72. В принципе различий нет, благодаря дополнительному внедрению обратной совместимости.

Как и раньше, поверх Dear ImGui стоят cimgui и LuaJIT-ImGui, а для MoonLoader добавлена обёртка с DX9/Win32 backend. Исходники этой ветки: xrlmdev/mimgui. Готовый пакет для установки — в Releases.

API по возможности близок к C++ ImGui: большинство функций вызываются так же, как в оригинальной документации, но через Lua (часто с imgui.new для буферов). Из‑за скачка версий часть API изменилась (текстуры/ImTextureRef, шрифты, флаги отрисовки и т.д.) — об этом отдельно в разделе про различия и обновление.

Примеры есть в репозитории (examples/); рабочие куски также разобраны ниже в теме.




Примеры использования
(Было позаимствовано из старого гайда)

Давайте напишем примитивный скрипт с использованием mimgui.
Lua:
local imgui = require('mimgui') -- Подключаем саму библиотеку

local newFrame = imgui.OnFrame( --[[Сама функция создания фрейма, их может быть сколько угодно.
                                    В mimgui рекомендуется создавать отдельный
                                    фрейм для каждого окна, однако это не обязательно.]]
    function() return true end, -- Определяет, выполняется/отображается ли текущий фрейм.
    function(player)            --[[Область, в которой рисуем элементы.
                                    В функцию первым аргументом передаётся таблица
                                    для взаимодействия с локальным игроком и рядом возможностей.]]
        imgui.Begin("Main Window")  -- Создаём окно с заголовком 'Main Window'
        imgui.Text("Hello")         -- Текст 'Hello'
        imgui.End()                 -- Конец окна
    end
)

function main()
    wait(-1)
end

Результат:
1784054126100.png


Добавим размер, позицию по умолчанию и клавишу показа окна.

Lua:
local imgui = require('mimgui')
local vkeys = require('vkeys')        -- Индексы клавиш и работа с ними
local wm = require('windows.message') -- Сообщения окна игры

local new = imgui.new                 --[[Создаём короткий псевдоним для функции,
                                        создающего буфера для различных функций ImGui]]
local renderWindow = new.bool(--[[true/false, по умолч. false]])
local sizeX, sizeY = getScreenResolution()

local newFrame = imgui.OnFrame(
    function() return renderWindow[0] end,
    function(player)
        -- Позиция по центру, пивот 0.5 — отсчёт от середины окна
        imgui.SetNextWindowPos(imgui.ImVec2(sizeX / 2, sizeY / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5))
        imgui.SetNextWindowSize(imgui.ImVec2(200, 150), imgui.Cond.FirstUseEver)
        imgui.Begin("Main Window", renderWindow)
        imgui.Text("Hello")
        imgui.Text(string.format("Current render mode: %s", tostring(renderWindow[0])))
        imgui.End()
    end
)

function main()
    -- Горячие клавиши через onWindowMessage — удобнее и стабильнее, чем опрос в цикле
    addEventHandler('onWindowMessage', function(msg, wparam, lparam)
        if msg == wm.WM_KEYDOWN or msg == wm.WM_SYSKEYDOWN then
            if wparam == vkeys.VK_X then
                renderWindow[0] = not renderWindow[0]
            end
        end
    end)
    wait(-1)
end


Результат:
1784054551609.png


Если размер и позиция «не применились» — окно, скорее всего, уже было сохранено в ini.

mimgui запоминает окна по индексу. Индекс окна — его заголовок, поэтому заголовки должны быть уникальными.
Если нужны одинаковые видимые имена — добавьте после названия ##уникальное_значение (хвост не отображается). То же касается InputText, Button, InputInt и т.д.: без уникальных id работать будет только первый виджет с таким лейблом.

Отключить автосохранение:

Lua:
local imgui = require('mimgui')
local vkeys = require('vkeys')        -- Индексы клавиш и работа с ними
local wm = require('windows.message') -- Сообщения окна игры

local new = imgui.new                 --[[Создаём короткий псевдоним для функции,
                                        создающего буфера для различных функций ImGui]]
local renderWindow = new.bool(--[[true/false, по умолч. false]])
local sizeX, sizeY = getScreenResolution()

imgui.OnInitialize(function()
    --[[ Вызывается один раз за жизнь скрипта, но только после того,
         как ImGui реально инициализируется (обычно при первом показе окна).
         Сюда — стиль, ini, шрифты, текстуры.
         Загрузка текстур вне ImGui-контекста даст ошибку. ]]

    -- Выключаем сохранение. По умолчанию: moonloader/config/mimgui/<script>.ini
    imgui.GetIO().IniFilename = nil
end)

local newFrame = imgui.OnFrame(
    function() return renderWindow[0] end,
    function(player)
        -- Позиция по центру, пивот 0.5 — отсчёт от середины окна
        imgui.SetNextWindowPos(imgui.ImVec2(sizeX / 2, sizeY / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5))
        imgui.SetNextWindowSize(imgui.ImVec2(200, 150), imgui.Cond.FirstUseEver)
        imgui.Begin("Main Window", renderWindow)
        imgui.Text("Hello")
        imgui.Text(string.format("Current render mode: %s", tostring(renderWindow[0])))
        imgui.End()
    end
)

function main()
    -- Горячие клавиши через onWindowMessage — удобнее и стабильнее, чем опрос в цикле
    addEventHandler('onWindowMessage', function(msg, wparam, lparam)
        if msg == wm.WM_KEYDOWN or msg == wm.WM_SYSKEYDOWN then
            if wparam == vkeys.VK_X then
                renderWindow[0] = not renderWindow[0]
            end
        end
    end)
    wait(-1)
end

Теперь окно по центру, нужного размера, по X скрывается/показывается.
1784054913870.png


Русский текст и поле ввода:

Lua:
local imgui = require('mimgui')
local ffi = require('ffi')
local vkeys = require('vkeys')
local wm = require('windows.message')

local new, str, sizeof = imgui.new, ffi.string, ffi.sizeof

local renderWindow = new.bool()
local inputField = new.char[256]()
local sizeX, sizeY = getScreenResolution()

imgui.OnInitialize(function()
    imgui.GetIO().IniFilename = nil
end)

local newFrame = imgui.OnFrame(
    function() return renderWindow[0] end,
    function(player)
        imgui.SetNextWindowPos(imgui.ImVec2(sizeX / 2, sizeY / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5))
        imgui.SetNextWindowSize(imgui.ImVec2(200, 150), imgui.Cond.FirstUseEver)
        imgui.Begin("Main Window", renderWindow)
        imgui.Text("Hello")
        imgui.Text(string.format("Current render mode: %s", tostring(renderWindow[0])))
        -- 1) уникальный лейбл, 2) буфер, 3) размер буфера
        if imgui.InputText("Привет", inputField, sizeof(inputField)) then
            print(str(inputField))
        end
        if imgui.Button("Очистить поле") then
            imgui.StrCopy(inputField, '')
        end
        imgui.End()
    end
)

function main()
    addEventHandler('onWindowMessage', function(msg, wparam, lparam)
        if msg == wm.WM_KEYDOWN or msg == wm.WM_SYSKEYDOWN then
            if wparam == vkeys.VK_X then
                renderWindow[0] = not renderWindow[0]
            end
        end
    end)
    wait(-1)
end

Результат:
1784055226112.png


Мы видим здесь каракули и вопросительные знаки, почему это происходит?


Работа с другими языками на примере русского
В MoonLoader 025 были добавлены библиотеки lua-iconv и encoding, они призваны помочь в работе с разными кодировками текста.
Следующий пример показывает, как использовать текст на русском в ImGui:
Скрипт должен быть сохранён в кодировке Windows-1251 (конкретно для данного примера)
Если в вашем скрипте имеется огромный код с использованием ImGui и мало взаимодействия с функциями MoonLoader'a либо SAMPFUNCS'a, то вы при желании можете сохранить ваш скрипт в UTF-8 и вам не придётся проделывать все эти операции.


Lua:
local imgui = require('mimgui')
local ffi = require('ffi')
local vkeys = require('vkeys')
local wm = require('windows.message')
local encoding = require 'encoding' --[[Подключаем библиотеку для чтения/записи данных с кодировкой,
                                        отличающейся от кодировки нашего скрипта.]]

encoding.default = 'CP1251'         --[[Указываем кодировку по умолчанию. Обратите внимание,
                                        что она должна совпадать с кодировкой вашего скрипта.]]
local u8 = encoding.UTF8            -- И создаём короткий псевдоним для кодировщика UTF-8

local new, str, sizeof = imgui.new, ffi.string, ffi.sizeof

local renderWindow, freezePlayer, removeCursor = new.bool(), new.bool(), new.bool()
local inputField = new.char[256](--[[Здесь также следует кодировать информацию! Исключение: запись сразу с нужной кодировкой (UTF-8)]])
local sizeX, sizeY = getScreenResolution()

imgui.OnInitialize(function()
    imgui.GetIO().IniFilename = nil
end)

local newFrame = imgui.OnFrame(
    function() return renderWindow[0] end,
    function(player)
        imgui.SetNextWindowPos(imgui.ImVec2(sizeX / 2, sizeY / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5))
        imgui.SetNextWindowSize(imgui.ImVec2(200, 150), imgui.Cond.FirstUseEver)
        imgui.Begin("Main Window", renderWindow)
        imgui.Text("Hello")
        imgui.Text(string.format("Current render mode: %s", tostring(renderWindow[0])))
        if imgui.InputText(u8"Привет", inputField, sizeof(inputField)) then
            -- Кодируем название инпута
            print(u8:decode(str(inputField))) -- Декодируем в Windows-1251
        end
        if imgui.Button(u8"Очистить поле") then -- Кодируем название кнопки
            imgui.StrCopy(inputField, '')
        end
        if imgui.Checkbox(u8'Заморозить игрока', freezePlayer) then -- Кодируем название кнопки
            player.LockPlayer = freezePlayer[0]
        end
        if imgui.Checkbox(u8'Скрыть курсор', removeCursor) then -- Кодируем название кнопки
            player.HideCursor = removeCursor[0]
        end
        if player.HideCursor then
            imgui.Text(u8'Курсор скрыт') -- Кодируем выводимый текст
        end
        imgui.End()
    end
)

function main()
    addEventHandler('onWindowMessage', function(msg, wparam, lparam)
        if msg == wm.WM_KEYDOWN or msg == wm.WM_SYSKEYDOWN then
            if wparam == vkeys.VK_X then
                renderWindow[0] = not renderWindow[0]
            end
        end
    end)
    wait(-1)
end

Результат:
1784055973581.png


В теме разобраны базовые окна, ini и кодировка. Остальное — в примерах:




Главные различия между ImGui C++ API и mimgui Lua API

Описание​
C++ ImGui​
Lua mimgui​
Все функции из пространства имён ImGui, как и все типы, и все перечисления находятся в таблице, возвращаемой модулемImGui::Text("text");
ImVec2(0.1f, 2.3f);
imgui.Text("text")
imgui.ImVec2(0.1, 2.3)
Названия перечислений (enum) и их значений лишились префиксов и символа "_" в концеImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResizeimgui.WindowFlags.NoTitleBar + imgui.WindowFlags.NoResize
Значения базовых типов, которые в ImGui записываются по указателю, должны быть использованы через ffistatic bool win = false;
ImGui::Begin("window", &win);
win = false;
local win = imgui.new.bool(false)
imgui.Begin("window", win)
win[0] = false
Использование функций InputText и InputTextMultilinechar buf[256];
ImGui::InputText('input', buf, IM_ARRAYSIZE(buf))
local buf = imgui.new.char[256]()
imgui.InputText('input', buf, ffi.sizeof(buf))
Динамические массивы в виде массива указателей следует объявлять через fficonst char* items[] = {"1", "2", "3"};
ImGui::ListBox("list", &lb_cur, items, 3)
local itemsList = {"1", "2", "3"}
local current = imgui.new.int(0)
local items = imgui.new['const char*'][#itemsList](itemsList)
imgui.ListBoxStr_arr("list", current, items, #itemsList)



Что изменилось при обновлении до 1.92.8

ТемаБыло (в v1.72)Сейчас (в v1.92.8)
Флаги скругления в DrawListimgui.DrawCornerFlags (в C++ ImDrawCornerFlags_*)imgui.DrawFlags (в C++ ImDrawFlags_*)
PushFontimgui.PushFont(font)imgui.PushFont(font, size)
(оба аргумента необязательны)
Картинки / атласImTextureIDImTextureRef
(для обратной совместимости есть поддержка ImTextureID)
Получение глифов (для корректного отображения символов)imgui.GetGlyphRangesCyrillic() и тд.imgui.GetGlyphRangesCyrillic() и тд.
(было удалено в новой версии, но теперь восстанавливается при сборке mimgui)
BeginChildimgui.BeginChild("Child", imgui.ImVec2(100, 100), true, imgui.WindowFlags.NoBackground)imgui.BeginChild("Child", imgui.ImVec2(100, 100), imgui.ChildFlags.Borders, imgui.WindowFlags.NoBackground)
(тип третьего аргумента изменился с bool на ImGuiChildFlags_*, но это всё равно работает с true/false)

Как я писал ранее, библиотеке была добавлена обратная совместимость. Это сделано для того, чтобы не плодить библиотеки, которые по сути являются форком первой версии mimgui (тому пример upmimgui), а так же для того, чтобы юзеры не сталкивались с тем, что при установке новой версии mimgui, старые скрипты переставали работать.

Множество методов, классов и тп. были удалены/переименованы за столь долгое время. Почти для всего этого была сохранена обратная совместимость (возвращены реализации методов, добавлены алиасы).

Был исправлен баг с моргающим курсором, когда было одновременно открыто более 1 окна (ураа!!!).




Новые возможности в mimgui

Для тех, кто всё же задается вопросом: "А для чего же всё таки было обновлять mimgui?" - В новой версии ImGui было добавлено много нового функционала.
Приведем примеры основного нового функционала.

BeginTable - удобная замена Columns

Lua:
if imgui.BeginTable('players', 3, imgui.TableFlags.Borders + imgui.TableFlags.RowBg + imgui.TableFlags.Resizable) then
    imgui.TableSetupColumn(u8'ID')
    imgui.TableSetupColumn(u8'Ник')
    imgui.TableSetupColumn(u8'Счёт')
    imgui.TableHeadersRow()
    for i, row in ipairs({
        {id = 1, name = 'John', score = 10},
        {id = 2, name = 'Alex', score = 5},
        {id = 3, name = 'Chapo', score = -10},
    }) do
        imgui.TableNextRow()
        imgui.TableSetColumnIndex(0); imgui.Text(tostring(row.id))
        imgui.TableSetColumnIndex(1); imgui.Text(row.name)
        imgui.TableSetColumnIndex(2); imgui.Text(tostring(row.score))
    end
    imgui.EndTable()
end
1784066552283.png


BeginChild Flags - повторюсь, были добавлены флаги для окон Child
Lua:
imgui.ChildFlags.None
imgui.ChildFlags.Borders
imgui.ChildFlags.AlwaysUseWindowPadding
imgui.ChildFlags.ResizeX
imgui.ChildFlags.ResizeY
imgui.ChildFlags.AutoResizeX
imgui.ChildFlags.AutoResizeY
imgui.ChildFlags.AlwaysAutoResize
imgui.ChildFlags.FrameStyle
imgui.ChildFlags.NavFlattened

BeginDisabled - отключение блоков
Lua:
imgui.Text('Not Disabled')
imgui.BeginDisabled(false)
imgui.InputText(u8'##1', inputField, ffi.sizeof(inputField))
imgui.SameLine()
imgui.Button(u8'Сохранить##1')
imgui.EndDisabled()

imgui.NewLine()

imgui.Text('Disabled')
imgui.BeginDisabled(true)
imgui.InputText(u8'##2', inputField, ffi.sizeof(inputField))
imgui.SameLine()
imgui.Button(u8'Сохранить##2')
imgui.EndDisabled()
1784064119477.png



BeginListBox - удобная обёртка для отрисовки списков вместо BeginChild
Lua:
if imgui.BeginListBox('##items', imgui.ImVec2(-1, 150)) then
    for i = 1, 10 do
        if imgui.Selectable('Select '..i, selected[0] == i) then
            selected[0] = i
        end
    end
    imgui.EndListBox()
end
1784064585428.png



TextLink / TextLinkOpenURL - кликабельный текст для открытия ссылок
Lua:
imgui.TextLinkOpenURL(u8'Открыть BlastHack', 'https://www.blast.hk/')
if imgui.TextLink(u8'Показать лог') then

end
1784064827573.png



ShortCut / SetNextItemShortcut - создание горячих клавиш внутри окна
Lua:
imgui.SetNextItemShortcut(imgui.Key.S + imgui.Mod.Ctrl, 0)
if imgui.Button(u8'Сохранить') then
    print('Сохранили')
end
1784065002741.png



BeginMultiSelect - мультивыбор элементов
Lua:
local items = { u8'Альфа', u8'Браво', u8'Чарли', u8'Дельта', u8'Эхо' }
local selection = imgui.ImGuiSelectionBasicStorage()

--OnFrame

imgui.Text(u8('Выбрано: %d / %d'):format(selection.Size, #items))
if imgui.BeginChild('##list', imgui.ImVec2(0, 200), imgui.ChildFlags.Borders) then
    local flags = imgui.MultiSelectFlags.ClearOnEscape + imgui.MultiSelectFlags.BoxSelect1d
    local ms_io = imgui.BeginMultiSelect(flags, selection.Size, #items)
    selection:ApplyRequests(ms_io)
    for i = 0, #items - 1 do
        local selected = selection:Contains(i)
        imgui.SetNextItemSelectionUserData(i)
        imgui.Selectable(items[i + 1], selected)
    end
    ms_io = imgui.EndMultiSelect()
    selection:ApplyRequests(ms_io)
end
imgui.EndChild()
1784065636106.png



И ещё много-много чего. Со всем функционалом mimgui можно ознакомиться в ShowDemoWindow.
 
Последнее редактирование:

kyrtion

🤪
Проверенный
1,443
576
Собрал готовые типизированные подсказки для новых MImGui версией 1.92.8.
Проверил на своем большом проекте - у его нет проблем с подсказкой. Нашел баг сразу пиши в ЛС на форум.

Инструкция:
  1. В вашем VS Code должен установлен расширение LuaLS от автора sumneko
  2. Скачать вложенные мной файл types.lua
  3. Файл types.lua скинуть в Game folder/moonloader/lib/mimgui
  4. Готово

Очень важно!
Обязательно объявлять новый класс при вызове mimgui для корректной работы типизированные подсказки с LuaLS:
Lua:
local imgui = require("mimgui") ---@class MImGui: mimgui или ---@class <свое имя>: mimgui
Из-за фичи LuaLS. Поэтому пришлось сделать как положено. Вот доказательства:
1784125070906.png

1784125040693.png


Получается, что мeтa созданные для mimgui из-за название папки.
Когда вызываешь local ig = require("mimgui") получаешь класс именно mimgui, а не ig. Следовательно, что не можете менять внутри класса mimgui (напр: создавать функция, которой нет в mimgui). Поэтому нужно объявлять класс и наследовать. Также название нового класса можете менять что угодно. Хоть imgui, MImGui, NewMimgui, MImGui_1_92_8 и т.д.

Почему так получилось? Зачем?
Хорошо, вы уберете первая строка для объявление класса и создание мета-таблицы в types.lua и вот что получилось:
1784125844974.png

Да, вы не можете создавать свои функции под imgui, вызванным mimgui без собственного класса.

Также оставил мeтa-класс в types.lua на всякий случай, но вернет так:
1784125282086.png
1784135986456.png

1784136020543.png

1784136468738.png

Позволяю редактировать сообщение модераторам и выше на свое усмотрение.
 

Вложения

  • types.lua
    296.2 KB · Просмотры: 0
Последнее редактирование:
  • Нравится
Реакции: romacaddy и Орк

Орк

Известный
546
522

Вложения

  • MMT.lua
    229.4 KB · Просмотры: 1

XRLM

Против ветра рождённый
Автор темы
Модератор
1,669
1,365
Была обновлена библиотека mimgui_blur под новую версию mimgui.
В библиотеке была исправлена ошибка, из-за которой в блюр окна попадали пиксели по краям, из-за чего портился визуал.
Так же на Аризоне на старой версии мимгуи блюр криво работал, сейчас же блюр работает стабильно (по крайней мере у меня так).

В дальнейшем планируется оптимизировать блюр, переписать шейдер, добавить новый функционал. Когда это реализуется, будет создан новый гайд под новую библиотеку, а пока пусть тут лежит эта библиотека для нуждающихся.
 

Вложения

  • mimgui_blur.rar
    8.6 KB · Просмотры: 6
  • Нравится
Реакции: 2elnwndrer. и kyrtion