Graphical User Interface DirectX v.2017

Эксперт
Статус
Оффлайн
Регистрация
12 Июн 2014
Сообщения
999
Реакции[?]
1,208
Поинты[?]
2K
Все примеры будут описаны на "MVS2015 Rus" с применением стандарта С++11.


Step #1: Создание проекта. Первичная настройка.
1) Создаем пустой проект Win32(я назвал проект "GUI DX v.2017")
2)
Пожалуйста, авторизуйтесь для просмотра ссылки.



Tеперь у нас стоит выбор в способе подключения этого SDK к проекту.Вариантов несколько, но предлагаю рассмотреть два, на мой взгляд, самых "правильно-актуальных", назовем их "удаленное подключение" и "компактное".

  1. В случаи с "удаленным подключением" Папка с SDK хранится отдельно от папки проекта и для подключения его к проекту требуется указать рабочии папки в настройках проекта.
    ПКМ по проекту->Свойства->Каталоги VC++: В графе "Каталоги включения" указываем путь к папке "Include" из SDK. В графе "Каталоги библиотек" указываем путь к папке "Lib\xXX", где ".хХХ" это папка с названием конечной платформы требуемого приложения(Win32либо Win64).
  2. "Компактное", именно его я и буду использовать: переносим папку с 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
    и соответственно для Lib:
    Код:
    $(SolutionDir)\GUI DX v.2017\SDK_Jun2010\Lib\x86

Общий вес проекта увеличился, но это дало нам возможность "таскать" проект где хочется, без лишних подстроек путей.


Остальные настройки ставим по желанию. Я оставил все в дефолте.

Создал файл main.cpp (именно он будет у нас основным) и файл cInclude.h(он будет как дополнительный со всеми ссылками на последующие).
В cInclude.h подключил необходимые зоголовки:
Код:
#pragma once
#include <Windows.h>
#include <process.h>
в main.cpp подключил cInclude.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)
  • Простота использования
  • Открытый исходный код
Но для еще более удобного использования этой Lib, опишем для нее обвязку классом(в конце будет ссылка на архив с либой и готовой обвязкой).
  • Скачиваем исходник MiniHook.
  • Создаем папку "MiniHook" в папке с проектом.
  • Помешаем в нее папки "include" и "src" из скаченного архива
Для того, что бы файлы с которыми мы не будем работать не мешались, добавим фильтр к проекту: ПКМ-Добавить-Новый фильтр (назвал его MHook. И добавил в него все скопированные файлы.

Отмеченные красным - это файлы библиотеки 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];

};
содержимое cHookContext.h:
Код:
#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;
}
в cInclude.h подключаем cHookContext.h
Код:
#include "MinHook\include\cHookContext.h"
как видим все довольно просто. Пример для WinAPI функции с использованием этой обвязки можно
Пожалуйста, авторизуйтесь для просмотра ссылки.


Пожалуйста, авторизуйтесь для просмотра ссылки.

 
Step #3: "DirectX Hooking Interface"

Первым делом подключим к проекту заголовки и либы DX9:
Код:
#include <d3d9.h>
#include <d3dx9.h>
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
Для хука нам требуется прототип функции и указатель на нее относительно класса нашей обвязки MiniHook. Для всех необходимых нужд нам нужны две функции: Present и Reset. Present необходим для вывода на экран информации, а Reset для сброса и перегрузки интерфейсов DX.
Код:
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;
}
тут нет не чего сложного, просто создаем устройство DX по заданным параметрам.

в нашем потоке стартуем ее с ожиданием положительного ответа:
Код:
unsigned APIENTRY GUIDX(LPVOID  lpParam)
{
   while (!Init())
     Sleep(200);
   return 0L;
}
ЗЫ: Данный метод 100% работает на Win7, Win8.1, Win10.
 
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);
    
};
как видим у нас есть несколько конструкторов класса, которые позволяют задавать параметры для ARGB каналов, в виде целых чисел(0-255). Так же конструктор
Код:
Color(unsigned long color)
и оператор
Код:
inline operator unsigned long() const
дает возможность работать нам с HEX значениями цвета(x00 - xFF)
а методы
Код:
inline float ##Base() const
получить значение цвета во float(0.f-1.f).


Теперь опишем класс нашего рисования:
Создаем класс 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;
cRender.cpp:
Код:
#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;
в myPresent() инициализируем:
Код:
if (Create == false)
   {
     pRender = new cRender(m_pDevice);
     Create = true;
   }
   else
   {
     //Тут рисуем все что хотим
   }
