Загрузка...

Graphical User Interface DirectX v.2017

Тема в разделе Программирование создана пользователем Happy 23 авг 2017. 790 просмотров

  1. Happy
    Happy Автор темы 23 авг 2017 Держу курс на заебись 539 23 апр 2017
    се примеры будут описаны на "MVS2015 Rus" с применением стандарта С++11.


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


    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 библиотеку MiniHook. Что дает данное решение:

    • Работоспособность на всех актуальных ОС(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 функции с использованием этой обвязки можно посмотреть тут

    Скачать архив с MiniHook и полной обвязкой

    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[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());
    [IMG]


    Скачать Проект с ЯД(в архиве присутствует собранная DLL и D3D9Test)

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

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

    Для внешнего вида, я определил такую конфигурацию:
    [IMG][/i][/i]
     
  2. Happy
    Happy Автор темы 23 авг 2017 Держу курс на заебись 539 23 апр 2017
    Такая конфигурация уже зарекомендовала себя простотой и удобством, да и выглядит вполне пристойно.можно конечно изобретать сложные графические интерфейсы по типу 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 параметров.

    Для сохранения и загрузки настроек решил использовать минимальные возможности данного проекта(GitHub)
    На мой взгляд очень удобная и простая реализация и при этом очень гибкая(не нужно говорить об хамл, джейсон и подобного рода парсерах).
    Качаем, скидываем в папку проекта и добавляем все файлы кода к проекту.
    Добавляем фаил в котором будем описывать все действа(я назвал 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());
    }
    и результат:
    [IMG]
     
  3. Happy
    Happy Автор темы 23 авг 2017 Держу курс на заебись 539 23 апр 2017
    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);
    В итоге получили:
    [IMG]

    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);
    Итог:
    [IMG]


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

    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);
    }
    [IMG]

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