Добро пожаловать в гайд по 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.
Содержание:
- Основная информация
- Примеры использования
- Главные различия между ImGui C++ API и mimgui Lua API
- Что изменилось при обновлении до 1.92.8
- Новые возможности в mimgui
GitHub ImGui:
GitHub - ocornut/imgui: Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies - ocornut/imgui
mimgui v1.72
Гайд - mimgui — Dear ImGui for MoonLoader
Добро пожаловать в гайд по mimgui. mimgui — это новая графическая библиотека, написанная в результате устаревания предыдущей графической библиотеки Moon ImGui, использующей Dear ImGui v.1.52; использующая в своей основе свежий релиз Dear ImGui v.1.72. Новая библиотека включает в себя все...
Благодарности:
@FYP, @#Northn за существование исходников
@kyrtion за помощь в тестировании
Гайд будет дорабатываться (я надеюсь на это).
Буду рад любому фитбеку насчет библиотеки. Активно будут исправляться баги.
Скачать mimgui
Установка: переместить папку mimgui из архива в папку «*Корневая папка с игрой*/moonloader/lib» с заменой
Основная информацияУстановка: переместить папку 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
Результат:
Добавим размер, позицию по умолчанию и клавишу показа окна.
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
Результат:
Если размер и позиция «не применились» — окно, скорее всего, уже было сохранено в 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 скрывается/показывается.
Русский текст и поле ввода:
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
Результат:
Мы видим здесь каракули и вопросительные знаки, почему это происходит?
Работа с другими языками на примере русского
В 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
Результат:
В теме разобраны базовые окна, 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_NoResize | imgui.WindowFlags.NoTitleBar + imgui.WindowFlags.NoResize |
| Значения базовых типов, которые в ImGui записываются по указателю, должны быть использованы через ffi | static bool win = false; ImGui::Begin("window", &win); win = false; | local win = imgui.new.bool(false) imgui.Begin("window", win) win[0] = false |
| Использование функций InputText и InputTextMultiline | char buf[256]; ImGui::InputText('input', buf, IM_ARRAYSIZE(buf)) | local buf = imgui.new.char[256]() imgui.InputText('input', buf, ffi.sizeof(buf)) |
| Динамические массивы в виде массива указателей следует объявлять через ffi | const 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) |
|---|---|---|
| Флаги скругления в DrawList | imgui.DrawCornerFlags (в C++ ImDrawCornerFlags_*) | imgui.DrawFlags (в C++ ImDrawFlags_*) |
| PushFont | imgui.PushFont(font) | imgui.PushFont(font, size) (оба аргумента необязательны) |
| Картинки / атлас | ImTextureID | ImTextureRef (для обратной совместимости есть поддержка ImTextureID) |
| Получение глифов (для корректного отображения символов) | imgui.GetGlyphRangesCyrillic() и тд. | imgui.GetGlyphRangesCyrillic() и тд. (было удалено в новой версии, но теперь восстанавливается при сборке mimgui) |
| BeginChild | imgui.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
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()
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
TextLink / TextLinkOpenURL - кликабельный текст для открытия ссылок
Lua:
imgui.TextLinkOpenURL(u8'Открыть BlastHack', 'https://www.blast.hk/')
if imgui.TextLink(u8'Показать лог') then
end
ShortCut / SetNextItemShortcut - создание горячих клавиш внутри окна
Lua:
imgui.SetNextItemShortcut(imgui.Key.S + imgui.Mod.Ctrl, 0)
if imgui.Button(u8'Сохранить') then
print('Сохранили')
end
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()
И ещё много-много чего. Со всем функционалом mimgui можно ознакомиться в ShowDemoWindow.
Последнее редактирование:
