Skip to content

Instantly share code, notes, and snippets.

@Flix01
Last active March 1, 2024 14:15
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Flix01/78182e9c9e0f9dfad79619e56530568a to your computer and use it in GitHub Desktop.
Save Flix01/78182e9c9e0f9dfad79619e56530568a to your computer and use it in GitHub Desktop.
Complete keyboard prototype for Dear ImGui version 1.87.
/* This code is an extension of the 'keyboard section' present in 'imgui_demo.cpp'.
License is the same (MIT License AFAIK)
*/
#include <imgui_virtual_keyboard.h>
namespace ImGui {
// VirtualKeyboard Implementation
const char** GetKeyboardLogicalLayoutNames() {
static const char* names[] = {"QWERTY","QWERTZ","AZERTY"};
IM_STATIC_ASSERT(IM_ARRAYSIZE(names)==KLL_COUNT);
return names;
}
const char** GetKeyboardPhysicalLayoutNames() {
static const char* names[] = {"ANSI","ISO","JIS"};
IM_STATIC_ASSERT(IM_ARRAYSIZE(names)==KPL_COUNT);
return names;
}
static void ImImplPrivateVirtualKeyboardResetLabels(const char* keyLabels[ImGuiKey_NamedKey_COUNT]) {
static const char* const KeyLabels[] = {
"|<->|", "<", ">", "^", "v", "Pg^", "Pgv",
"Home", "End", "Ins", "Canc", "<-", "", "Enter", "Esc",
"Ctrl", "^", "Alt", "Super", "Ctrl", "^", "Alt", "Super", "Menu",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "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",
"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
"'", ",", "-", ".", "/", ";", "=", "(",
"\\", ")", "`", "CapsL", "ScrL", "NumL", "Print", // [...] graveaccent, capsL scrollL numL Print
"Pause", "0", "1", "2", "3", "4", "5", "6",
"7", "8", "9", ".", "/", "*",
"-", "+", "Ent", "=",
"Start", "Back", "FaceUp", "FaceDown", "FaceLeft", "FaceRight",
"^", "v", "<", ">", // Dpad
"L1", "R1", "L2", "R2", "L3", "R3",
"^", "v", "<", ">", // LStick
"^", "v", "<", ">", // RStick
"ModCtrl", "ModShift", "ModAlt", "ModSuper"
};
IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(KeyLabels));
IM_ASSERT(keyLabels);
memcpy(keyLabels,&KeyLabels[0],ImGuiKey_NamedKey_COUNT*sizeof(char*));
}
ImGuiKey VirtualKeyboard(VirtualKeyboardFlags flags,KeyboardLogicalLayout logicalLayout,KeyboardPhysicalLayout physicalLayout) {
IM_ASSERT(logicalLayout!=KLL_COUNT && physicalLayout!=KPL_COUNT);
const float sf = GetFontSize()/12.f; // scaling factor
const float key_rounding = 3.0f;
const ImVec2 key_face_size = ImVec2(25.0f, 25.0f)*sf;
const float key_border_width = 10.f*sf;
const ImVec2 key_size_base = ImVec2(key_face_size.x+key_border_width, key_face_size.y+key_border_width);
const ImVec2 key_face_pos = ImVec2(5.0f, 3.0f)*sf;
const float key_face_rounding = 2.0f;
const ImVec2 key_label_pos = ImVec2(7.0f, 4.0f)*sf;
const ImVec2 key_step = ImVec2(key_size_base.x - 1.0f*sf, key_size_base.y - 1.0f*sf);
const float thickness = 1.f*sf; // used by AddRect(...) and AddLine(...) calls
const float thicknessDouble = 2.f*thickness;
ImVec2 board_min = GetCursorScreenPos();
//ImVec2 board_max = ImVec2(board_min.x + 3 * key_step.x + 2 * key_row_offset + 10.0f, board_min.y + 3 * key_step.y + 10.0f);
//ImVec2 start_pos = ImVec2(board_min.x + 5.0f - key_step.x, board_min.y);
ImVec2 start_pos = ImVec2(board_min.x, board_min.y);
// These should match the 'GKeyNames' variable in 'imgui.cpp', displaying what's written on the keys... most values are unused for now...
static const char* KeyLabels[ImGuiKey_NamedKey_COUNT] = {};
// USAGE:
//# define IMIMPL_KEY_LABEL(key) ((key==ImGuiKey_None)?"":KeyLabels[key-ImGuiKey_NamedKey_BEGIN]):
//IM_ASSERT(key>=ImGuiKey_NamedKey_BEGIN && key<ImGuiKey_NamedKey_END);
struct KeyLayoutName {ImGuiKey key;float width;};
static KeyLayoutName keyNamesRows[6][26] ={
// keyNamesRows[row in 0-6][column in 0-26]
// rows are trivial: 0 -> F1, F2, etc.; 5 -> Ctrl, Super, Alt, Spacebar, etc.
// columns:
//
// |---------------------------------|---------------------|-------------------|
// 0 17 18 21 22 26
// | Base | Arrow | Keypad |
// ISO Layout
{{ImGuiKey_Escape,1.75f/1.4f},{ImGuiKey_None,-0.9f/1.2f},{ImGuiKey_F1,1.f},{ImGuiKey_F2,1.f},{ImGuiKey_F3,1.f},{ImGuiKey_F4,1.f},{ImGuiKey_None,-0.52f/1.2f},{ImGuiKey_F5,1.f},{ImGuiKey_F6,1.f},{ImGuiKey_F7,1.f},{ImGuiKey_F8,1.f},{ImGuiKey_None,-0.52f/1.2f},{ImGuiKey_F9,1.f},{ImGuiKey_F10,1.f},{ImGuiKey_F11,1.f},{ImGuiKey_F12,1.f},{ImGuiKey_None,0.f}, {ImGuiKey_None,-0.52f}, {ImGuiKey_PrintScreen,1.f},{ImGuiKey_ScrollLock,1.f},{ImGuiKey_Pause,1.f}, {ImGuiKey_None,0.f}, {ImGuiKey_None,0.f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f} },
{{ImGuiKey_None,1.f},{ImGuiKey_1,1.f},{ImGuiKey_2,1.f},{ImGuiKey_3,1.f},{ImGuiKey_4,1.f},{ImGuiKey_5,1.f},{ImGuiKey_6,1.f},{ImGuiKey_7,1.f},{ImGuiKey_8,1.f},{ImGuiKey_9,1.f},{ImGuiKey_0,1.f},{ImGuiKey_None,1.f},{ImGuiKey_None,1.f},{ImGuiKey_None,0.f},{ImGuiKey_Backspace,3.25f/1.2f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f}, {ImGuiKey_None,-0.52f}, {ImGuiKey_Insert,1.f},{ImGuiKey_Home,1.f},{ImGuiKey_PageUp,1.f}, {ImGuiKey_None,-0.52f/1.2f}, {ImGuiKey_NumLock,1.f},{ImGuiKey_KeypadDivide,1.f},{ImGuiKey_KeypadMultiply,1.f},{ImGuiKey_KeypadSubtract,1.f} },
{{ImGuiKey_Tab,2.15f/1.2f},{ImGuiKey_Q,1.f},{ImGuiKey_W,1.f},{ImGuiKey_E,1.f},{ImGuiKey_R,1.f},{ImGuiKey_T,1.f},{ImGuiKey_Y,1.f},{ImGuiKey_U,1.f},{ImGuiKey_I,1.f},{ImGuiKey_O,1.f},{ImGuiKey_P,1.f},{ImGuiKey_None,1.f},{ImGuiKey_None,1.f},{ImGuiKey_Enter,2.3f/1.2f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f}, {ImGuiKey_None,-0.52f}, {ImGuiKey_Delete,1.f},{ImGuiKey_End,1.f},{ImGuiKey_PageDown,1.f}, {ImGuiKey_None,-0.52f/1.2f}, {ImGuiKey_Keypad7,1.f},{ImGuiKey_Keypad8,1.f},{ImGuiKey_Keypad9,1.f},{ImGuiKey_KeypadAdd,1.f} },
{{ImGuiKey_CapsLock,2.7f/1.2f},{ImGuiKey_A,1.f},{ImGuiKey_S,1.f},{ImGuiKey_D,1.f},{ImGuiKey_F,1.f},{ImGuiKey_G,1.f},{ImGuiKey_H,1.f},{ImGuiKey_J,1.f},{ImGuiKey_K,1.f},{ImGuiKey_L,1.f},{ImGuiKey_None,1.f},{ImGuiKey_None,1.f},{ImGuiKey_None,1.f},{ImGuiKey_Enter,1.74f/1.2f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f}, {ImGuiKey_None,-0.52f}, {ImGuiKey_None,-1.f},{ImGuiKey_None,-1.f},{ImGuiKey_None,-1.f}, {ImGuiKey_None,-0.52f/1.2f}, {ImGuiKey_Keypad4,1.f},{ImGuiKey_Keypad5,1.f},{ImGuiKey_Keypad6,1.f},{ImGuiKey_KeypadAdd,1.f} },
{{ImGuiKey_LeftShift,1.74f/1.2f},{ImGuiKey_None,1.f},{ImGuiKey_Z,1.f},{ImGuiKey_X,1.f},{ImGuiKey_C,1.f},{ImGuiKey_V,1.f},{ImGuiKey_B,1.f},{ImGuiKey_N,1.f},{ImGuiKey_M,1.f},{ImGuiKey_None,1.f},{ImGuiKey_None,1.f},{ImGuiKey_None,1.f},{ImGuiKey_None,0.f},{ImGuiKey_RightShift,4.4f/1.2f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f}, {ImGuiKey_None,-0.6f/1.2f}, {ImGuiKey_None,-1.f},{ImGuiKey_UpArrow,1.f},{ImGuiKey_None,-1.f}, {ImGuiKey_None,-0.52f/1.2f}, {ImGuiKey_Keypad1,1.f},{ImGuiKey_Keypad2,1.f},{ImGuiKey_Keypad3,1.f},{ImGuiKey_KeypadEnter,1.f} },
{{ImGuiKey_LeftCtrl,1.74f/1.2f},{ImGuiKey_LeftSuper,1.74f/1.2f},{ImGuiKey_LeftAlt,1.74f/1.2f},{ImGuiKey_None,0.f},{ImGuiKey_Space,9.6f/1.2f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f},{ImGuiKey_RightAlt,1.74f/1.2f},{ImGuiKey_RightSuper,1.74f/1.2f},{ImGuiKey_Menu,1.74f/1.2f},{ImGuiKey_RightCtrl,1.74f/1.2f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f},{ImGuiKey_None,0.f}, {ImGuiKey_None,-0.52f/1.2f}, {ImGuiKey_LeftArrow,1.f},{ImGuiKey_DownArrow,1.f},{ImGuiKey_RightArrow,1.f}, {ImGuiKey_None,-0.52f/1.2f}, {ImGuiKey_Keypad0,2.9f/1.2f},{ImGuiKey_None,0.f},{ImGuiKey_KeypadDecimal,1.f},{ImGuiKey_KeypadEnter,1.f} }
};
static KeyboardPhysicalLayout phyLayout = KPL_COUNT; // KPL_ISO, but it's just for initializing stuff
static KeyboardLogicalLayout logLayout = KLL_QWERTY;
if (phyLayout==KPL_COUNT) {
phyLayout = KPL_ISO;
ImImplPrivateVirtualKeyboardResetLabels(KeyLabels);
// well, we should do this every frame (or store a reference of the last checked font),
// but we just do it once at startup
static const char* utf8[] = {
"\xe2\x87\xa7", // 0x21E7 ⇧
"\xe2\xac\x86", // 0x2B06 ⬆
"\xe2\x86\x91", // 0x2191 ↑
"\xe2\x9f\xb5", // 0x27F5 ⟵
"\xe2\x86\x90", // 0x2190 ←
"\xe2\x86\x96", // 0x2196 ↖
"\xe2\x8c\x82", // 0x2302 ⌂
"\xe2\x8f\x8f", // 0x23CF ⏏
"\xe2\x86\xb9", // 0x21B9 ↹
"\xe2\x87\xa4\n\xe2\x87\xa5", // 0x21E4,0x21E5 ⇤⇥
"\xe2\x86\x92", // 0x2191 →
"\xe2\x86\x93", // 0x2191 ↓
"Pg\xe2\x86\x91", // 0x2191 Pg↑
"Pg\xe2\x86\x93", // 0x2193 Pg↓
"\xe2\x88\x97", // 0x2217 ∗
"\xe2\x88\x92", // 0x2212 −
"\xe2\x8f\xb8", // 0x23f8 ⏸
"\xe2\x96\xae\xe2\x96\xae", // 0x25AE ▮▮
"\xe2\x96\xae\xe2\x96\xaf", // 0x25AF ▯▯
"Enter\n\xe2\x86\xb5", // 0x21B5 ↵
"Enter\n\xe2\x86\xa9", // 0x21A9 ↩
"Enter\n\xe2\xa4\xb6" // 0x2936 ⤶
};
const ImFont* font = ImGui::GetFont();
# define IMIMPL_CHANGE_GLYPH(K,C,S) if (font->FindGlyphNoFallback((C))) KeyLabels[(K)-ImGuiKey_NamedKey_BEGIN]=S
# define IMIMPL_CHANGE_GLYPH_FALLBACK1(K,C,S,C1,S1) if (font->FindGlyphNoFallback((C))) KeyLabels[(K)-ImGuiKey_NamedKey_BEGIN]=S; \
else if (font->FindGlyphNoFallback((C1))) KeyLabels[(K)-ImGuiKey_NamedKey_BEGIN]=S1
# define IMIMPL_CHANGE_GLYPH_FALLBACK2(K,C,S,C1,S1,C2,S2) if (font->FindGlyphNoFallback((C))) KeyLabels[(K)-ImGuiKey_NamedKey_BEGIN]=S; \
else if (font->FindGlyphNoFallback((C1))) KeyLabels[(K)-ImGuiKey_NamedKey_BEGIN]=S1; \
else if (font->FindGlyphNoFallback((C2))) KeyLabels[(K)-ImGuiKey_NamedKey_BEGIN]=S2
# define IMIMPL_CHANGE_GLYPH_ARROWS(KL,KU,KR,KD,CL,CU,CR,CD,SL,SU,SR,SD) if (font->FindGlyphNoFallback((CL)) && font->FindGlyphNoFallback((CU)) && font->FindGlyphNoFallback((CR)) && font->FindGlyphNoFallback((CD))) \
{KeyLabels[(KL)-ImGuiKey_NamedKey_BEGIN]=SL;KeyLabels[(KU)-ImGuiKey_NamedKey_BEGIN]=SU;KeyLabels[(KR)-ImGuiKey_NamedKey_BEGIN]=SR;KeyLabels[(KD)-ImGuiKey_NamedKey_BEGIN]=SD;}
IMIMPL_CHANGE_GLYPH_FALLBACK2(ImGuiKey_LeftShift, 0x21E7,utf8[0],0x2B06,utf8[1],0x2191,utf8[2]);
IMIMPL_CHANGE_GLYPH_FALLBACK2(ImGuiKey_RightShift, 0x21E7,utf8[0],0x2B06,utf8[1],0x2191,utf8[2]);
IMIMPL_CHANGE_GLYPH_FALLBACK1(ImGuiKey_Backspace,0x27F5,utf8[3],0x2190,utf8[4]);
IMIMPL_CHANGE_GLYPH_FALLBACK1(ImGuiKey_Home, 0x2196,utf8[5],0x2302,utf8[6]);
IMIMPL_CHANGE_GLYPH(ImGuiKey_CapsLock, 0x23CF,utf8[7]);
IMIMPL_CHANGE_GLYPH(ImGuiKey_Tab, 0x21B9,utf8[8]);
else if (font->FindGlyphNoFallback(0x21E4) && font->FindGlyphNoFallback(0x21E5)) KeyLabels[ImGuiKey_Tab-ImGuiKey_NamedKey_BEGIN]=utf8[9];
IMIMPL_CHANGE_GLYPH_ARROWS(ImGuiKey_LeftArrow,ImGuiKey_UpArrow,ImGuiKey_RightArrow,ImGuiKey_DownArrow,
0x2190,0x2191,0x2192,0x2193,
utf8[4],utf8[5],utf8[10],utf8[11]) // no semicolon here
IMIMPL_CHANGE_GLYPH_ARROWS(ImGuiKey_GamepadDpadLeft,ImGuiKey_GamepadDpadUp,ImGuiKey_GamepadDpadRight,ImGuiKey_GamepadDpadDown,
0x2190,0x2191,0x2192,0x2193,
utf8[4],utf8[5],utf8[10],utf8[11]) // no semicolon here
IMIMPL_CHANGE_GLYPH_ARROWS(ImGuiKey_GamepadLStickLeft,ImGuiKey_GamepadLStickUp,ImGuiKey_GamepadLStickRight,ImGuiKey_GamepadLStickDown,
0x2190,0x2191,0x2192,0x2193,
utf8[4],utf8[5],utf8[10],utf8[11]) // no semicolon here
IMIMPL_CHANGE_GLYPH_ARROWS(ImGuiKey_GamepadRStickLeft,ImGuiKey_GamepadRStickUp,ImGuiKey_GamepadRStickRight,ImGuiKey_GamepadRStickDown,
0x2190,0x2191,0x2192,0x2193,
utf8[4],utf8[5],utf8[10],utf8[11]) // no semicolon here
IMIMPL_CHANGE_GLYPH_ARROWS(ImGuiKey_GamepadFaceLeft,ImGuiKey_GamepadFaceUp,ImGuiKey_GamepadFaceRight,ImGuiKey_GamepadFaceDown,
0x2190,0x2191,0x2192,0x2193,
utf8[4],utf8[5],utf8[10],utf8[11]) // no semicolon here
IMIMPL_CHANGE_GLYPH(ImGuiKey_PageUp, 0x2191,utf8[12]);
IMIMPL_CHANGE_GLYPH(ImGuiKey_PageDown, 0x2193,utf8[13]);
IMIMPL_CHANGE_GLYPH(ImGuiKey_KeypadMultiply, 0x2217,utf8[14]);
IMIMPL_CHANGE_GLYPH(ImGuiKey_KeypadSubtract, 0x2212,utf8[15]);
IMIMPL_CHANGE_GLYPH_FALLBACK2(ImGuiKey_Pause, 0x23f8,utf8[16],0x25AE,utf8[17],0x25AF,utf8[18]);
IMIMPL_CHANGE_GLYPH_FALLBACK2(ImGuiKey_Enter, 0x21B5,utf8[19],0x21A9,utf8[20],0x2936,utf8[21]);
# undef IMIMPL_CHANGE_GLYPH
# undef IMIMPL_CHANGE_GLYPH_FALLBACK1
# undef IMIMPL_CHANGE_GLYPH_FALLBACK2
# undef IMIMPL_CHANGE_GLYPH_ARROWS
}
IM_ASSERT(phyLayout!=KPL_COUNT && logLayout!=KLL_COUNT);
if (phyLayout!=physicalLayout) {
// we must update the physical layout
phyLayout = physicalLayout;
KeyLayoutName *R1 = keyNamesRows[1], *R2 = keyNamesRows[2], *R3 = keyNamesRows[3], *R4 = keyNamesRows[4], *R5 = keyNamesRows[5];
IM_ASSERT(R1[14].key==ImGuiKey_Backspace);
IM_ASSERT(R2[13].key==ImGuiKey_Enter || R2[13].key==ImGuiKey_Backslash);
IM_ASSERT(R3[13].key==ImGuiKey_Enter);
IM_ASSERT(R4[13].key==ImGuiKey_RightShift);
IM_ASSERT(R5[4].key==ImGuiKey_Space && R5[7].key==ImGuiKey_RightAlt && R5[10].key==ImGuiKey_RightCtrl);
switch (phyLayout) {
case KPL_ANSI: {
R1[13].width=0.f;R1[14].width=3.25f/1.2f;
R2[13].key=ImGuiKey_Backslash;
R3[12].width=0.f;R3[13].width=3.425f/1.2f;
R4[0].width=3.45f/1.2f; R4[1].width=0.f;R4[12].width=0.f;R4[13].width=4.4f/1.2f;//R4[12].width=1.f;R4[13].width=2.725f/1.2f;
R5[3].width=0.f;R5[4].width=9.6f/1.2f;R5[5].width=0.f;R5[6].width=0.f;R5[7].width=1.74f/1.2f;R5[8].width=1.74f/1.2f;R5[9].width=1.74f/1.2f;R5[10].width=1.74f/1.2f;
}
break;
case KPL_ISO: {
R1[13].width=0.f;R1[14].width=3.25f/1.2f;
R2[13].key=ImGuiKey_Enter;
R3[12].width=1.f;R3[13].width=1.74f/1.2f;
R4[0].width=1.74f/1.2f; R4[1].width=1.f;R4[12].width=0.f;R4[13].width=4.4f/1.2f;
R5[3].width=0.f;R5[4].width=9.6f/1.2f;R5[5].width=0.f;R5[6].width=0.f;R5[7].width=1.74f/1.2f;R5[8].width=1.74f/1.2f;R5[9].width=1.74f/1.2f;R5[10].width=1.74f/1.2f;
}
break;
case KPL_JIS: {
R1[13].width=1.f;R1[14].width=1.575f/1.2f;
R2[13].key=ImGuiKey_Enter;
R3[12].width=1.f;R3[13].width=1.74f/1.2f;
R4[0].width=3.45f/1.2f; R4[1].width=0.f;R4[12].width=1.f;R4[13].width=2.725f/1.2f;
R5[3].width=0.98f;R5[4].width=6.1f/1.2f;R5[5].width=0.98f;R5[6].width=0.98f;R5[7].width=1.38f/1.2f;R5[8].width=1.38f/1.2f;R5[9].width=1.38f/1.2f;R5[10].width=1.38f/1.2f;
}
break;
default:
IM_ASSERT(0);
break;
}
}
if (logLayout!=logicalLayout) {
// we must update the keyboard layout
logLayout = logicalLayout;
KeyLayoutName *R2 = keyNamesRows[2], *R3 = keyNamesRows[3], *R4 = keyNamesRows[4];
switch (logLayout) {
case KLL_QWERTY: {
// QWERTY
R2[1].key=ImGuiKey_Q;R2[2].key=ImGuiKey_W;R2[6].key=ImGuiKey_Y;
R3[1].key=ImGuiKey_A;R3[10].key=ImGuiKey_None;
R4[2].key=ImGuiKey_Z;R4[8].key=ImGuiKey_M;
}
break;
case KLL_QWERTZ: {
// QWERTZ
R2[1].key=ImGuiKey_Q;R2[2].key=ImGuiKey_W;R2[6].key=ImGuiKey_Z;
R3[1].key=ImGuiKey_A;R3[10].key=ImGuiKey_None;
R4[2].key=ImGuiKey_Y;R4[8].key=ImGuiKey_M;
}
break;
case KLL_AZERTY: {
// AZERTY
R2[1].key=ImGuiKey_A;R2[2].key=ImGuiKey_Z;R2[6].key=ImGuiKey_Y;
R3[1].key=ImGuiKey_Q;R3[10].key=ImGuiKey_M;
R4[2].key=ImGuiKey_W;R4[8].key=ImGuiKey_None;
}
break;
default:
IM_ASSERT(0);
break;
}
}
ImVec2 cur_pos = start_pos;ImVec2 board_max = start_pos;
ImDrawList* draw_list = GetWindowDrawList();
int rStart = 0,rEnd = 6;
int cStart = 0,cEnd = 26;
if (flags&VirtualKeyboardFlags_ShowBaseBlock && flags&VirtualKeyboardFlags_ShowKeypadBlock) flags|=VirtualKeyboardFlags_ShowArrowBlock;
if (!(flags&VirtualKeyboardFlags_ShowFunctionBlock)) rStart=1;
if (flags&VirtualKeyboardFlags_ShowFunctionBlock && !(flags&(VirtualKeyboardFlags_ShowBaseBlock|VirtualKeyboardFlags_ShowArrowBlock|VirtualKeyboardFlags_ShowKeypadBlock))) rEnd=1;
if (!(flags&(VirtualKeyboardFlags_ShowArrowBlock|VirtualKeyboardFlags_ShowKeypadBlock))) cEnd=17;
else if (!(flags&(VirtualKeyboardFlags_ShowKeypadBlock))) cEnd=21;
if ((flags&VirtualKeyboardFlags_ShowAllBlocks)==VirtualKeyboardFlags_ShowFunctionBlock) {
// user wants only the function block: we don't give him the part above the arrow block
rStart=0;rEnd=1;cStart=0;cEnd=17;
}
else if (!(flags&VirtualKeyboardFlags_ShowBaseBlock)) {
if (flags&VirtualKeyboardFlags_ShowArrowBlock) cStart=18;
else {
cStart=22;
rStart=1;flags&=~VirtualKeyboardFlags_ShowFunctionBlock; // optional line: removes empty space above Keypad when shown alone.
}
}
const bool mouseEnabled = !(flags&VirtualKeyboardFlags_NoMouseInteraction);
const bool keyboardEnabled = !(flags&VirtualKeyboardFlags_NoKeyboardInteraction);
const bool mouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
ImGuiKey mouseClickedKey = ImGuiKey_COUNT;
ImGuiKey keyboardClickedKey = ImGuiKey_COUNT;
ImVec2 key_min_upper_enter(0,0),key_max_upper_enter(0,0);
ImVec2 key_min_lower_enter(0,0),key_max_lower_enter(0,0);
//draw_list->PushClipRect(board_min, board_max, true);
for (int r = rStart; r < rEnd; r++) {
cur_pos.x = start_pos.x;
if (flags&VirtualKeyboardFlags_ShowFunctionBlock) {
cur_pos.y = start_pos.y+r*key_step.y+(r>0?(key_face_size.x*0.7f/1.2f):0.f);
}
else {
IM_ASSERT(r!=0);
cur_pos.y = start_pos.y+(r-1)*key_step.y;
}
const KeyLayoutName *keyDataRow = keyNamesRows[r];
for (int c = cStart; c < cEnd; c++) {
const KeyLayoutName* key_data = &keyDataRow[c];
if (key_data->width==0.f) continue;
if (key_data->width<0.f) {
// spacing entry
IM_ASSERT(key_data->key==ImGuiKey_None);
cur_pos.x+=key_face_size.x*(-key_data->width)+key_border_width;
continue;
}
IM_ASSERT(key_data->key==ImGuiKey_None || (key_data->key>=ImGuiKey_NamedKey_BEGIN && key_data->key<ImGuiKey_NamedKey_END));
const char* key_label = (key_data->key==ImGuiKey_None)?"":KeyLabels[key_data->key-ImGuiKey_NamedKey_BEGIN];
// Draw the key ----------------------------------------------------------------------------
ImDrawFlags df = ImDrawFlags_RoundCornersAll;
ImVec2 key_min = ImVec2(cur_pos.x, cur_pos.y);
ImVec2 key_max = ImVec2(key_min.x + key_face_size.x*key_data->width+key_border_width, key_min.y + key_size_base.y);
ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y);
ImVec2 face_max = ImVec2(face_min.x + key_face_size.x*key_data->width, face_min.y + key_face_size.y);
ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y);
if (key_data->key==ImGuiKey_Enter && phyLayout!=KPL_ANSI) {
if (r==2) {
df = ImDrawFlags_RoundCornersTop|ImDrawFlags_RoundCornersBottomLeft;
key_min_upper_enter = key_min;key_max_upper_enter = key_max;
}
else if (r==3) {
df = ImDrawFlags_RoundCornersBottom;key_label="";
key_min_lower_enter = key_min; key_max_lower_enter = key_max;
key_min_lower_enter.y = key_max_upper_enter.y;
key_min.y = cur_pos.y-6.f*sf; // The last number will be reused twice
face_min.y = key_min.y;
}
else {IM_ASSERT(0);}
}
else if (c==25) {//key_data->key==ImGuiKey_KeypadAdd || key_data->key==ImGuiKey_KeypadEnter)
if (r==2 || r==4) continue;
if (r==3 || r==5) {
// This button requires two rows: draw the whole button at once
if (flags&VirtualKeyboardFlags_ShowFunctionBlock) key_min.y = start_pos.y+(r-1)*key_step.y+key_face_size.x*0.7f/1.2f;
else key_min.y = start_pos.y+(r-2)*key_step.y;
face_min.y = key_min.y + key_face_pos.y;
label_min.y = key_min.y + key_label_pos.y;
}
}
draw_list->AddRectFilled(key_min, key_max, IM_COL32(204, 204, 204, 255), key_rounding, df);
if (key_data->key!=ImGuiKey_Enter || r==2) {
draw_list->AddRect(key_min, key_max, IM_COL32(24, 24, 24, 255), key_rounding, df, thickness);
draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, ImDrawFlags_None, thicknessDouble);
}
else {
IM_ASSERT(r==3);
draw_list->AddLine(ImVec2(key_min.x,key_min.y+6.f*sf), ImVec2(key_min.x,key_max.y), IM_COL32(24, 24, 24, 255), thickness); // left
draw_list->AddLine(ImVec2(key_min.x,key_max.y), key_max, IM_COL32(24, 24, 24, 255), thickness); // bottom
draw_list->AddLine(ImVec2(key_max.x,key_min.y-1.f), key_max, IM_COL32(24, 24, 24, 255), thickness); // right
draw_list->AddLine(ImVec2(face_min.x,face_min.y+6.f*sf), ImVec2(face_min.x,face_max.y), IM_COL32(193, 193, 193, 255), thicknessDouble); // left
draw_list->AddLine(ImVec2(face_min.x,face_max.y), face_max, IM_COL32(193, 193, 193, 255), thicknessDouble); // bottom
draw_list->AddLine(ImVec2(face_max.x,face_min.y), face_max, IM_COL32(193, 193, 193, 255), thicknessDouble); // right
}
draw_list->AddRectFilled(face_min, face_max, IM_COL32(252, 252, 252, 255), key_face_rounding, df);
draw_list->AddText(label_min, IM_COL32(64, 64, 64, 255), key_label);
// Process input and draw the 'mouse hovered' and 'pressed' color----------------------------------
if (key_data->key!=ImGuiKey_Enter || phyLayout==KPL_ANSI) {
if (keyboardEnabled && key_data->key!=ImGuiKey_None) {
if (ImGui::IsKeyDown(key_data->key)) {
draw_list->AddRectFilled(key_min, key_max, IM_COL32(255, 0, 0, 128), key_rounding, df);
}
if ((keyboardClickedKey==ImGuiKey_COUNT || keyboardClickedKey==ImGuiKey_None) && ImGui::IsKeyReleased(key_data->key)) keyboardClickedKey = key_data->key;
}
if (mouseEnabled) {
if (ImGui::IsMouseHoveringRect(key_min,key_max)) {
const ImU32 color = mouseDown ? IM_COL32(255, 0, 0, 128) : IM_COL32(0, 0, 0, 32);
if (mouseDown) mouseClickedKey = key_data->key;
draw_list->AddRectFilled(key_min, key_max, color, key_rounding, df);
}
}
}
else if (r==3) {
IM_ASSERT(key_data->key==ImGuiKey_Enter);
if (keyboardEnabled && key_data->key!=ImGuiKey_None) {
if (ImGui::IsKeyDown(key_data->key)) {
draw_list->AddRectFilled(key_min_upper_enter, key_max_upper_enter, IM_COL32(255, 0, 0, 128), key_rounding, ImDrawFlags_RoundCornersTop|ImDrawFlags_RoundCornersBottomLeft);
draw_list->AddRectFilled(key_min_lower_enter, key_max_lower_enter, IM_COL32(255, 0, 0, 128), key_rounding, ImDrawFlags_RoundCornersBottom);
}
if ((keyboardClickedKey==ImGuiKey_COUNT || keyboardClickedKey==ImGuiKey_None) && ImGui::IsKeyReleased(key_data->key)) keyboardClickedKey = key_data->key;
}
if (mouseEnabled) {
if (ImGui::IsMouseHoveringRect(key_min_upper_enter,key_max_upper_enter) || ImGui::IsMouseHoveringRect(key_min_lower_enter,key_max_lower_enter)) {
const ImU32 color = mouseDown ? IM_COL32(255, 0, 0, 128) : IM_COL32(0, 0, 0, 32);
if (mouseDown) mouseClickedKey = key_data->key;
draw_list->AddRectFilled(key_min_upper_enter, key_max_upper_enter, color, key_rounding, ImDrawFlags_RoundCornersTop|ImDrawFlags_RoundCornersBottomLeft);
draw_list->AddRectFilled(key_min_lower_enter, key_max_lower_enter, color, key_rounding, ImDrawFlags_RoundCornersBottom);
}
}
}
//----------------------------------------------------------------------------------------------------------------
cur_pos.x = key_max.x;
board_max.y = key_max.y;
}
board_max.x = cur_pos.x;
}
//draw_list->PopClipRect();
ImGui::Dummy(ImVec2(board_max.x - board_min.x, board_max.y - board_min.y));
if (mouseEnabled) {
if (!ImGui::IsItemClicked(ImGuiMouseButton_Left)) mouseClickedKey = ImGuiKey_COUNT;
//else {printf("keyClicked: %s\n",mouseClickedKey==ImGuiKey_None?"None":ImGui::GetKeyName(mouseClickedKey));fflush(stdout);}
}
if (keyboardClickedKey!=ImGuiKey_COUNT) {
if (mouseClickedKey==ImGuiKey_COUNT || mouseClickedKey==ImGuiKey_None) return keyboardClickedKey;
else return mouseClickedKey;
}
else return mouseClickedKey;
}
} // namespace ImGui
namespace ImGui {
// Virtual Keyboard
// USAGE: to get started, just call VirtualKeyboard() in one of your ImGui windows
enum KeyboardLogicalLayout {
KLL_QWERTY = 0,
KLL_QWERTZ,
KLL_AZERTY,
KLL_COUNT
};
IMGUI_API const char** GetKeyboardLogicalLayoutNames();
enum KeyboardPhysicalLayout {
KPL_ANSI = 0,
KPL_ISO,
KPL_JIS,
KPL_COUNT
};
IMGUI_API const char** GetKeyboardPhysicalLayoutNames();
enum VirtualKeyboardFlags_ {
VirtualKeyboardFlags_ShowBaseBlock = 1<<0,
VirtualKeyboardFlags_ShowFunctionBlock = 1<<1,
VirtualKeyboardFlags_ShowArrowBlock = 1<<2, // This can't be excluded when both VirtualKeyboardFlags_BlockBase and VirtualKeyboardFlags_BlockKeypad are used
VirtualKeyboardFlags_ShowKeypadBlock = 1<<3,
VirtualKeyboardFlags_ShowAllBlocks = VirtualKeyboardFlags_ShowBaseBlock|VirtualKeyboardFlags_ShowFunctionBlock|VirtualKeyboardFlags_ShowArrowBlock|VirtualKeyboardFlags_ShowKeypadBlock,
VirtualKeyboardFlags_NoMouseInteraction = 1<<4,
VirtualKeyboardFlags_NoKeyboardInteraction = 1<<5,
VirtualKeyboardFlags_NoInteraction = VirtualKeyboardFlags_NoMouseInteraction | VirtualKeyboardFlags_NoKeyboardInteraction
};
typedef int VirtualKeyboardFlags;
// Displays a virtual keyboard.
// It always returns ImGuiKey_COUNT, unless:
// a) a mouse is clicked (clicked event) on a key AND flag VirtualKeyboardFlags_NoMouseInteraction is not used (DEFAULT)
// b) a key is typed (released event) AND VirtualKeyboardFlags_NoKeyboardInteraction is not used (DEFAULT). Note that multiple keys can be pressed together, but only one is returned.
// In that case, it returns the clicked (or typed) key.
IMGUI_API ImGuiKey VirtualKeyboard(VirtualKeyboardFlags flags=VirtualKeyboardFlags_ShowAllBlocks,KeyboardLogicalLayout logicalLayout=KLL_QWERTY,KeyboardPhysicalLayout physicalLayout=KPL_ISO);
// Possible improvements (in random order):
// 1) The L-shaped enter key is not displayed perfectly. Improve it.
// 2) Add entries to the KeyboardLogicalLayout enum and implement keyboards for specific countries, riducing the number of 'empty' keys present in the general layout.
}
@Flix01
Copy link
Author

Flix01 commented Jan 25, 2022

REVISION 4

image

CHANGELOG

  • Removed the VirtualKeyboardFlags_ReturnKeyboardEntryToo flag (enabled by default)
  • Now the keyboard correctly scales with the font size
  • If some specific unicode codepoints are present in the font, they are automatically used to improve rendering some key labels

REVISION 3

CHANGELOG

  • Changed ImGui::VirtualKeyboard(...) signature
  • Added 3 "keyboard physical layouts": ANSI, ISO and JIS
  • Moved the keys: Print, ScrollLock and Pause one row above (in their standard position, at the right side of the "function keys")
  • Changed the names of the 'visibility' flags
  • Now when VirtualKeyboardFlags_ReturnKeyboardEntryToo is used, each key is reported only once (using the "key released" event)

REVISION 2 #### USAGE To get started, in one valid ImGui window, just add the line: ImGui::VirtualKeyboard(); #### SCREENSHOT ![vk](https://user-images.githubusercontent.com/9608982/151658527-a2d65cc6-9a6b-4c80-b097-8525b383b08b.png)

REVISION 1

image

@fobo
Copy link

fobo commented Dec 8, 2023

Hi, this looks great! I got the keyboard to work in my window but I am unsure of how you would link up the key presses to possibly put the associated letter into a text field.

@Flix01
Copy link
Author

Flix01 commented Dec 9, 2023

Hi, this looks great! I got the keyboard to work in my window but I am unsure of how you would link up the key presses to possibly put the associated letter into a text field.

Well, AFAIK, imgui.h provides only the "debug" function: const char* ImGui::GetKeyName(ImGuiKey key), that returns a const char*. However, ATM I don't remember an easy way to append a string to an ImGui::InputText(...). Maybe setting an ImGuiInputTextCallback is required, but I don't remember right now.

Also note that there is no way to detect if an ImGuiKey character is uppercase or lowercase...

@monkeyx-net
Copy link

Hi, Thanks for publishing this. I am getting a compile error

keys.cpp: In function ‘void ImGui::ImImplPrivateVirtualKeyboardResetLabels(const char**)’:
keys.cpp:91:46: error: static assertion failed
91 | IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(KeyLabels));
../imgui_internal.h:242:55: note: in definition of macro ‘IM_STATIC_ASSERT’
242 | #define IM_STATIC_ASSERT(_COND) static_assert(_COND, "")

I can remark the file, but I think this prevents the keyboard resizing.

Could you also please give an example of invoking the keyboard as shown in the screenshot. I am not sure how to pass the flags needed to do this

@Flix01
Copy link
Author

