Всем привет, сегодня я решил сделать небольшой гайд по графической библиотеке ImGui на C++
И так, что нам потребуется:
- Visual Studio 2022(подойдет любая по сути, просто так удобнее)
- ImGui (скачать можно на гитхабе)
- Мозг (шучу, он не нужен)
Начнем!
Сезон 1: Начало
После скачивания Visual Studio 2022 для разработки приложений на C++, мы заходим в него и выбирает плашку с текстом "Создание проекта"
->
-> Называем как хотим потому что мы сигмы и сами себе хозяины
Сезон 2: ImGui
Перекидываем в директорию нашего проекта папку которую скачали с гитхаба ImGui
->
->
Создаем новую папку с любым названием потому что мы сигмачки (для меня это src)
->
к слову да, лучше включить такой просмотр файлов в VS, так удобнее как минимум для гайда
->
Заходим в папку imgui-master (переименую в imgui для удобства) и включаем в проект всё что находится в ней
->
Заходим в папку backends и удаляем там всё кроме файлов содержащих "win32" и "dx9" и так же включаем их
->
По желанию переносим всё в основную папку и удаляем лишнее
Сезон 3: Настройка проекта и точка входа
Заходим в свойства проекта и ставим конфигурацию Release и платформу Win32 (или x86)
->
Ставим подсистему во вкладке "Компоновщик" > "Система" на Windows
->
Добавляем библиотеку d3d9.lib в "Компоновщик" > "Ввод" > "Дополнительные зависимости"
->
Изменяем стандарт языка на ISO C++ 20
->
Создаем новый элемент main.cpp а так же gui.h и gui.cpp
->
Сезон 4: gui.h
Заходим в gui.h и добавляем зависимости
->
Пишем то что написал я:
namespace gui
{
constexpr int WIDTH = 700; // Это ширина вашего будущего окна
constexpr int HEIGHT = 400; // Это высота
inline bool isRunning = true;
inline HWND window = nullptr;
inline WNDCLASSEX windowClass = { };
inline POINTS position = { };
inline PDIRECT3D9 d3d = nullptr;
inline LPDIRECT3DDEVICE9 device = nullptr;
inline D3DPRESENT_PARAMETERS presentParameters = { };
void CreateHWindow(const char* windowName) noexcept;
void DestroyHWindow() noexcept;
bool CreateDevice() noexcept;
void ResetDevice() noexcept;
void DestroyDevice() noexcept;
void CreateImGui() noexcept;
void DestroyImGui() noexcept;
void BeginRender() noexcept;
void EndRender() noexcept;
void Render() noexcept;
}
Сезон 5: gui.cpp
Добавляем зависимости (в нашем случае это один gui.h)
#include "gui.h"
->
Добавляем (пишем) обращенеи к апи ImGui
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(
HWND window,
UINT message,
WPARAM wideParameter,
LPARAM longParameter
);
->
Пишем одну из главных функций в коде, WindowProccess, для отслеживания перетаскиваний всяких и подобного
long __stdcall WindowProcess(
HWND window,
UINT message,
WPARAM wideParameter,
LPARAM longParameter)
{
if (ImGui_ImplWin32_WndProcHandler(window, message, wideParameter, longParameter))
return true;
switch (message)
{
case WM_SIZE: {
if (gui::device && wideParameter != SIZE_MINIMIZED)
{
gui::presentParameters.BackBufferWidth = LOWORD(longParameter);
gui::presentParameters.BackBufferHeight = HIWORD(longParameter);
gui::ResetDevice();
}
}return 0;
case WM_SYSCOMMAND: {
if ((wideParameter & 0xfff0) == SC_KEYMENU)
return 0;
}break;
case WM_DESTROY: {
PostQuitMessage(0);
}return 0;
case WM_LBUTTONDOWN: {
gui::position = MAKEPOINTS(longParameter);
}return 0;
case WM_MOUSEMOVE: {
if (wideParameter == MK_LBUTTON)
{
const auto points = MAKEPOINTS(longParameter);
auto rect = ::RECT{ };
GetWindowRect(gui::window, &rect);
rect.left += points.x - gui::position.x;
rect.top += points.y - gui::position.y;
if (gui::position.x >= 0 &&
gui::position.x <= gui::WIDTH &&
gui::position.y >= 0 && gui::position.y <= 20)
SetWindowPos(
gui::window,
HWND_TOPMOST,
rect.left,
rect.top,
0, 0,
SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOZORDER
);
}
}return 0;
}
return DefWindowProc(window, message, wideParameter, longParameter);
}long __stdcall WindowProcess(
HWND window,
UINT message,
WPARAM wideParameter,
LPARAM longParameter)
{
if (ImGui_ImplWin32_WndProcHandler(window, message, wideParameter, longParameter))
return true;
switch (message)
{
case WM_SIZE: {
if (gui::device && wideParameter != SIZE_MINIMIZED)
{
gui::presentParameters.BackBufferWidth = LOWORD(longParameter);
gui::presentParameters.BackBufferHeight = HIWORD(longParameter);
gui::ResetDevice();
}
}return 0;
case WM_SYSCOMMAND: {
if ((wideParameter & 0xfff0) == SC_KEYMENU)
return 0;
}break;
case WM_DESTROY: {
PostQuitMessage(0);
}return 0;
case WM_LBUTTONDOWN: {
gui::position = MAKEPOINTS(longParameter);
}return 0;
case WM_MOUSEMOVE: {
if (wideParameter == MK_LBUTTON)
{
const auto points = MAKEPOINTS(longParameter);
auto rect = ::RECT{ };
GetWindowRect(gui::window, &rect);
rect.left += points.x - gui::position.x;
rect.top += points.y - gui::position.y;
if (gui::position.x >= 0 &&
gui::position.x <= gui::WIDTH &&
gui::position.y >= 0 && gui::position.y <= 20) // Тут 20 относится к месту где можно взяться мышкой и перемещать окно
SetWindowPos(
gui::window,
HWND_TOPMOST,
rect.left,
rect.top,
0, 0,
SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOZORDER
);
}
}return 0;
}
return DefWindowProc(window, message, wideParameter, longParameter);
}
->
Пишем функцию создания окна CreateHWindow (мы ее добавляли в gui.h из-за этого используем его неймспейс)
void gui::CreateHWindow(const char* windowName) noexcept
{
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_CLASSDC;
windowClass.lpfnWndProc = WindowProcess;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = GetModuleHandleA(0);
windowClass.hIcon = 0;
windowClass.hCursor = 0;
windowClass.hbrBackground = 0;
windowClass.lpszMenuName = 0;
windowClass.lpszClassName = L"classwayzer";
windowClass.hIconSm = 0;
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
int posX = (screenWidth - WIDTH) / 2;
int posY = (screenHeight - HEIGHT) / 2;
RegisterClassEx(&windowClass);
wchar_t wWindowName[256];
mbstowcs_s(nullptr, wWindowName, windowName, 256);
window = CreateWindowExW(
WS_EX_LAYERED,
L"classwayzer",
wWindowName,
WS_POPUP,
posX,
posY,
WIDTH,
HEIGHT,
0,
0,
windowClass.hInstance,
0
);
SetLayeredWindowAttributes(window, RGB(0, 0, 0), 0, LWA_COLORKEY);
ShowWindow(window, SW_SHOWDEFAULT);
UpdateWindow(window);
}
->
Теперь пишем вспомогательные функции DirectX9 и для разрушения нашего окна
void gui::DestroyHWindow() noexcept
{
DestroyWindow(window);
UnregisterClass(windowClass.lpszClassName, windowClass.hInstance);
}
bool gui::CreateDevice() noexcept
{
d3d = Direct3DCreate9(D3D_SDK_VERSION);
if (!d3d)
return false;
ZeroMemory(&presentParameters, sizeof(presentParameters));
presentParameters.Windowed = TRUE;
presentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
presentParameters.BackBufferFormat = D3DFMT_UNKNOWN;
presentParameters.EnableAutoDepthStencil = TRUE;
presentParameters.AutoDepthStencilFormat = D3DFMT_D16;
presentParameters.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
if (d3d->CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
window,
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&presentParameters,
&device) < 0)
return false;
return true;
}
void gui::ResetDevice() noexcept
{
ImGui_ImplDX9_InvalidateDeviceObjects();
const auto result = device->Reset(&presentParameters);
if (result == D3DERR_INVALIDCALL)
IM_ASSERT(0);
ImGui_ImplDX9_CreateDeviceObjects();
}
void gui::DestroyDevice() noexcept
{
if (device)
{
device->Release();
device = nullptr;
}
if (d3d)
{
d3d->Release();
d3d = nullptr;
}
}
->
Осталось совсем немного! Самое последнее и самое важное! Создания ImGui меню, мне лень расписывать так что вот вам код. В нем мы инициализируем ImGui и библиотеки win32 и dx9, добавляем стандартный шрифт и еще несколько функций для рендера и уничтожения ImGui
void gui::CreateImGui() noexcept
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ::ImGui::GetIO();
io.IniFilename = NULL;
io.Fonts->AddFontDefault(); // вот шрифты дефолт, можно добавлять свои через функцию
// io.Fonts->AddFontFromFileTTF(...) или AddFontFromMemoryTTF < это с байтов, поищи в инете если интересно
ImGui_ImplWin32_Init(window);
ImGui_ImplDX9_Init(device);
}
void gui::DestroyImGui() noexcept
{
ImGui_ImplDX9_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
void gui::BeginRender() noexcept
{
MSG message;
while (PeekMessage(&message, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&message);
DispatchMessage(&message);
if (message.message == WM_QUIT)
{
isRunning = !isRunning;
return;
}
}
ImGui_ImplDX9_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
}
void gui::EndRender() noexcept
{
ImGui::EndFrame();
device->SetRenderState(D3DRS_ZENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
device->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0, 0, 0, 255), 1.0f, 0);
if (device->BeginScene() >= 0)
{
ImGui::Render();
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
device->EndScene();
}
const auto result = device->Present(0, 0, 0, 0);
if (result == D3DERR_DEVICELOST && device->TestCooperativeLevel() == D3DERR_DEVICENOTRESET)
ResetDevice();
}
void gui::Render() noexcept // САМАЯ ВАЖНАЯ ФУНКЦИЯ! ТУТ МЫ БУДЕМ РИСОВАТЬ НАШЕ МЕНЮ
{
ImGui::SetNextWindowPos({ 0, 0 });
ImGui::SetNextWindowSize({ WIDTH, HEIGHT });
ImGui::Begin(
"ForumWayzer sigma",
&isRunning,
ImGuiWindowFlags_NoResize |
// ImGuiWindowFlags_NoTitleBar | Для того чтобы убрать тайтл бар, иногда ведь хочется закастомить и быть самым крутым с кастомным тайтл баром
ImGuiWindowFlags_NoSavedSettings |
// ImGuiWindowFlags_NoCollapse | Для того чтобы убрать сворачивание(для десктопных внешних приложений очень удобно)
ImGuiWindowFlags_NoMove
);
ImGui::Button("like"); // для примера вот вам кнопочка, почитать о всех виджетах можно так же в [GitHub ImGui](https://github.com/ocornut/imgui)
ImGui::End();
}
Сезон 6: Точка входа
Открываем файл main.cpp и добавляем зависимости (в нашем случае это просто gui.h и thread)
#include "gui.h"
#include <thread>
->
Пишем точку входа wWinMain, потому что мы используем подсистему Windows, если вы используете Console то точка входа это main
int __stdcall wWinMain(
HINSTANCE instance,
HINSTANCE previousInstance,
PWSTR arguments,
int commandShow)
{
gui::CreateHWindow("WayzerForum ImGui C++"); // название вашего окна, заголовок
gui::CreateDevice();
gui::CreateImGui();
while (gui::isRunning) // цикл рендера
{
gui::BeginRender();
gui::Render();
gui::EndRender();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
gui::DestroyImGui(); // выход из программы
gui::DestroyDevice();
gui::DestroyHWindow();
return EXIT_SUCCESS;
}
Конец!
Вот такой изичный гайд
Теперь запустим и посмотрим что у нас получилось
не забудьте Release x86
->
->
Если у вас произойдет подобная ошибка LNK... то вы что-то не добавили в свойства проекта
->
В моем случае это была d3d9.lib
Билд и кайф от того что мы с вами написали
Как мы можем увидеть это мало похоже на стандартное ImGui меню ведь в тегах к gui::Render() > ImGui::Begin() я ввел ImGuiWindowFlags_NoTitleBar и ImGuiWindowFlags_NoCollapse
->
Вот как меню выглядит без этих тегов(флагов)
Благодарность: