1. Этот сайт использует файлы cookie. Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie. Узнать больше.

Graphical User Interface DirectX v.2017

Тема в разделе "Программирование", создана пользователем wasdgamer, 23.08.2017.

  1. Модератор
    Читер [1 LVL]

    Регистрация:
    19.04.2016
    Сообщения:
    213
    Симпатии:
    98
    Розыгрышей:
    3
    се примеры будут описаны на "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. И добавил в него все скопированные файлы.

    [​IMG]
    Отмеченные красным - это файлы библиотеки 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.x = (float)(x + radius * cos(theta));
    circle.y = (float)(y - radius * sin(theta));
    circle.z = 0;
    circle.rhw = 1.0f;
    circle.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, dwMainFlags, 0xFF000000);
    if (dwMainFlags & DT_CALCRECT)
    this->m_pFont->DrawText(nullptr, buffer, -1, &rs, 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());

    [​IMG]


    Пожалуйста, Зарегистрируйтесь или Авторизируйтесь для просмотра ссылки(в архиве присутствует собранная DLL и D3D9Test)

    Step #5: Menu. Начало.

    Основная площадка подготовлена, теперь предстоит самое сложное(на мой взгляд) - это определится с основной концепцией и общей реализацией.

    Для внешнего вида, я определил такую конфигурацию:
    [​IMG]
    Такая конфигурация уже зарекомендовала себя простотой и удобством, да и выглядит вполне пристойно.можно конечно изобретать сложные графические интерфейсы по типу 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 = 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");

    }
    в итоге мы получили:

    [​IMG]



    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::persevere:onfigMap 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());
    }
    и результат:

    [​IMG]
     
Похожие темы
  1. AllenSalar
    Ответов:
    3
    Просмотров:
    383
  2. Infe[R]nos
    Ответов:
    6
    Просмотров:
    2.736
  3. Bratka
    Ответов:
    1
    Просмотров:
    697
  4. yihrg
    Ответов:
    0
    Просмотров:
    180
  5. ʜᴇɴᴛᴀɪʙᴏʏs
    Ответов:
    0
    Просмотров:
    578
Загрузка...