Flix01 commented Feb 27, 2024

91 | IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(KeyLabels));
242 | #define IM_STATIC_ASSERT(_COND) static_assert(_COND, "")

Try: 242 | IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(KeyLabels),"Error: wrong KeyLabels size");

If the assert triggers, some new keys have been added to the ImGuiKey_NamedKey enum after the date Revision 4 of this gist was posted.

Could you also please give an example of invoking the keyboard as shown in the screenshot. I am not sure how to pass the flags needed to do this

In the WebGL Demo the virtual keyboard can be found inside imguivariouscontrols/virtual keyboard.
The invoking source code is here: Code. Please note that ImGui::CheckboxFlags is a custom widget, but that's only used to turn on and off some flags in the bitmap mask: so you can just call ImGui::VirtualKeyboard with the flags you need.
Also note that maybe the code Here might be more recent than the one in this gist (but for sure it's not up to date with the upstream Dear ImGui version).

Hope this helps.

@monkeyx-net
Copy link

Thanks for your help. I tracked the GKeyNames[] in imgui.cpp and added them to your implementation and that removed the error.

I have also been able to see some examples of using the flags and can change the layout etc.

Do you know if this is likely to be ever added as an official add on?

@Flix01
Copy link
Author

Flix01 commented Feb 28, 2024

Do you know if this is likely to be ever added as an official add on?

What are "official" addons?

@monkeyx-net
Copy link

monkeyx-net commented Feb 29, 2024 via email

@Flix01
Copy link
Author

Flix01 commented Feb 29, 2024

I was thinking listing it here to give more visibility?
https://github.com/ocornut/imgui/wiki/Useful-Extensions

Well, this gist is already contained in the Flix01's ImGui-Addons entry present in the link you posted (my links in the previous post pointed to it).

Also note that here I've just extended the "keyboard block" that was already present here:
image
So the code is not 100% original.

If you need any help testing or developing this further please let me know.
I've already integrated it into a project I'm working on to use on devices without a keyboard.
They can emulate a mouse though.
Turning the keyboard into buttons possibly too

Thank you for your support.
I'm not sure if I'll develop it further.

P.S. I guess Dear ImGui is currently try to take the hard route of supporting local country keys, so maybe in the future this code could be improved... (not an easy task in my opinion).

@Flix01
Copy link
Author

Flix01 commented Mar 1, 2024

@monkeyx-net if you want to fork/copy this repository or create a new one to develop this code further, fell free to do it! It's all open-source!

@monkeyx-net
Copy link

monkeyx-net commented Mar 1, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment