C++ Исходник KeyBind System

Участник
Статус
Оффлайн
Регистрация
15 Янв 2021
Сообщения
500
Реакции[?]
311
Поинты[?]
101K
Итак. Приветствую человека, который зашел в мою тему. Сегодня я ликну достаточно интересную штуку, а именно расширенную систему кейбиндов ( как вот в этих ваших неврлуз.цц, оверси.ю ( покойся с миром ) и ванфлюид.тех ). Всё написано с помощью библиотеки ImGui. Я не видел каких-либо похожих тем, и реализация у меня будет (возможно) получше. Базу чита использовал CSGOSimple.

Начать стоит с того, что мы будем использовать, какие структуры и тому подобное.
C++:
// Для определния на что мы нажали ПКМ и какой тип вызывать
enum EHotKeyType : int
{
    HOTKEY_CHECKBOX,
    HOTKEY_SLIDER,
    HOTKEY_NONE
};

// Как будет работать бинд
enum EHotKeyMode : int
{
    MODE_TOGGLE = 0,
    MODE_HOLD
};
C++:
// Структура нашего бинда специально для чекбокса
struct ImGui_HotKey_Checkbox
{
    int id = -1;
    std::string name;
    std::string label_name;
    int key = 0;
    int mode = MODE_HOLD;

    bool* value = nullptr;

    ImGui_HotKey_Checkbox()
    {
        id = -1;
        label_name = "";
        key = 0;
    }

    ImGui_HotKey_Checkbox(int _id, bool* _value)
    {
        id = _id;
        label_name = "";
        key = 0;
        value = _value;
    }
};
C++:
namespace ImGui_HotKeys
{
    // Открыто ли меню
    inline bool open_menu;
    // Позиция для отрисовки меню
    inline ImVec2 menu_pos;
  
    // Массив кейбиндов специально для Checkbox
    inline std::vector<ImGui_HotKey_Checkbox> checkbox_hotkeys;
    inline ImGui_HotKey_Checkbox current_checkbox;
  
    // "Кастомный" Checkbox
    bool Checkbox(const char* label, bool* v);
    // Combo для отображения кейбиндов под Checkbox
    bool Combo_CheckBox(const char* label, int& current_item, std::vector<ImGui_HotKey_Checkbox>& data);
  
    bool Hotkey(const char* label, int& k, const ImVec2& size_arg = ImVec2(0, 0));
    void Draw();
}

Но есть небольшая уловочка для любителей попастить. Хоть я и предоставляю готовый код, как вы видите, я не сделал кейбиндов для слайдеров и других каких-нибудь виджетов. Это вам предстоит сделать самим)
А дальше у нас идут определения всех функций.
C++:
bool ImGui_HotKeys::Checkbox(const char* label, bool* v)
{
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems)
        return false;

    ImGuiContext& g = *GImGui;
    const ImGuiStyle& style = g.Style;
    const ImGuiID id = window->GetID(label);
    const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);

    const float square_sz = 14;
    const ImVec2 pos = window->DC.CursorPos;
    const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));
    ImGui::ItemSize(total_bb, style.FramePadding.y);
    if (!ImGui::ItemAdd(total_bb, id))
    {
        IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
        return false;
    }

    bool hovered, held;
    bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held);
    if (pressed)
    {
        *v = !(*v);
        ImGui::MarkItemEdited(id);
    }
  
    // если курсор наведён и нажата ПКМ
    if (hovered && g.IO.MouseClicked[1])
    {
        open_menu = true;
        menu_pos = total_bb.Max;

        current_checkbox.id = id;
        current_checkbox.label_name = label;
        current_checkbox.value = v;
    }
  
    // Проверк, если нажали на любую другую позицию вместе нашего окна
    if (g.IO.MouseClicked[0] && !hovered && open_menu)
    {
        auto cursor_pos = ImGui::GetIO().MousePos;
        if(cursor_pos.x > menu_pos.x + HOTKEY_MENU_SIZE.x || cursor_pos.x < menu_pos.x
            || cursor_pos.y < menu_pos.y || cursor_pos.y > menu_pos.y + HOTKEY_MENU_SIZE.y)
            open_menu = false;
    }

    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
    ImGui::RenderNavHighlight(total_bb, id);
    ImGui::RenderFrame(check_bb.Min + ImVec2(0, 4), check_bb.Max + ImVec2(0, 4), ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
  
    if (held || hovered)
    {
        window->DrawList->AddRect(check_bb.Min + ImVec2(-1, 3), check_bb.Max + ImVec2(1, 5), ImColor(0, 0, 0));
    }
  
    ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark);

    if (*v)
    {
        const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
        ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad) + ImVec2(0, 4), check_col, square_sz - pad * 2.0f);
    }

    ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);

    if (label_size.x > 0.0f)
        ImGui::RenderText(label_pos, label);

    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
    return pressed;
}
C++:
// Просто дефолтная функция бинда из CSGOSimple
bool ImGui_HotKeys::Hotkey(const char* label, int& k, const ImVec2& size_arg)
{
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems)
        return false;

    ImGuiContext& g = *GImGui;
    ImGuiIO& io = g.IO;
    const ImGuiStyle& style = g.Style;

    const ImGuiID id = window->GetID(label);
    const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
    ImVec2 size = ImGui::CalcItemSize(size_arg, ImGui::CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f);
    const ImRect frame_bb(window->DC.CursorPos + ImVec2(label_size.x + style.ItemInnerSpacing.x, 0.0f), window->DC.CursorPos + size + ImVec2(ImGui::CalcTextSize("<Press a key>", NULL, true).x / 2, 0));
    const ImRect total_bb(window->DC.CursorPos, frame_bb.Max);

    ImGui::ItemSize(total_bb, style.FramePadding.y);
    if (!ImGui::ItemAdd(total_bb, id))
        return false;

    const bool hovered = ImGui::ItemHoverable(frame_bb, id);

    if (hovered) {
        ImGui::SetHoveredID(id);
        //g.MouseCursor = ImGuiMouseCursor_TextInput;
    }

    const bool user_clicked = hovered && io.MouseClicked[0];

    if (user_clicked) {
        if (g.ActiveId != id) {
            // Start edition
            memset(io.MouseDown, 0, sizeof(io.MouseDown));
            memset(io.KeysDown, 0, sizeof(io.KeysDown));
            k = 0;
        }
        ImGui::SetActiveID(id, window);
        ImGui::FocusWindow(window);
    }
    else if (io.MouseClicked[0]) {
        // Release focus when we click outside
        if (g.ActiveId == id)
            ImGui::ClearActiveID();
    }

    bool value_changed = false;
    int key = k;

    if (g.ActiveId == id) {
        for (auto i = 0; i < 5; i++) {
            if (io.MouseDown[i]) {
                switch (i) {
                case 0:
                    key = VK_LBUTTON;
                    break;
                case 1:
                    key = VK_RBUTTON;
                    break;
                case 2:
                    key = VK_MBUTTON;
                    break;
                case 3:
                    key = VK_XBUTTON1;
                    break;
                case 4:
                    key = VK_XBUTTON2;
                    break;
                }
                value_changed = true;
                ImGui::ClearActiveID();
            }
        }
        if (!value_changed) {
            for (auto i = VK_BACK; i <= VK_RMENU; i++) {
                if (io.KeysDown[i]) {
                    key = i;
                    value_changed = true;
                    ImGui::ClearActiveID();
                }
            }
        }

        if (ImGui::IsKeyPressedMap(ImGuiKey_Escape)) {
            k = 0;
            ImGui::ClearActiveID();
        }
        else {
            k = key;
        }
    }

    // Render
    // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is Set 'buf' might still be the old value. We Set buf to NULL to prevent accidental usage from now on.
    char buf_display[64] = "...";

    ImGui::RenderFrame(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32(ImVec4(0.20f, 0.25f, 0.30f, 1.0f)), true, style.FrameRounding);

    if (k != 0 && g.ActiveId != id) {
        strcpy_s(buf_display, KeyNames[k]);
    }

    if (g.ActiveId == id) {
        strcpy_s(buf_display, "<Press a key>");
    }

    const ImRect clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
    ImVec2 render_pos = frame_bb.Min + style.FramePadding;
    ImGui::RenderTextClipped(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding, buf_display, NULL, NULL, style.ButtonTextAlign, &clip_rect);
  
    if (label_size.x > 0)
        ImGui::RenderText(ImVec2(total_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), label);

    return value_changed;
}
C++:
// А вот и самое вкусное что вам нужно будет. Имеется часть говнокода, но я её не мог избежать по тем или иным причинам
bool ImGui_HotKeys::Combo_CheckBox(const char* label, int& current_item, std::vector<ImGui_HotKey_Checkbox> &data)
{
    if (data.size() == 0)
        data.push_back(ImGui_HotKey_Checkbox::ImGui_HotKey_Checkbox(current_checkbox.id, current_checkbox.value));
  
    // Проверка, существует ли кейбинды для нашего checkbox'а, если нет, то создаём и ставим current_item на
    // последний добавленный элемент
    if (std::all_of(data.begin(), data.end(), [](ImGui_HotKey_Checkbox h) {return h.id != current_checkbox.id; }))
    {
        data.push_back(ImGui_HotKey_Checkbox::ImGui_HotKey_Checkbox(current_checkbox.id, current_checkbox.value));
        current_item = data.size() - 1;
    }

    // Если current_item не равен активному checbox'у то надо найти его первое вхождение и выставить наш current_item
    if (data[current_item].id != current_checkbox.id)
    {
        for (int i = 0; i < data.size(); i++)
        {
            if (data[i].id == current_checkbox.id)
            {
                current_item = i;
                break;
            }
        }
    }

    // Main code
    ImGuiContext& g = *GImGui;

    if (data[current_item].key != 0)
    {
        data[current_item].name = "Bind ";
        data[current_item].name += KeyNames[data[current_item].key];
    }
    else {
        data[current_item].name = "Unknown bind";
    }

    std::string preview_name = data[current_item].name;

    int items_count = data.size();

    if (!ImGui::BeginCombo("##__keybind.combo__", preview_name.c_str(), ImGuiComboFlags_HeightRegular | ImGuiComboFlags_NoArrowButton))
        return false;

    // Display items
    bool value_changed = false;
    int delete_index = -1;

    bool found_hotkey = false;

    for (int i = 0; i < items_count; i++)
    {
        if (data[i].id != current_checkbox.id)
            continue;

        ImGui::PushID((void*)(intptr_t)i);

        const bool item_selected = (i == current_item);

        if (data[i].key != 0)
        {
            data[i].name = "Bind ";
            data[i].name += KeyNames[data[i].key];
        }
        else {
            data[i].name = "Empty bind";
        }

        if (ImGui::Selectable(data[i].name.c_str(), item_selected, 0, ImVec2(80, 20)))
        {
            value_changed = true;
            current_item = i;
        }

        if (items_count > 1)
        {
            ImGui::SameLine();
            // Здесь имеется небольшой баг, увидети его, если будуте использовать данный код для своих нужд
            if (ImGui::Selectable("-", item_selected, ImGuiSelectableFlags_DontClosePopups, ImVec2(20, 20)))
            {
                // Так как current item останется на месте при удалении последнего элемента, мы можем указывать
                // на элемент массива, которого не существует и получим exception out of range
                if (data.begin() + i <= data.end())
                {
                    if (i < current_item)
                        current_item--;
                    if (i > current_item)
                        current_item++;
                    if (i == current_item)
                        current_item = 0;

                    data.erase(data.begin() + i);
                    ImGui::PopID();
                    break;
                }
            }
        }

        if (i == items_count - 1)
        {
            ImGui::SetCursorPosX((ImGui::GetCurrentWindow()->Size.x / 2) - ImGui::CalcTextSize("add_one").x / 2);
            if (ImGui::Button("Add Bind ", ImVec2(0, 0)))
            {
                data.push_back(ImGui_HotKey_Checkbox::ImGui_HotKey_Checkbox(current_checkbox.id, current_checkbox.value));
            }
        }

        if (item_selected)
            ImGui::SetItemDefaultFocus();

        ImGui::PopID();
    }

    ImGui::EndCombo();
    return value_changed;
}
C++:
// Вот и заключительная функция для отрисовки,
void ImGui_HotKeys::Draw()
{
    // Просто защита от дебила
    if (!Menu::Get().IsVisible() || !open_menu)
        return;

    static int key = 0;

    static int current_key = 0;

    ImGui::SetNextWindowSize(HOTKEY_MENU_SIZE);
    ImGui::SetNextWindowPos(menu_pos);
    ImGui::Begin("Keybind", 0, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNav);
    {
        //ImGui::Checkbox("biba", &b);
        ImGui::Text("HotKey list"); ImGui::SameLine();
        Combo_CheckBox("##list.of.keybinds", current_key, checkbox_hotkeys);

        ImGui::Text("Mode"); ImGui::SameLine();
        ImGui::Combo("##keybind.mode", &checkbox_hotkeys[current_key].mode, key_modes, IM_ARRAYSIZE(key_modes));

        Hotkey("Key", checkbox_hotkeys[current_key].key);
    }
    ImGui::End();
}

Вот в общем-то основной код, который вам понадобится. Но надо бы написать ещё функцию, которая будет проверять, нажата ли кнопка и включать или выключать наш Checkbox. Вот простенький код для этих двух режимов
C++:
void CHotKeysSystem::Run()
{
    // Checkbox checking
    auto checkbox_hotkeys = ImGui_HotKeys::checkbox_hotkeys;

    for (auto &hk : ImGui_HotKeys::checkbox_hotkeys)
    {
        if (hk.mode == MODE_HOLD)
        {
            *hk.value = InputSys::Get().IsKeyDown(hk.key);
        }

        if (hk.mode == MODE_TOGGLE)
        {
            if (InputSys::Get().WasKeyPressed(hk.key))
                *hk.value = !(*hk.value);
        }
    }
}

Как-то так мои дорогие друзьяшки. Думаю не буду ставить даже хайд на данную тему, я просто не хочу, чтобы люди использовали кейбинд систему как в лв или прямо в функциях проверяли активна ли кнопка для данного чекбокса. Так что готов к любой критике, надеюсь мой гайд хоть немножко помог пользователям данного форума)
C++:
static const char* const KeyNames[] = {
    "Unknown",
    "MOUSE1",
    "MOUSE2",
    "CANCEL",
    "MOUSE3",
    "MOUSE4",
    "MOUSE5",
    "Unknown",
    "BACKSPACE",
    "TAB",
    "Unknown",
    "Unknown",
    "CLEAR",
    "ENTER",
    "Unknown",
    "Unknown",
    "SHIFT",
    "CTRL",
    "ALT",
    "PAUSE BREAK",
    "CAPS LOCK",
    "KANA",
    "Unknown",
    "JUNJA",
    "FINAL",
    "KANJI",
    "Unknown",
    "ESC",
    "CON",
    "VK_NONCONVERT",
    "ACCEPT",
    "M CHANGE",
    "SPACE",
    "PGUP",
    "PGDOWN",
    "END",
    "HOME",
    "LEFT",
    "UP",
    "RIGHT",
    "DOWN",
    "SELECT",
    "PRINT",
    "EXECUTE",
    "SNAP",
    "INSIRT",
    "DELETE",
    "HELP",
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "A",
    "B",
    "C",
    "D",
    "E",
    "F",
    "G",
    "H",
    "I",
    "J",
    "K",
    "L",
    "M",
    "N",
    "O",
    "P",
    "Q",
    "R",
    "S",
    "T",
    "U",
    "V",
    "W",
    "X",
    "Y",
    "Z",
    "LWIN",
    "RWIN",
    "APPS",
    "Unknown",
    "SLEEP",
    "N0",
    "N1",
    "N2",
    "N3",
    "N4",
    "N5",
    "N6",
    "N7",
    "N8",
    "N9",
    "MULTIPLY",
    "ADD",
    "SEPARATOR",
    "SUBTRACT",
    "DECIMAL",
    "DIVIDE",
    "F1",
    "F2",
    "F3",
    "F4",
    "F5",
    "F6",
    "F7",
    "F8",
    "F9",
    "F10",
    "F11",
    "F12",
    "F13",
    "F14",
    "F15",
    "F16",
    "F17",
    "F18",
    "F19",
    "F20",
    "F21",
    "F22",
    "F23",
    "F24",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "NUMLOCK",
    "SCROLL LOCK",
    "OEM_NEC_EQUAL",
    "OEM_FJ_MASSHOU",
    "OEM_FJ_TOUROKU",
    "OEM_FJ_LOYA",
    "OEM_FJ_ROYA",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "Unknown",
    "LSHIFT",
    "RSHIFT",
    "LCTRL",
    "RCTRL",
    "LMENU",
    "RMENU"
};