в myReset() перегружаем по условию:
Код:
if (!Create)
     return m_pDevice->Reset(pPresentationParameters);

   pRender->LostDevice();
   auto  result = oReset->GetTrampoline()(m_pDevice, pPresentationParameters);

   if (SUCCEEDED(result))
   {
     pRender->ResetDevice();
   }
Выведем все возможности cRender:
Код:
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"
а сам зоголовок меню прописываем в cInclude.h
Код:
#include "cMenu\cMenu.h"
cMenu.h:
Код:
#pragma once
#include "../cRender/cRender.h"

class cMenu
{
public:
   cMenu(cRender &Render);
   virtual ~cMenu();
    
private:
   cRender *m_pRender;
};
extern cMenu *Menu;
cMenu.cpp:
Код:
#include "cMenu.h"

cMenu *Menu = nullptr;
cMenu::cMenu(cRender &Render)
{
   m_pRender = &Render;
}

cMenu::~cMenu()
{
}
Это будет основой нашего интерфейса.Тут конечно проще и целесообразней было бы использовать умные указатели, но будем обходиться, на сколько это возможно, без STL.
 
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);
}
и вызываем ее в конструкторе класса cMenu.
Теперь для установки параметров размера и положения опишем внутри класса структуры и указатели на них(под приватным флагом):
Код:
   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 переменную для установки показа\скрытия меню и SIZE для получения размеров текста(private):
Код:
bool  ShowGUI;
  SIZE pSize;
иницализировав ShowGUI в конструкторе мы получим следующее:
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();
Draw - это непосредственно наша отрисовка, ее мы будем вызывать в myPresent
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;
}
а в RenderMenu() рисуем на экран:
Код:
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;
dfltOptions - это дефолтные параметры на случай если не удалось загрузить файл или его не существует(+этим мы инициализируем переменные для функций).
Структура очень проста и представляет из себя ассоциативный контейнер
Код:
std::map<"Имя Блока", std::map<"Имя переменной",Параметр>>
ConfigData - структура в которой будут храниться переменные. Обращаться будет через метод сonfig(), который вернет нам указатель на эту структуру(можно и проще, но это на будущее).

Save()\Load() - соответственно: Сохранение и загрузка параметров. Load вызывается в конструкторе класса при его инициализации. Save придется вызывать вручную, либо обыгрывать своими методами или методами самого "конфигуратора".


Для использования изобретем немного цепочек))
Создаем фаил в котором будем описывать все элементы меню(RenderMenu.h) и переносим в него
Код:
 void cMenu::RenderMenu()
{
......
}
Подключим к нему:
Код:
#include "cMenu.h"
#include "../cUtilit/cSetting.h"
и укажем переменную вектора, которая определена у нас в cMenu.cpp:
Код:
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])
   {
   }

}
Теперь из cInclude.h удалим ссылку на cMenu.h и укажем ссылку на RenderMenu.h. В main.cpp прописываем указатель на класс Setting:
Код:
Setting m_pSetting;
Готово) Теперь при загрузке длл будет создан экземпляр класса Setting, который через свой конструктор задаст нашим переменным указанные дефолтные настройки, либо загруженные из ранее сохраненного файла.

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;
}
в RenderMenu() дописываем вызов:
Код:
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());
}
и результат:
 
Эксперт
Статус
Оффлайн
Регистрация
12 Июн 2014
Сообщения
999
Реакции[?]
1,208
Поинты[?]
2K
Step #9: Menu. "Scroll".

Задача: Сделать возможность редактирования параметров переменной по заданным критериям(min\max значения, шаг) и наглядно это отобразить в меню, посредством отображения реального значения указанной переменной или указанными маркерами.

Добавляем:
Код:
void  GuiScroll(TCHAR * Text, void *value, std::vector<TCHAR*> const& temp);
   void  GuiScroll(TCHAR * Text, void *value, float min, float max, float step = 1.f);
Text - "имя"
value - ссылка на переменную
temp ссылка на вектор строк
min - минимальное значение переменной, ниже которой значение не опустится
max - максимальное значение переменной, выше которого значение не поднимется
step - шаг перебора(на сколько будет увеличиваться\уменьшаться значение переменной при каждом клике)

Описываем функции:
Код:
void cMenu::GuiScroll(TCHAR * Text, void *value, std::vector<TCHAR*> const& temp)
{
   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, (float)pSize.cx, m_pCtx.HeightControl - 4.f))
   {
     isDecrement = GUIColor[color_Hover];;
     if (State_Key(VK_LBUTTON))
       if ((*(int*)value >= 0) && (*(int*)value < (temp.size() - 1)))
         *(int*)value = *(int*)value + 1;
   }
   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, (float)pSize.cx, m_pCtx.HeightControl - 4.f))
   {
     isIncrement = GUIColor[color_Hover];;
     if (State_Key(VK_LBUTTON))
       if (*(int*)value != 0)
         *(int*)value = *(int*)value - 1;
   }
   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];
   if (*(int*)value > 0)
   {
     isState = GUIColor[color_Active];
   }
   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, temp[*(int*)value]);

   (ItemMenu).yCtrl = (ItemMenu).yCtrl + m_pCtx.HeightControl + 2;
}
void cMenu::GuiScroll(TCHAR * Text, void *value, float min, float max, float step /*= 1.f*/)
{
   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, (float)pSize.cx, m_pCtx.HeightControl - 4.f))
   {
     isDecrement = GUIColor[color_Hover];;
     if (State_Key(VK_LBUTTON))
       if (*(float*)value >= min && *(float*)value < (max))
         *(float*)value += step;
   }
   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, (float)pSize.cx, m_pCtx.HeightControl - 4.f))
   {
     isIncrement = GUIColor[color_Hover];;
     if (State_Key(VK_LBUTTON))
       if (*(float*)value > min)
         *(float*)value -= step;
   }
   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];

#ifdef _UNICODE
   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, L"%.1f", *(float*)value);
#else
   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, "%.1f", *(float*)value);
#endif

   (ItemMenu).yCtrl = (ItemMenu).yCtrl + m_pCtx.HeightControl + 2;
}


Пример использования:
переменные
Код:
float  FunFloat;
     int  FunInt;
вектор строк(может быть любое количество строк внутри)
Код:
std::vector<TCHAR*>  temp = { L"Off",L"одЫн",L"Two",L"Three",L"Four" };
вызываем в RenderMenu()
Код:
if (vTab[TAB_WEAPON])
   {
     GuiScroll(L"GuiScroll 1", &m_pSetting.config().FunInt, temp);
     GuiScroll(L"GuiScroll 2", &m_pSetting.config().FunFloat, -5.f,5.f);
   }
И отобразим значение, переменных для наглядности, в myPresent:
Код:
pRender->render_String(150, 200, Color::Green(), DT_LEFT, L" GuiScroll 1:  %i\n GuiScroll 2:  %.1f", m_pSetting.config().FunInt, m_pSetting.config().FunFloat);
В итоге получили:

 
Step #10: Menu. "SelectColor".

Задача: Позволить пользователю самостоятельно выбрать тот или иной цвет для указанного элемента.

Определяем:
Код:
void  GuiColor(TCHAR * Text, Color *color);
Описываем:
Код:
void cMenu::GuiColor(TCHAR * Text, Color *color)
{
   m_pRender->render_Box((ItemMenu).x + m_pCtx.WidthMain + 3, (ItemMenu).yCtrl, m_pCtx.WidthControl, m_pCtx.HeightControl * 3 - 9, GUIColor[color_Background]);
   m_pRender->render_Border((ItemMenu).x + m_pCtx.WidthMain + 3, (ItemMenu).yCtrl, m_pCtx.WidthControl, m_pCtx.HeightControl * 3 - 9, 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);

   if (color->r > 255)color->r = 255;
   if (color->r < 0)color->r = 0;

   if (color->g  > 255)color->g = 255;
   if (color->g < 0)color->g = 0;

   if (color->b  > 255)color->b = 255;
   if (color->b < 0)color->b = 0;

   POINT CursorPos;
   GetCursorPos(&CursorPos);
   ScreenToClient(GetForegroundWindow(), &CursorPos);
   float xS = (ItemMenu).x + m_pCtx.WidthMain + 3 + m_pCtx.WidthControl - 110;
   
   float width = 100;
   float height = pSize.cy + 2;
   
   /*Red*/
   float valueXR = xS + (color->r * width /255);
   pRender->render_Box(xS, (ItemMenu).yCtrl + 5, valueXR - xS, height - 1, Color(255, 0, 0));
   pRender->render_Border(xS, (ItemMenu).yCtrl + 4, width, height, GUIColor[color_Border]);
   if (IsInControl(xS, (ItemMenu).yCtrl + 5, width + 2, height))
   {
     pRender->render_String(xS + width / 2, (ItemMenu).yCtrl +  pSize.cy/2, GUIColor[color_Text], DT_CENTER, L"%i", color->r);
     if (GetKeyState(VK_LBUTTON) < 0)
       color->r = (CursorPos.x - xS) *255/width;
   }
   /*Green*/
   float valueXG = xS + (color->g * width /255);
   pRender->render_Box(xS, (ItemMenu).yCtrl + 7+ height, valueXG - xS, height - 1, Color(0, 255, 0));
   pRender->render_Border(xS, (ItemMenu).yCtrl + 6 + height, width, height, GUIColor[color_Border]);
   if (IsInControl(xS, (ItemMenu).yCtrl + 7 + height, width + 2, height))
   {
     pRender->render_String(xS + width / 2, (ItemMenu).yCtrl + pSize.cy / 2 + height + 2, GUIColor[color_Text], DT_CENTER, L"%i", color->g);
     if (GetKeyState(VK_LBUTTON) < 0)
       color->g = (CursorPos.x - xS) * 255 / width;
   }
   /*Blue*/
   float valueXB = xS + (color->b * width /255);
   pRender->render_Box(xS, (ItemMenu).yCtrl + 9+ height*2 , valueXB - xS, height - 1, Color(0, 0, 255));
   pRender->render_Border(xS, (ItemMenu).yCtrl + 8 + height * 2, width, height, GUIColor[color_Border]);
   if (IsInControl(xS, (ItemMenu).yCtrl + 9 + height * 2, width + 2, height))
   {
     pRender->render_String(xS + width / 2, (ItemMenu).yCtrl + pSize.cy / 2 + height * 2 + 4, GUIColor[color_Text], DT_CENTER, L"%i", color->b);
     if (GetKeyState(VK_LBUTTON) < 0)
       color->b = (CursorPos.x - xS) * 255 / width;
   }

   pRender->render_Box(xS - 12, (ItemMenu).yCtrl + 5, 10, m_pCtx.HeightControl*3 - 18, *color);

   (ItemMenu).yCtrl = (ItemMenu).yCtrl + m_pCtx.HeightControl*3 - 7;
}

Определяем переменные:
Код:
Color  PrwColor_1;
     Color  PrwColor_2;
под настройки придется каждый отдельный канал описывать:
Код:
"BLOCK C",
     {
       { "IntRed_1",  cfg::makeOption(0) },
       { "IntGreen_1"  ,  cfg::makeOption(255) },
       { "IntBlue_1",  cfg::makeOption(0) },
       { "IntRed_2",  cfg::makeOption(255) },
       { "IntGreen_2"  ,  cfg::makeOption(0) },
       { "IntBlue_2",  cfg::makeOption(0) }
     }
вызываем в меню:
Код:
if (vTab[TAB_PLAYER])
   {
     GuiColor(L"GuiColor 1",&m_pSetting.config().PrwColor_1);
     GuiColor(L"GuiColor 2", &m_pSetting.config().PrwColor_2);
   }
И прикрутим к рисовке:
Код:
pRender->render_Box(100, 200, 30, 30, m_pSetting.config().PrwColor_2); 
     if (m_pSetting.config().FunBool_3 == true)
       pRender->render_Circle(70, 200, 50, m_pSetting.config().PrwColor_1);
Итог:



ЗЫ: Код работает, но не совсем мне нравится. Другого решения пока не изобрел - поэтому пока так)))
 
Step #11: Menu. "Split".

Простые разделители в виде текста или полоски.

Определяем:
Код:
   void  GuiSplit();
   void  GuiSplit(TCHAR *Text);
Описываем:
Код:
void  cMenu::GuiSplit()
{
   m_pRender->render_Box((ItemMenu).x + m_pCtx.WidthMain + 3, (ItemMenu).yCtrl, m_pCtx.WidthControl, 3, GUIColor[color_Background]);
   m_pRender->render_Border((ItemMenu).x + m_pCtx.WidthMain + 3, (ItemMenu).yCtrl, m_pCtx.WidthControl, 3, GUIColor[color_Border]);
   (ItemMenu).yCtrl = (ItemMenu).yCtrl + 5;
}
void  cMenu::GuiSplit(TCHAR *Text)
{
   m_pRender->render_Box((ItemMenu).x + m_pCtx.WidthMain + 3,  (ItemMenu).yCtrl, m_pCtx.WidthControl,  pSize.cy + 4, GUIColor[color_Background]);
   m_pRender->render_Border((ItemMenu).x + m_pCtx.WidthMain + 3, (ItemMenu).yCtrl,  m_pCtx.WidthControl, pSize.cy+4, GUIColor[color_Border]);
   m_pRender->render_String((ItemMenu).x + m_pCtx.WidthMain + 7 + m_pCtx.WidthControl /2, (ItemMenu).yCtrl + (pSize.cy + 4) / 2 - pSize.cy / 2, GUIColor[color_Text], DT_CENTER | DT_SHADOW, Text);
   (ItemMenu).yCtrl = (ItemMenu).yCtrl + (pSize.cy + 4) + 2;
}
а так выглядит это визуально и в коде:
Код:
if (vTab[TAB_VISUAL])
   {
     GuiCheckBox(L"GuiCheckBox 1", &m_pSetting.config().FunBool_1);
     GuiCheckBox(L"GuiCheckBox 2", &m_pSetting.config().FunBool_2);
     GuiSplit();
     GuiCheckBox(L"GuiCheckBox 3", &m_pSetting.config().FunBool_3);
     GuiCheckBox(L"GuiCheckBox 4", &m_pSetting.config().FunBool_4);
     GuiSplit(L"Split::Color");
     GuiColor(L"GuiColor 1", &m_pSetting.config().PrwColor_1);
     GuiColor(L"GuiColor 2", &m_pSetting.config().PrwColor_2);
     GuiSplit(L"Split::Scroll");
     GuiScroll(L"GuiScroll 1", &m_pSetting.config().FunInt, temp);
     GuiScroll(L"GuiScroll 2", &m_pSetting.config().FunFloat, -5.f, 5.f);
   }

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

Результат виден в предыдущем посте.
Думаю на этом можно закончить, а если появятся еще идеи, то они будут добавлены в тему и указаны в стартовом сообщении.



Пробуйте, фантазируйте, реализовывайте ;)



PS: Автором данного туториала являюсь я. Писалось для другого форума и скопировано как есть.
 
Пользователь
Статус
Оффлайн
Регистрация
5 Июн 2017
Сообщения
227
Реакции[?]
242
Поинты[?]
0
DrawPrimitiveUP не самый лучший способ отрисовки для DirectX.
При небольшом количестве линий хавает достаточно FPS. Нельзя настраивать (Anti Aliasing / Ширина).
Уж не знаю как вам, но отрисовка даже через ID3DXLine лучше будет. А вообще лучше рисовать как у ImGui - через DrawIndexedPrimitive.
 
Эксперт
Статус
Оффлайн
Регистрация
12 Июн 2014
Сообщения
999
Реакции[?]
1,208
Поинты[?]
2K
DrawPrimitiveUP не самый лучший способ отрисовки для DirectX.
При небольшом количестве линий хавает достаточно FPS. Нельзя настраивать (Anti Aliasing / Ширина).
Уж не знаю как вам, но отрисовка даже через ID3DXLine лучше будет. А вообще лучше рисовать как у ImGui - через DrawIndexedPrimitive.
это если у тебя руки из жопы растут..
На досуге почитай о DrawPrimitiveUP и DrawIndexedPrimitive...
Согласен ширина линий не регулируется, но и 1pxl вполне хватает + не загромождает экран, но тут на вкус и цвет....
Anti Aliasing- посмотри что это и для чего нужно, а потом повтори свои слова...
ID3DXLine это отдельный интерфейс который в конечном итоге приходит к DrawPrimitiveU, причем итерация у нее в четыре раза выше + кода в разы больше.

ПС: суть темы не в рисовке, а в построении своего графического интерфейса. вырисовывать ты можешь хоть "черепашкой"....
 
Начинающий
Статус
Оффлайн
Регистрация
6 Авг 2016
Сообщения
66
Реакции[?]
4
Поинты[?]
0
как понять "в приватные члены класса добавим массив для цветов:"
 
Забаненный
Статус
Оффлайн
Регистрация
12 Фев 2017
Сообщения
144
Реакции[?]
3
Поинты[?]
0
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Можно D3D9 Test дать?
 
Забаненный
Статус
Оффлайн
Регистрация
16 Май 2017
Сообщения
40
Реакции[?]
4
Поинты[?]
0
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Похожие темы
Сверху Снизу