-
Автор темы
- #1
Все примеры будут описаны на "MVS2015 Rus" с применением стандарта С++11.
Step #1: Создание проекта. Первичная настройка.
1) Создаем пустой проект Win32(я назвал проект "GUI DX v.2017")
2)
Tеперь у нас стоит выбор в способе подключения этого SDK к проекту.Вариантов несколько, но предлагаю рассмотреть два, на мой взгляд, самых "правильно-актуальных", назовем их "удаленное подключение" и "компактное".
Общий вес проекта увеличился, но это дало нам возможность "таскать" проект где хочется, без лишних подстроек путей.
Остальные настройки ставим по желанию. Я оставил все в дефолте.
Создал файл main.cpp (именно он будет у нас основным) и файл cInclude.h(он будет как дополнительный со всеми ссылками на последующие).
В cInclude.h подключил необходимые зоголовки:
в main.cpp подключил cInclude.h и описал точку входа и пустой поток:
ЗЫ: готовые проекты к каждому шагу прикреплять скорее всего не буду.
Step #2: Универсальное решение хука
В качестве основного решения для хука я выбрал open source библиотеку
Отмеченные красным - это файлы библиотеки MiniHook, отмеченные зеленым - файлы созданные мной. Фиолетовым - это бонус, который нам в этом проекте не пригодится, но будет весьма полезен для хука игровых интерфейсов и виртуальных функций.
Содержимое cDetour.h:
содержимое cHookContext.h:
в cInclude.h подключаем cHookContext.h
как видим все довольно просто. Пример для WinAPI функции с использованием этой обвязки можно
Step #3: "DirectX Hooking Interface"
Первым делом подключим к проекту заголовки и либы DX9:
Для хука нам требуется прототип функции и указатель на нее относительно класса нашей обвязки MiniHook. Для всех необходимых нужд нам нужны две функции: Present и Reset. Present необходим для вывода на экран информации, а Reset для сброса и перегрузки интерфейсов DX.
опишем наш метод подмены:
ну и сам хук:
тут нет не чего сложного, просто создаем устройство DX по заданным параметрам.
в нашем потоке стартуем ее с ожиданием положительного ответа:
ЗЫ: Данный метод 100% работает на Win7, Win8.1, Win10.
Step #4: Rendering. Графическая основа проекта
Графическая часть "наше" все. именно она задаст стабильность и плавность выполнения остального составляющего. Если с ней ошибиться, то на выходе получим совершенно "неблагоприятны продукт".
Это может выражаться в просадке FPS, утечке памяти и прочих нехороших финтов.
Для общего использования опишем класс для хранения цвета(cColor.h):
как видим у нас есть несколько конструкторов класса, которые позволяют задавать параметры для ARGB каналов, в виде целых чисел(0-255). Так же конструктор
и оператор
дает возможность работать нам с HEX значениями цвета(x00 - xFF)
а методы
получить значение цвета во float(0.f-1.f).
Теперь опишем класс нашего рисования:
Создаем класс cRender и заполняем(как работает думаю не стоит объяснять. Кому нужно либо знают, либо самостоятельно найдут в интернете. а кому не нужно просто скопируют):
cRender.h:
cRender.cpp:
Как видим этот класс дает нам возможность нарисовать основные геометрические элементы.
Теперь подключим все это к проекту и иницализируем:
в cInclude.h подключаем заголовок и заведем переменную bool:
в myPresent() инициализируем:
в myReset() перегружаем по условию:
Выведем все возможности cRender:
Step #5: Menu. Начало.
Основная площадка подготовлена, теперь предстоит самое сложное(на мой взгляд) - это определится с основной концепцией и общей реализацией.
Для внешнего вида, я определил такую конфигурацию:
Такая конфигурация уже зарекомендовала себя простотой и удобством, да и выглядит вполне пристойно.можно конечно изобретать сложные графические интерфейсы по типу ImGui, wxWidgets, CEF.... но поставленная цель: Написать простую и понятную графическую оболочку, которую может осилить новичок(wxWidgets и CEF я до сих пор не смог осилить(().
В общем ближе к делу: Внешний вид определен и у нас уже есть "лекало". так что приступим)
для реализации меню будем использовать отдельный класс( косим под ОПП). Тут встает вопрос: как использовать ранее описанный рендер? вариантов много, но на ум приходит всего два более подходящих: использовать глобальный указатель или передавать ссылку на объект класса рендера. Именно второй вариант более пригляден.В данном примере я все делаю на основе Dx9, но данный подход даст нам возможность просто портировать меню под другие версии рендеринга. а так же описав виртуальный класс использовать несколько видов способов рисовки(Dx9, Dx11,OpGL, vulkan....) не затрагивая сам класс меню.
Создаем класс cMenu:
в .h файле определяем заголовок ранее созданного рендера
а сам зоголовок меню прописываем в cInclude.h
cMenu.h:
cMenu.cpp:
Это будет основой нашего интерфейса.Тут конечно проще и целесообразней было бы использовать умные указатели, но будем обходиться, на сколько это возможно, без STL.
Step #6: Menu. Общая реализация."TabControl".
Для реализации легкого управления цветом и основными кнопками меню заведем в cMenu.h два перечисления за пределами класса:
в приватные члены класса добавим массив для цветов:
для установки параметров цветовой гаммы описываем функцию:
и вызываем ее в конструкторе класса cMenu.
Теперь для установки параметров размера и положения опишем внутри класса структуры и указатели на них(под приватным флагом):
первая структура будет хранить каждое последующее положение контрола, а вторая все основные размеры меню. Это даст нам возможность быстро и без всяких трудностей оперировать внешним видом.
Допишем параметры конструктора и зададим данные:
Добавим bool переменную для установки показа\скрытия меню и SIZE для получения размеров текста(private):
иницализировав ShowGUI в конструкторе мы получим следующее:
true - меню сразу показывается при инжекте.
false - нужно вызвать меню после инжекта для показа.
После всех действий у нас получается:
Перейдем непосредственно к обрисовке задуманного:
Добавим функцию которая нам вернет статус при наведении курсора на указанную область:
добавим два вызова:
Draw - это непосредственно наша отрисовка, ее мы будем вызывать в myPresent
RenderMenu - это описание нашего меню
В cMenu.cpp добавим контейнер и пару переменных, подключив заголовок <vector>:
и опишем:
Теперь опишем основные кнопки, опираясь на описанные данные:
а в RenderMenu() рисуем на экран:
в итоге мы получили:
cMenu.h:
Step #7: Хранение переменных.Save\Load параметров.
Для сохранения и загрузки настроек решил использовать минимальные возможности
На мой взгляд очень удобная и простая реализация и при этом очень гибкая(не нужно говорить об хамл, джейсон и подобного рода парсерах).
Качаем, скидываем в папку проекта и добавляем все файлы кода к проекту.
Добавляем фаил в котором будем описывать все действа(я назвал cSetting.h) и подключаем configfile.h из скачанного проекта:
dfltOptions - это дефолтные параметры на случай если не удалось загрузить файл или его не существует(+этим мы инициализируем переменные для функций).
Структура очень проста и представляет из себя ассоциативный контейнер
ConfigData - структура в которой будут храниться переменные. Обращаться будет через метод сonfig(), который вернет нам указатель на эту структуру(можно и проще, но это на будущее).
Save()\Load() - соответственно: Сохранение и загрузка параметров. Load вызывается в конструкторе класса при его инициализации. Save придется вызывать вручную, либо обыгрывать своими методами или методами самого "конфигуратора".
Для использования изобретем немного цепочек))
Создаем фаил в котором будем описывать все элементы меню(RenderMenu.h) и переносим в него
Подключим к нему:
и укажем переменную вектора, которая определена у нас в cMenu.cpp:
получилось так:
Теперь из cInclude.h удалим ссылку на cMenu.h и укажем ссылку на RenderMenu.h. В main.cpp прописываем указатель на класс Setting:
Готово) Теперь при загрузке длл будет создан экземпляр класса Setting, который через свой конструктор задаст нашим переменным указанные дефолтные настройки, либо загруженные из ранее сохраненного файла.
GUI_DX.cfg:
Step #8: Menu. "CheckBox".
В классе меню добавим:
так же добавим еще один метод, который отслеживает нажатие указанной клавиши с заданным интервалом:
и описываем:
в RenderMenu() дописываем вызов:
как видим все просто: задаем имя и даем ссылку на переменную)
пропишем для теста вывод круга:
и результат:
Step #1: Создание проекта. Первичная настройка.
1) Создаем пустой проект Win32(я назвал проект "GUI DX v.2017")
2)
Пожалуйста, авторизуйтесь для просмотра ссылки.
Tеперь у нас стоит выбор в способе подключения этого SDK к проекту.Вариантов несколько, но предлагаю рассмотреть два, на мой взгляд, самых "правильно-актуальных", назовем их "удаленное подключение" и "компактное".
- В случаи с "удаленным подключением" Папка с SDK хранится отдельно от папки проекта и для подключения его к проекту требуется указать рабочии папки в настройках проекта.
ПКМ по проекту->Свойства->Каталоги VC++: В графе "Каталоги включения" указываем путь к папке "Include" из SDK. В графе "Каталоги библиотек" указываем путь к папке "Lib\xXX", где ".хХХ" это папка с названием конечной платформы требуемого приложения(Win32либо Win64). - "Компактное", именно его я и буду использовать: переносим папку с SDK в папку созданного проекта.
у меня вышло так:Код:C:\Users\Sergey\Documents\Visual Studio 2015\Projects\GUI DX v.2017\GUI DX v.2017\SDK_Jun2010
Код:$(SolutionDir)\GUI DX v.2017\SDK_Jun2010\Include
Код:$(SolutionDir)\GUI DX v.2017\SDK_Jun2010\Lib\x86
Общий вес проекта увеличился, но это дало нам возможность "таскать" проект где хочется, без лишних подстроек путей.
Остальные настройки ставим по желанию. Я оставил все в дефолте.
Создал файл main.cpp (именно он будет у нас основным) и файл cInclude.h(он будет как дополнительный со всеми ссылками на последующие).
В cInclude.h подключил необходимые зоголовки:
Код:
#pragma once
#include <Windows.h>
#include <process.h>
Код:
#include "cInclude.h"
unsigned APIENTRY GUIDX(LPVOID lpParam)
{
return 0L;
}
BOOL APIENTRY DllMain(_In_ HINSTANCE hinstDLL,_In_ DWORD dwReason,_In_ LPVOID lpvReserved)
{
DisableThreadLibraryCalls(hinstDLL);
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
{
_beginthreadex(NULL, NULL, GUIDX, NULL, NULL, NULL);
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Пожалуйста, авторизуйтесь для просмотра ссылки.
ЗЫ: готовые проекты к каждому шагу прикреплять скорее всего не буду.
Step #2: Универсальное решение хука
В качестве основного решения для хука я выбрал open source библиотеку
Пожалуйста, авторизуйтесь для просмотра ссылки.
. Что дает данное решение:- Работоспособность на всех актуальных ОС(Win7, 8.1,10)
- Работоспособность для различных платформ (Win32, Win64)
- Простота использования
- Открытый исходный код
- Скачиваем исходник MiniHook.
- Создаем папку "MiniHook" в папке с проектом.
- Помешаем в нее папки "include" и "src" из скаченного архива
Отмеченные красным - это файлы библиотеки MiniHook, отмеченные зеленым - файлы созданные мной. Фиолетовым - это бонус, который нам в этом проекте не пригодится, но будет весьма полезен для хука игровых интерфейсов и виртуальных функций.
Содержимое cDetour.h:
Код:
#pragma once
template<typename T>
class cDetour
{
public:
explicit cDetour<T>(T target, T detour) : m_target(target), m_detour(detour)
{
MH_CreateHook(m_target, m_detour, reinterpret_cast<void**>(&m_trampoline));
}
~cDetour()
{
MH_DisableHook(m_target);
}
T GetTrampoline() const
{
return static_cast<T>(m_trampoline);
}
bool IsApplied() const
{
return m_isEnabled;
}
void Apply()
{
if (!m_isEnabled)
{
m_isEnabled = MH_EnableHook(m_target) == MH_OK;
if (m_isEnabled)
memcpy(m_hookBuffer, m_target, sizeof(m_hookBuffer));
}
}
void Remove()
{
m_isEnabled = !(m_isEnabled && MH_DisableHook(m_target) == MH_OK);
}
void EnsureApply()
{
if (memcmp(m_hookBuffer, m_target, sizeof(m_hookBuffer)) != 0)
{
DWORD oldProtect;
VirtualProtect(m_target, sizeof(m_hookBuffer), PAGE_READWRITE, &oldProtect);
memcpy(m_target, m_hookBuffer, sizeof(m_hookBuffer));
VirtualProtect(m_target, sizeof(T), oldProtect, &oldProtect);
}
}
private:
T m_trampoline;
T m_target;
T m_detour;
bool m_isEnabled = false;
char m_hookBuffer[20];
};
Код:
#pragma once
#include "MinHook.h"
#include "cDetour.h"
#include "cVmt.h"
class cContext
{
public:
static cContext& GetInstance();
template<typename T> cDetour<T>* CreateDetour(T target, T detour)
{
auto pDetour = new cDetour<T>(target, detour);
return pDetour;
}
template<typename T> bool ApplyDetour(T target, T detour, cDetour<T>** ppDetour)
{
auto pDetour = CreateDetour(target, detour);
if (pDetour)
{
*ppDetour = pDetour;
pDetour->Apply();
return true;
}
return false;
}
template<typename T> cVmt<T>* CreateVmt(void** ppVtable, size_t index, T detour)
{
auto pVmtHook = new cVmt<T>(ppVtable, index, detour);
return pVmtHook;
}
template<typename T> bool ApplyVmt(void** ppVtable, size_t index, T detour, cVmt<T>** ppVmtHook)
{
auto pVmtHook = CreateVmt(ppVtable, index, detour);
if (pVmtHook)
{
*ppVmtHook = pVmtHook;
pVmtHook->Apply();
return true;
}
return false;
}
void CloseExit()
{
if (!(MH_Uninitialize() == MH_OK))
TerminateProcess(GetCurrentProcess(), -1);
}
cContext() {}
~cContext() {}
};
bool bInitialized = false;
cContext& cContext::GetInstance()
{
if (!bInitialized)
bInitialized = MH_Initialize() == MH_OK;
static cContext pCtx;
return pCtx;
}
Код:
#include "MinHook\include\cHookContext.h"
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Step #3: "DirectX Hooking Interface"
Первым делом подключим к проекту заголовки и либы DX9:
Код:
#include <d3d9.h>
#include <d3dx9.h>
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
Код:
typedef HRESULT(APIENTRY* PresentFn)(IDirect3DDevice9 *, CONST RECT*, CONST RECT*, HWND, CONST RGNDATA*);
cDetour<PresentFn>* oPresent;
typedef HRESULT(APIENTRY *ResetFn)(IDirect3DDevice9*, D3DPRESENT_PARAMETERS*);
cDetour<ResetFn>* oReset;
Код:
HRESULT APIENTRY myPresent(IDirect3DDevice9 * m_pDevice, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion)
{
return oPresent->GetTrampoline()(m_pDevice, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);;
}
HRESULT APIENTRY myReset(IDirect3DDevice9* m_pDevice, D3DPRESENT_PARAMETERS *pPresentationParameters)
{
auto result = oReset->GetTrampoline()(m_pDevice, pPresentationParameters);
return result;
}
Код:
bool Init()
{
bool bResult = false;
HMODULE hD3d9 = NULL;
if (hD3d9 = GetModuleHandleA("d3d9.dll"))
{
typedef HRESULT(APIENTRY* Direct3DCreate9ExFn)(UINT, IDirect3D9Ex**);
Direct3DCreate9ExFn oDirect3DCreate9Ex = (Direct3DCreate9ExFn)GetProcAddress(hD3d9, "Direct3DCreate9Ex");
if (oDirect3DCreate9Ex)
{
HRESULT hr = D3D_OK;
LPDIRECT3D9EX m_pCreate9Ex = nullptr;
if (SUCCEEDED(hr = oDirect3DCreate9Ex(D3D_SDK_VERSION, &m_pCreate9Ex)))
{
D3DPRESENT_PARAMETERS dp;
ZeroMemory(&dp, sizeof(dp));
dp.Windowed = 1;
dp.SwapEffect = D3DSWAPEFFECT_FLIP;
dp.BackBufferFormat = D3DFMT_A8R8G8B8;
dp.BackBufferCount = 1;
dp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
IDirect3DDevice9Ex *mDevice = nullptr;
if (SUCCEEDED(hr = m_pCreate9Ex->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_NULLREF, NULL, D3DCREATE_HARDWARE_VERTEXPROCESSING, &dp, NULL, &mDevice)))
{
bResult = true;
PVOID* vtbl = *reinterpret_cast<PVOID**>(mDevice);
auto& pContext = cContext::GetInstance();
pContext.ApplyDetour<PresentFn>(static_cast<PresentFn>(vtbl[17]), reinterpret_cast<PresentFn>(myPresent), &oPresent);
pContext.ApplyDetour<ResetFn>(static_cast<ResetFn>(vtbl[16]), reinterpret_cast<ResetFn>(myReset), &oReset);
mDevice->Release();
}
m_pCreate9Ex->Release();
}
}
}
return bResult;
}
в нашем потоке стартуем ее с ожиданием положительного ответа:
Код:
unsigned APIENTRY GUIDX(LPVOID lpParam)
{
while (!Init())
Sleep(200);
return 0L;
}
Step #4: Rendering. Графическая основа проекта
Графическая часть "наше" все. именно она задаст стабильность и плавность выполнения остального составляющего. Если с ней ошибиться, то на выходе получим совершенно "неблагоприятны продукт".
Это может выражаться в просадке FPS, утечке памяти и прочих нехороших финтов.
Для общего использования опишем класс для хранения цвета(cColor.h):
Код:
class Color
{
#define DEFCOLOR_SRC(name, r, g, b) static Color name##(){ return Color(r, g, b); }
public:
int a, r, g, b;
Color()
{
Color(0, 0, 0, 0);
}
Color(int a, int r, int g, int b)
{
this->a = a;
this->r = r;
this->g = g;
this->b = b;
}
Color(int r, int g, int b)
{
this->a = 255;
this->r = r;
this->g = g;
this->b = b;
}
Color(unsigned long color)
{
this->b = (color & 0xff);
this->g = ((color >> 8) & 0xff);
this->r = ((color >> 16) & 0xff);
this->a = ((color >> 24) & 0xff);
}
inline float* Base()
{
float fColor[3];
fColor[0] = this->r / 255.0f;
fColor[1] = this->g / 255.0f;
fColor[2] = this->b / 255.0f;
return &fColor[0];
}
inline float rBase() const { return this->r / 255.0f; }
inline float gBase() const { return this->g / 255.0f; }
inline float bBase() const { return this->b / 255.0f; }
inline float aBase() const { return this->a / 255.0f; }
inline operator unsigned long() const
{
return (a << 24) | (r << 16) | (g << 8) | b;
}
DEFCOLOR_SRC(Black, 0, 0, 0);
DEFCOLOR_SRC(White, 255, 255, 255);
DEFCOLOR_SRC(Red, 255, 0, 0);
DEFCOLOR_SRC(Green, 0, 128, 0);
DEFCOLOR_SRC(Blue, 0, 0, 255);
};
Код:
Color(unsigned long color)
Код:
inline operator unsigned long() const
а методы
Код:
inline float ##Base() const
Теперь опишем класс нашего рисования:
Создаем класс cRender и заполняем(как работает думаю не стоит объяснять. Кому нужно либо знают, либо самостоятельно найдут в интернете. а кому не нужно просто скопируют):
cRender.h:
Код:
#pragma once
#include <d3d9.h>
#include <d3dx9.h>
#include "Color.h"
#include <stdio.h>
#include <math.h>
#define DT_SHADOW 0x0040
struct DX_VERTEX
{
DX_VERTEX(float X, float Y, float Z, DWORD Color) :
x(X), y(Y), z(Z), color(Color) {};
DX_VERTEX(){}
float x;
float y;
float z;
float rhw = 1.0f;
DWORD color;
static const DWORD FVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE;
};
class cRender
{
public:
cRender(IDirect3DDevice9 *m_pDevice);
~cRender();
void LostDevice();
void ResetDevice();
void render_Line(float x, float y, float x2, float y2, Color color);
void render_Border(float x, float y, float w, float h, Color color);
void render_Box(float x, float y, float w, float h, Color color);
void render_Circle(float x, float y, float radius, Color color);
void render_String(float x, float y, Color color, DWORD dwFlag, const TCHAR* fmt, ...);
HRESULT GetTextExtent(const char* text, SIZE* pSize);
private:
IDirect3DDevice9 *m_pDevice;
ID3DXFont *m_pFont;
TCHAR *szFomtName;
};
extern cRender *pRender;
Код:
#include "cRender.h"
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
cRender *pRender = nullptr;
cRender::cRender(IDirect3DDevice9 *m_pDevice)
{
this->m_pDevice = m_pDevice;
szFomtName = __TEXT("Tahoma");
D3DXCreateFont(m_pDevice, 13, 0, FW_BOLD, 0, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, szFomtName , &m_pFont);
}
void cRender::LostDevice()
{
if (m_pFont)
m_pFont->OnLostDevice();
}
void cRender::ResetDevice()
{
if (m_pFont)
m_pFont->OnResetDevice();
}
void cRender::render_Line(float x, float y,float x2, float y2, Color color)
{
DX_VERTEX Vertices[2] =
{
DX_VERTEX(x, y, 0.f, color),
DX_VERTEX(x2, y2, 0.f, color)
};
this->m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
this->m_pDevice->SetFVF(DX_VERTEX::FVF);
this->m_pDevice->SetTexture(0, NULL);
this->m_pDevice->DrawPrimitiveUP(D3DPT_LINELIST, 1, Vertices, sizeof(DX_VERTEX));
}
void cRender::render_Border(float x, float y, float w, float h, Color color)
{
DX_VERTEX Vertex[5] =
{
DX_VERTEX(x, y, 0.0f, color),
DX_VERTEX(x + w, y, 0.0f, color),
DX_VERTEX(x + w, y + h, 0.0f, color),
DX_VERTEX(x, y + h, 0.0f, color),
DX_VERTEX(x, y, 0.0f, color)
};
this->m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
this->m_pDevice->SetFVF(DX_VERTEX::FVF);
this->m_pDevice->SetTexture(0, NULL);
this->m_pDevice->DrawPrimitiveUP(D3DPT_LINESTRIP, 4, Vertex, sizeof(DX_VERTEX));
}
void cRender::render_Box(float x, float y, float w, float h, Color color)
{
DX_VERTEX Vertex[4] =
{
DX_VERTEX(x, y, 0.0f, color),
DX_VERTEX(x + w, y, 0.0f, color),
DX_VERTEX(x, y + h, 0.0f, color),
DX_VERTEX(x + w, y + h, 0.0f, color)
};
this->m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
this->m_pDevice->SetFVF(DX_VERTEX::FVF);
this->m_pDevice->SetTexture(0, NULL);
m_pDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, &Vertex[0], sizeof(DX_VERTEX));
}
void cRender::render_Circle(float x, float y, float radius, Color color)
{
const int NUMPOINTS = 360/*24*/;
DX_VERTEX circle[NUMPOINTS + 1];
float theta;
float wedgeAngle = (float)((2 * D3DX_PI) / NUMPOINTS);
for (int i = 0; i <= NUMPOINTS; i++)
{
theta = i * wedgeAngle;
circle[i].x = (float)(x + radius * cos(theta));
circle[i].y = (float)(y - radius * sin(theta));
circle[i].z = 0;
circle[i].rhw = 1.0f;
circle[i].color = color;
}
this->m_pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
this->m_pDevice->SetFVF(DX_VERTEX::FVF);
this->m_pDevice->SetTexture(0, NULL);
this->m_pDevice->DrawPrimitiveUP(D3DPT_LINESTRIP, NUMPOINTS, &circle[0], sizeof(DX_VERTEX/*circle[0]*/));
}
void cRender::render_String(float x, float y, Color color, DWORD dwFlag, const TCHAR* fmt, ...)
{
TCHAR buffer[512];
va_list args;
va_start(args, fmt);
#ifdef _UNICODE
vswprintf_s(buffer, fmt, args);
#else
vsprintf_s(buffer, fmt, args);
#endif
va_end(args);
DWORD dwMainFlags = NULL;
RECT r,
rs[4];
dwMainFlags = dwFlag | DT_CALCRECT | DT_NOCLIP;
if (dwFlag & DT_SHADOW)
{
SetRect(&rs[0], (int)x - 1, (int)y, (int)x, 0);
SetRect(&rs[1], (int)x + 1, (int)y, (int)x, 0);
SetRect(&rs[2], (int)x, (int)y - 1, (int)x, 0);
SetRect(&rs[3], (int)x, (int)y + 1, (int)x, 0);
for (INT i = NULL; i < 4; i++)
{
this->m_pFont->DrawText(nullptr, buffer, -1, &rs[i], dwMainFlags, 0xFF000000);
if (dwMainFlags & DT_CALCRECT)
this->m_pFont->DrawText(nullptr, buffer, -1, &rs[i], NULL, 0xFF000000);
}
}
SetRect(&r, (int)x, (int)y, (int)x, 0);
this->m_pFont->DrawText(nullptr, buffer, -1, &r, dwMainFlags, color);
if (dwMainFlags & DT_CALCRECT)
this->m_pFont->DrawText(nullptr, buffer, -1, &r, NULL, color);
}
HRESULT cRender::GetTextExtent(const char* text, SIZE* pSize)
{
if (NULL == text || NULL == pSize)
return E_FAIL;
RECT Rect = { 0, 0, 0, 0 };
this->m_pFont->DrawTextA(NULL, text, -1, &Rect, DT_CALCRECT, 0xff000000);
pSize->cx = Rect.right - Rect.left;
pSize->cy = Rect.bottom - Rect.top;
return S_OK;
}
cRender::~cRender()
{
}
Теперь подключим все это к проекту и иницализируем:
в cInclude.h подключаем заголовок и заведем переменную bool:
Код:
#include "cRender\cRender.h"
bool Create = false;
Код:
if (Create == false)
{
pRender = new cRender(m_pDevice);
Create = true;
}
else
{
//Тут рисуем все что хотим
}
Код:
if (!Create)
return m_pDevice->Reset(pPresentationParameters);
pRender->LostDevice();
auto result = oReset->GetTrampoline()(m_pDevice, pPresentationParameters);
if (SUCCEEDED(result))
{
pRender->ResetDevice();
}
Код:
pRender->render_Line(10, 10, 10, 400, Color::Red());
pRender->render_Box(30, 30, 50, 50, Color(78, 78, 78));
pRender->render_Border(40, 40, 50, 50,0xFFFF0000);
pRender->render_String(100, 100, Color(255, 0, 255, 0), DT_LEFT | DT_SHADOW, L"Cheaton.ru");
SIZE pSize;
pRender->GetTextExtent("Cheaton.ru", &pSize);
pRender->render_Line(100-2, 100, 100-2, 100 + pSize.cy, Color::Green());
pRender->render_Line(100, 102 + pSize.cy, 100 + pSize.cx, 102 + pSize.cy, Color::Green());
Пожалуйста, авторизуйтесь для просмотра ссылки.
(в архиве присутствует собранная DLL и D3D9Test)Step #5: Menu. Начало.
Основная площадка подготовлена, теперь предстоит самое сложное(на мой взгляд) - это определится с основной концепцией и общей реализацией.
Для внешнего вида, я определил такую конфигурацию:
Такая конфигурация уже зарекомендовала себя простотой и удобством, да и выглядит вполне пристойно.можно конечно изобретать сложные графические интерфейсы по типу ImGui, wxWidgets, CEF.... но поставленная цель: Написать простую и понятную графическую оболочку, которую может осилить новичок(wxWidgets и CEF я до сих пор не смог осилить(().
В общем ближе к делу: Внешний вид определен и у нас уже есть "лекало". так что приступим)
для реализации меню будем использовать отдельный класс( косим под ОПП). Тут встает вопрос: как использовать ранее описанный рендер? вариантов много, но на ум приходит всего два более подходящих: использовать глобальный указатель или передавать ссылку на объект класса рендера. Именно второй вариант более пригляден.В данном примере я все делаю на основе Dx9, но данный подход даст нам возможность просто портировать меню под другие версии рендеринга. а так же описав виртуальный класс использовать несколько видов способов рисовки(Dx9, Dx11,OpGL, vulkan....) не затрагивая сам класс меню.
Создаем класс cMenu:
в .h файле определяем заголовок ранее созданного рендера
Код:
#include "../cRender/cRender.h"
Код:
#include "cMenu\cMenu.h"
Код:
#pragma once
#include "../cRender/cRender.h"
class cMenu
{
public:
cMenu(cRender &Render);
virtual ~cMenu();
private:
cRender *m_pRender;
};
extern cMenu *Menu;
Код:
#include "cMenu.h"
cMenu *Menu = nullptr;
cMenu::cMenu(cRender &Render)
{
m_pRender = &Render;
}
cMenu::~cMenu()
{
}
Step #6: Menu. Общая реализация."TabControl".
Для реализации легкого управления цветом и основными кнопками меню заведем в cMenu.h два перечисления за пределами класса:
Код:
/*перечисления для цветов интерфейса*/
enum GUIColorScheme
{
color_Background,
color_Border,
color_Title,
color_Text,
color_Active,
color_Hover,
COUNT_COLOR
};
/*перечисления основных кнопок*/
enum Button
{
TAB_VISUAL,
TAB_WEAPON,
TAB_PLAYER,
TAB_OTHER,
COUNT_TAB
};
Код:
Color GUIColor[COUNT_COLOR];
Код:
void cMenu::SetColor(int iAlfa/* = 255*/)
{
GUIColor[color_Background] = Color(iAlfa, 26, 26, 26);
GUIColor[color_Border] = Color(iAlfa, 73, 73, 73);
GUIColor[color_Title] = Color(iAlfa, 245, 239, 3);
GUIColor[color_Text] = Color(iAlfa, 0, 204, 0);
GUIColor[color_Active] = Color(iAlfa, 0, 255, 0);
GUIColor[color_Hover] = Color(iAlfa, 0, 191, 255);
}
Теперь для установки параметров размера и положения опишем внутри класса структуры и указатели на них(под приватным флагом):
Код:
struct MenuItem
{
float x;
float y;
};
struct Context
{
float WidthMain;
float HeightMain;
float WidthControl;
float HeightControl;
float HeightTitle;
Context()
{
WidthMain = 160.f;
HeightMain = 22.f;
WidthControl = 350.f;
HeightControl = 22.f;
HeightTitle = 26.f;
}
};
private:
Context m_pCtx;
MenuItem ItemMenu;
float xPos;
float yPos;
Допишем параметры конструктора и зададим данные:
Код:
cMenu(cRender &Render, float xPos, float yPos)
Код:
cMenu::cMenu(cRender &Render, float xPos, float yPos)
{
m_pRender = &Render;
this->xPos = xPos;
this->yPos = yPos;
SetColor();
}
Код:
bool ShowGUI;
SIZE pSize;
true - меню сразу показывается при инжекте.
false - нужно вызвать меню после инжекта для показа.
Код:
pRender->GetTextExtent("<<", &pSize);
Код:
cMenu::cMenu(cRender &Render, float xPos, float yPos)
{
m_pRender = &Render;
ShowGUI = true;
this->xPos = xPos;
this->yPos = yPos;
SetColor();
pRender->GetTextExtent("<<", &pSize);
}
Добавим функцию которая нам вернет статус при наведении курсора на указанную область:
Код:
bool cMenu::IsInControl(float x, float y, float w, float h)
{
POINT MousePosition;
GetCursorPos(&MousePosition);
ScreenToClient(GetForegroundWindow(), &MousePosition);
return(MousePosition.x >= x && MousePosition.x <= x + w && MousePosition.y >= y && MousePosition.y <= y + h);
}
Код:
void Draw();
void RenderMenu();
RenderMenu - это описание нашего меню
В cMenu.cpp добавим контейнер и пару переменных, подключив заголовок <vector>:
Код:
std::vector<int> vTab(COUNT_TAB);
int Tab_Number = 0;
int Tab_Max = 0;
Код:
void cMenu::Draw()
{
if (GetAsyncKeyState(VK_END) & 1)ShowGUI ^= 1;
if (!ShowGUI)
return;
(ItemMenu).x = this->xPos;
(ItemMenu).y = this->yPos + m_pCtx.HeightTitle+4;
m_pRender->render_Box(this->xPos, this->yPos, m_pCtx.WidthMain, m_pCtx.HeightTitle, GUIColor[color_Background]);
m_pRender->render_Border(this->xPos, this->yPos, m_pCtx.WidthMain, m_pCtx.HeightTitle, GUIColor[color_Border]);
m_pRender->render_String(this->xPos + m_pCtx.WidthMain / 2, this->yPos+ m_pCtx.HeightTitle/2- pSize.cy/2, GUIColor[color_Title], DT_CENTER | DT_SHADOW, L"GUI DX v.2017");
RenderMenu();
Tab_Number = 0;
}
Теперь опишем основные кнопки, опираясь на описанные данные:
Код:
void cMenu::GuiTab(TCHAR* Text)
{
Color isHover = GUIColor[color_Border];
if (IsInControl((ItemMenu).x, (ItemMenu).y, m_pCtx.WidthMain, m_pCtx.HeightMain))
{
isHover = GUIColor[color_Hover];
if (GetAsyncKeyState(VK_LBUTTON) & 1)
{
if (vTab[Tab_Number] != 1)
vTab[Tab_Number] = 1;
}
}
if (vTab[Tab_Number])
{
isHover = GUIColor[color_Active];
for (int i = 0; i < COUNT_TAB; i++)
if (i != Tab_Number) vTab[i] = 0;
}
m_pRender->render_Box((ItemMenu).x, (ItemMenu).y, m_pCtx.WidthMain, m_pCtx.HeightMain, GUIColor[color_Background]);
m_pRender->render_Border((ItemMenu).x, (ItemMenu).y, m_pCtx.WidthMain, m_pCtx.HeightMain, isHover);
m_pRender->render_String((ItemMenu).x + m_pCtx.WidthMain / 2, (ItemMenu).y + m_pCtx.HeightMain / 2 - pSize.cy / 2, GUIColor[color_Text], DT_CENTER | DT_SHADOW, Text);
Tab_Number = Tab_Number + 1;
if (Tab_Max < Tab_Number)
Tab_Max = Tab_Number;
(ItemMenu).y = (ItemMenu).y + m_pCtx.HeightMain + 2;
}
Код:
void cMenu::RenderMenu()
{
GuiTab(L"TAB_VISUAL");
GuiTab(L"TAB_WEAPON");
GuiTab(L"TAB_PLAYER");
GuiTab(L"TAB_OTHER");
}
cMenu.h:
Код:
#pragma once
#include "../cRender/cRender.h"
#include <vector>
/*перечисления для цветов интерфейса*/
enum GUIColorScheme
{
color_Background,
color_Border,
color_Title,
color_Text,
color_Active,
color_Hover,
COUNT_COLOR
};
/*перечисления основных кнопок*/
enum Button
{
TAB_VISUAL,
TAB_WEAPON,
TAB_PLAYER,
TAB_OTHER,
COUNT_TAB
};
class cMenu
{
public:
cMenu(cRender &Render,float xPos,float yPos);
virtual ~cMenu();
protected:
struct MenuItem
{
float x;
float y;
};
struct Context
{
float WidthMain;
float HeightMain;
float WidthControl;
float HeightControl;
float HeightTitle;
Context()
{
WidthMain = 160.f;
HeightMain = 22.f;
WidthControl = 350.f;
HeightControl = 22.f;
HeightTitle = 26.f;
}
};
private:
Context m_pCtx;
MenuItem ItemMenu;
Color GUIColor[COUNT_COLOR];
public:
void Draw();
private:
void RenderMenu();
bool IsInControl(float x, float y, float w, float h);
void SetColor(int iAlfa = 255);
void GuiTab(TCHAR *Text);
private:
cRender *m_pRender;
float xPos;
float yPos;
bool ShowGUI;
SIZE pSize;
};
extern cMenu *Menu;
Step #7: Хранение переменных.Save\Load параметров.
Для сохранения и загрузки настроек решил использовать минимальные возможности
Пожалуйста, авторизуйтесь для просмотра ссылки.
На мой взгляд очень удобная и простая реализация и при этом очень гибкая(не нужно говорить об хамл, джейсон и подобного рода парсерах).
Качаем, скидываем в папку проекта и добавляем все файлы кода к проекту.
Добавляем фаил в котором будем описывать все действа(я назвал cSetting.h) и подключаем configfile.h из скачанного проекта:
Код:
#pragma once
#include <windows.h>
#include "cConfiguration\configfile.h"
cfg::File::ConfigMap dfltOptions =
{
{
"BLOCK A",
{
{ "FunBool_1", cfg::makeOption(false) },
{ "FunBool_2" , cfg::makeOption(false) }
}
},
{
"BLOCK B",
{
{ "FunBool_3", cfg::makeOption(false) },
{ "FunBool_4" , cfg::makeOption(false) }
}
}
};
class Setting
{
private:
cfg::File m_pOptions;
public:
Setting()
{
m_pOptions.loadFromFile("GUI_DX.cfg");
m_pOptions.setDefaultOptions(dfltOptions);
m_pOptions.setFlag(cfg::File::Autosave);
Load();
}
struct ConfigData
{
bool FunBool_1;
bool FunBool_2;
bool FunBool_3;
bool FunBool_4;
};
inline ConfigData& config() { return m_pConfig; }
void Save()
{
m_pOptions.useSection("BLOCK A");
m_pOptions("FunBool_1") = m_pConfig.FunBool_1;
m_pOptions("FunBool_2") = m_pConfig.FunBool_2;
m_pOptions.useSection("BLOCK B");
m_pOptions("FunBool_3") = m_pConfig.FunBool_3;
m_pOptions("FunBool_4") = m_pConfig.FunBool_4;
m_pOptions.writeToFile();
MessageBeep(3000);
}
void Load()
{
m_pOptions.useSection("BLOCK A");
m_pConfig.FunBool_1 = m_pOptions("FunBool_1").toBool();
m_pConfig.FunBool_2 = m_pOptions("FunBool_2").toBool();
m_pOptions.useSection("BLOCK B");
m_pConfig.FunBool_3 = m_pOptions("FunBool_3").toBool();
m_pConfig.FunBool_4 = m_pOptions("FunBool_4").toBool();
MessageBeep(3000);
}
void Reset()
{
Load();
}
private:
ConfigData m_pConfig;
};
extern Setting m_pSetting;
Структура очень проста и представляет из себя ассоциативный контейнер
Код:
std::map<"Имя Блока", std::map<"Имя переменной",Параметр>>
Save()\Load() - соответственно: Сохранение и загрузка параметров. Load вызывается в конструкторе класса при его инициализации. Save придется вызывать вручную, либо обыгрывать своими методами или методами самого "конфигуратора".
Для использования изобретем немного цепочек))
Создаем фаил в котором будем описывать все элементы меню(RenderMenu.h) и переносим в него
Код:
void cMenu::RenderMenu()
{
......
}
Код:
#include "cMenu.h"
#include "../cUtilit/cSetting.h"
Код:
extern std::vector<int> vTab;
Код:
#pragma once
#include "cMenu.h"
#include "../cUtilit/cSetting.h"
extern std::vector<int> vTab;
void cMenu::RenderMenu()
{
GuiTab(L"TAB_VISUAL");
GuiTab(L"TAB_WEAPON");
GuiTab(L"TAB_PLAYER");
GuiTab(L"TAB_OTHER");
if (vTab[TAB_VISUAL])
{
}
if (vTab[TAB_WEAPON])
{
}
if (vTab[TAB_PLAYER])
{
}
if (vTab[TAB_OTHER])
{
}
}
Код:
Setting m_pSetting;
GUI_DX.cfg:
Код:
[BLOCK A]
FunBool_1 = false
FunBool_2 = false
[BLOCK B]
FunBool_3 = true
FunBool_4 = false
Step #8: Menu. "CheckBox".
В классе меню добавим:
Код:
void GuiCheckBox(TCHAR * Text, void *value);
Код:
bool State_Key(int Key, DWORD dwTimeOut = 30);
struct Keys
{
bool bPressed;
DWORD dwStartTime;
};
Код:
bool cMenu::State_Key(int Key, DWORD dwTimeOut/* = 30*/)
{
if (HIWORD(GetKeyState(Key)))
{
if (!m_pKeys[Key].bPressed || (m_pKeys[Key].dwStartTime && (m_pKeys[Key].dwStartTime + dwTimeOut) <= GetTickCount()))
{
m_pKeys[Key].bPressed = true;
if (dwTimeOut > NULL)
m_pKeys[Key].dwStartTime = GetTickCount();
return true;
}
}
else
m_pKeys[Key].bPressed = false;
return FALSE;
}
Код:
void cMenu::GuiCheckBox(TCHAR * Text, void *value)
{
m_pRender->render_Box((ItemMenu).x + m_pCtx.WidthMain + 3, (ItemMenu).yCtrl, m_pCtx.WidthControl, m_pCtx.HeightControl, GUIColor[color_Background]);
m_pRender->render_Border((ItemMenu).x + m_pCtx.WidthMain + 3, (ItemMenu).yCtrl, m_pCtx.WidthControl, m_pCtx.HeightControl, GUIColor[color_Border]);
m_pRender->render_String((ItemMenu).x + m_pCtx.WidthMain + 7, (ItemMenu).yCtrl + m_pCtx.HeightControl / 2 - pSize.cy / 2, GUIColor[color_Text], DT_LEFT| DT_SHADOW, Text);
Color isDecrement = GUIColor[color_Text];
if (IsInControl((ItemMenu).x + m_pCtx.WidthMain + 3 + m_pCtx.WidthControl - pSize.cx - 1, (ItemMenu).yCtrl + 2.f, pSize.cx, m_pCtx.HeightControl - 4.f))
{
isDecrement = GUIColor[color_Hover];;
if (State_Key(VK_LBUTTON))
*(bool*)value = true;
}
m_pRender->render_String((ItemMenu).x + m_pCtx.WidthMain + 3.f + m_pCtx.WidthControl - pSize.cx/2 - 1, (ItemMenu).yCtrl + m_pCtx.HeightControl / 2 - pSize.cy / 2, isDecrement, DT_CENTER | DT_SHADOW, L">>");
Color isIncrement = GUIColor[color_Text];
if (IsInControl((ItemMenu).x + m_pCtx.WidthMain + 3.f + m_pCtx.WidthControl - pSize.cx - 80, (ItemMenu).yCtrl + 2, pSize.cx, m_pCtx.HeightControl - 4.f))
{
isIncrement = GUIColor[color_Hover];;
if (State_Key(VK_LBUTTON))
*(bool*)value = false;
}
m_pRender->render_String((ItemMenu).x + m_pCtx.WidthMain + 3 + m_pCtx.WidthControl - pSize.cx / 2 - 80.f, (ItemMenu).yCtrl + m_pCtx.HeightControl / 2 - pSize.cy / 2, isIncrement, DT_CENTER | DT_SHADOW, L"<<");
Color isState = GUIColor[color_Text];
const TCHAR *buff = L"Off";
if (*(bool*)value)
{
isState = GUIColor[color_Active];
buff = L"On";
}
m_pRender->render_String((ItemMenu).x + m_pCtx.WidthMain + 3 + m_pCtx.WidthControl - pSize.cx / 2 - 80 + pSize.cx + 25, (ItemMenu).yCtrl + m_pCtx.HeightControl / 2 - pSize.cy / 2, isState, DT_CENTER | DT_SHADOW, buff);
(ItemMenu).yCtrl = (ItemMenu).yCtrl + m_pCtx.HeightControl + 2;
}
Код:
if (vTab[TAB_VISUAL])
{
GuiCheckBox(L"GuiCheckBox 1", &m_pSetting.config().FunBool_1);
GuiCheckBox(L"GuiCheckBox 2", &m_pSetting.config().FunBool_2);
GuiCheckBox(L"GuiCheckBox 3", &m_pSetting.config().FunBool_3);
GuiCheckBox(L"GuiCheckBox 4", &m_pSetting.config().FunBool_4);
}
пропишем для теста вывод круга:
Код:
else
{
Menu->Draw();
if (m_pSetting.config().FunBool_3 == true)
pRender->render_Circle(70, 200, 50, Color::Red());
}