Так же вот вам и SS:
1654277250962.png1654277276981.png
 
Начинающий
Статус
Оффлайн
Регистрация
6 Мар 2022
Сообщения
12
Реакции[?]
8
Поинты[?]
0
В целом направление верное, но CHotKeysSystem::Run() вызывает боль в анальном отверстии, ровно как и реализация popup через обычное окно. Реализовывать изменение значений нужно в Wndproc самостоятельно и единоразово, иначе при активном бинде не будет возможности изменить значение в меню ( при тоггле ).

Я один из девов onefluid.tech
 
Участник
Статус
Оффлайн
Регистрация
15 Янв 2021
Сообщения
500
Реакции[?]
311
Поинты[?]
101K
Я один из девов onefluid.tech
Так там же вроде один разраб как раз таки... по поводу Popup, знаю, что по хорошему было делать это не через Begin/End. Я так изначально и пытался, но почему-то толком не работало ничего и я забил, решив так сделать, что тоже не самое плохоё моё решение.
иначе при активном бинде не будет возможности изменить значение в меню ( при тоггле )
Хм, странно, но у меня всё корректно меняется, мб данный прикол чуть позже замечу, но пока не наблюдаю. но приметил ещё один баг в коде, связанный с кнопкой добавления Бинда. В общем есть над чем работать, если что тему буду обновлять.
 
Эксперт
Статус
Оффлайн
Регистрация
31 Авг 2018
Сообщения
1,792
Реакции[?]
1,073
Поинты[?]
29K
C++:
// Структура нашего бинда специально для чекбокса
struct ImGui_HotKey_Checkbox
{
    int id = -1;
    std::string name;
    std::string label_name;
    int key = 0;
    int mode = MODE_HOLD;

    bool* value = nullptr;

    ImGui_HotKey_Checkbox()
    {
        id = -1;
        label_name = "";
        key = 0;
    }

    ImGui_HotKey_Checkbox(int _id, bool* _value)
    {
        id = _id;
        label_name = "";
        key = 0;
        value = _value;
    }
};
Если у тебя имеется ключ(id), объясни мне пожалуйста на какой блядский хуй тебе вектор? ебани ты std::unordered_map, выкинь айдишники из структуры, и будет у тебя все заебись
C++:
// Проверка, существует ли кейбинды для нашего checkbox'а, если нет, то создаём и ставим current_item на
// последний добавленный элемент
if (std::all_of(data.begin(), data.end(), [](ImGui_HotKey_Checkbox h) {return h.id != current_checkbox.id; }))
{ 
    data.push_back(ImGui_HotKey_Checkbox::ImGui_HotKey_Checkbox(current_checkbox.id, current_checkbox.value));
    current_item = data.size() - 1;
}
Это кстати то, почему тебе нужно использовать std::unordered_map и имеющийся в нем метод find. Однако если по каким-то религиозным причинам тебе им запрещено пользоваться - std::ranges::find + проекция на айдишник - к вашим услугам
P.S: Все таки рекомендую unordered_map - Поиск O(1) гораздо лучше чем O(n), и чем больше элементов в контейнере тем эта разница существеннее
Код:
void CHotKeysSystem::Run()
{
    // Checkbox checking
    auto checkbox_hotkeys = ImGui_HotKeys::checkbox_hotkeys;

    for (auto &hk : ImGui_HotKeys::checkbox_hotkeys)
    {
        if (hk.mode == MODE_HOLD)
        {
            *hk.value = InputSys::Get().IsKeyDown(hk.key);
        }

        if (hk.mode == MODE_TOGGLE)
        {
            if (InputSys::Get().WasKeyPressed(hk.key))
                *hk.value = !(*hk.value);
        }
    }
}
Ну бляха муха, изменяй ты значения прямо в wndproc, нахуя эти костыли то городить?

Короче код хуйня, мне не нравится, так что использовать не советую
 
Участник
Статус
Оффлайн
Регистрация
15 Янв 2021
Сообщения
500
Реакции[?]
311
Поинты[?]
101K
Сверху Снизу