imgui_impl_allegro5.cpp (18475B)
1 // dear imgui: Renderer + Platform Backend for Allegro 5 2 // (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.) 3 4 // Implemented features: 5 // [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as ImTextureID. Read the FAQ about ImTextureID! 6 // [X] Platform: Clipboard support (from Allegro 5.1.12) 7 // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. 8 // Issues: 9 // [ ] Renderer: The renderer is suboptimal as we need to unindex our buffers and convert vertices manually. 10 // [ ] Platform: Missing gamepad support. 11 12 // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. 13 // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. 14 // Read online: https://github.com/ocornut/imgui/tree/master/docs 15 16 // CHANGELOG 17 // (minor and older changes stripped away, please see git history for details) 18 // 2021-05-19: Renderer: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) 19 // 2021-02-18: Change blending equation to preserve alpha in output buffer. 20 // 2020-08-10: Inputs: Fixed horizontal mouse wheel direction. 21 // 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor. 22 // 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter. 23 // 2019-05-11: Inputs: Don't filter character value from ALLEGRO_EVENT_KEY_CHAR before calling AddInputCharacter(). 24 // 2019-04-30: Renderer: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. 25 // 2018-11-30: Platform: Added touchscreen support. 26 // 2018-11-30: Misc: Setting up io.BackendPlatformName/io.BackendRendererName so they can be displayed in the About Window. 27 // 2018-06-13: Platform: Added clipboard support (from Allegro 5.1.12). 28 // 2018-06-13: Renderer: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. 29 // 2018-06-13: Renderer: Backup/restore transform and clipping rectangle. 30 // 2018-06-11: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag. 31 // 2018-04-18: Misc: Renamed file from imgui_impl_a5.cpp to imgui_impl_allegro5.cpp. 32 // 2018-04-18: Misc: Added support for 32-bit vertex indices to avoid conversion at runtime. Added imconfig_allegro5.h to enforce 32-bit indices when included from imgui.h. 33 // 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplAllegro5_RenderDrawData() in the .h file so you can call it yourself. 34 // 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. 35 // 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. 36 37 #include <stdint.h> // uint64_t 38 #include <cstring> // memcpy 39 #include "imgui.h" 40 #include "imgui_impl_allegro5.h" 41 42 // Allegro 43 #include <allegro5/allegro.h> 44 #include <allegro5/allegro_primitives.h> 45 #ifdef _WIN32 46 #include <allegro5/allegro_windows.h> 47 #endif 48 #define ALLEGRO_HAS_CLIPBOARD (ALLEGRO_VERSION_INT >= ((5 << 24) | (1 << 16) | (12 << 8))) // Clipboard only supported from Allegro 5.1.12 49 50 // Visual Studio warnings 51 #ifdef _MSC_VER 52 #pragma warning (disable: 4127) // condition expression is constant 53 #endif 54 55 // Data 56 static ALLEGRO_DISPLAY* g_Display = NULL; 57 static ALLEGRO_BITMAP* g_Texture = NULL; 58 static double g_Time = 0.0; 59 static ALLEGRO_MOUSE_CURSOR* g_MouseCursorInvisible = NULL; 60 static ALLEGRO_VERTEX_DECL* g_VertexDecl = NULL; 61 static char* g_ClipboardTextData = NULL; 62 63 struct ImDrawVertAllegro 64 { 65 ImVec2 pos; 66 ImVec2 uv; 67 ALLEGRO_COLOR col; 68 }; 69 70 static void ImGui_ImplAllegro5_SetupRenderState(ImDrawData* draw_data) 71 { 72 // Setup blending 73 al_set_separate_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA); 74 75 // Setup orthographic projection matrix 76 // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). 77 { 78 float L = draw_data->DisplayPos.x; 79 float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; 80 float T = draw_data->DisplayPos.y; 81 float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; 82 ALLEGRO_TRANSFORM transform; 83 al_identity_transform(&transform); 84 al_use_transform(&transform); 85 al_orthographic_transform(&transform, L, T, 1.0f, R, B, -1.0f); 86 al_use_projection_transform(&transform); 87 } 88 } 89 90 // Render function. 91 void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data) 92 { 93 // Avoid rendering when minimized 94 if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) 95 return; 96 97 // Backup Allegro state that will be modified 98 ALLEGRO_TRANSFORM last_transform = *al_get_current_transform(); 99 ALLEGRO_TRANSFORM last_projection_transform = *al_get_current_projection_transform(); 100 int last_clip_x, last_clip_y, last_clip_w, last_clip_h; 101 al_get_clipping_rectangle(&last_clip_x, &last_clip_y, &last_clip_w, &last_clip_h); 102 int last_blender_op, last_blender_src, last_blender_dst; 103 al_get_blender(&last_blender_op, &last_blender_src, &last_blender_dst); 104 105 // Setup desired render state 106 ImGui_ImplAllegro5_SetupRenderState(draw_data); 107 108 // Render command lists 109 for (int n = 0; n < draw_data->CmdListsCount; n++) 110 { 111 const ImDrawList* cmd_list = draw_data->CmdLists[n]; 112 113 // Allegro's implementation of al_draw_indexed_prim() for DX9 is completely broken. Unindex our buffers ourselves. 114 // FIXME-OPT: Unfortunately Allegro doesn't support 32-bit packed colors so we have to convert them to 4 float as well.. 115 static ImVector<ImDrawVertAllegro> vertices; 116 vertices.resize(cmd_list->IdxBuffer.Size); 117 for (int i = 0; i < cmd_list->IdxBuffer.Size; i++) 118 { 119 const ImDrawVert* src_v = &cmd_list->VtxBuffer[cmd_list->IdxBuffer[i]]; 120 ImDrawVertAllegro* dst_v = &vertices[i]; 121 dst_v->pos = src_v->pos; 122 dst_v->uv = src_v->uv; 123 unsigned char* c = (unsigned char*)&src_v->col; 124 dst_v->col = al_map_rgba(c[0], c[1], c[2], c[3]); 125 } 126 127 const int* indices = NULL; 128 if (sizeof(ImDrawIdx) == 2) 129 { 130 // FIXME-OPT: Unfortunately Allegro doesn't support 16-bit indices.. You can '#define ImDrawIdx int' in imconfig.h to request Dear ImGui to output 32-bit indices. 131 // Otherwise, we convert them from 16-bit to 32-bit at runtime here, which works perfectly but is a little wasteful. 132 static ImVector<int> indices_converted; 133 indices_converted.resize(cmd_list->IdxBuffer.Size); 134 for (int i = 0; i < cmd_list->IdxBuffer.Size; ++i) 135 indices_converted[i] = (int)cmd_list->IdxBuffer.Data[i]; 136 indices = indices_converted.Data; 137 } 138 else if (sizeof(ImDrawIdx) == 4) 139 { 140 indices = (const int*)cmd_list->IdxBuffer.Data; 141 } 142 143 // Render command lists 144 int idx_offset = 0; 145 ImVec2 clip_off = draw_data->DisplayPos; 146 for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) 147 { 148 const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; 149 if (pcmd->UserCallback) 150 { 151 // User callback, registered via ImDrawList::AddCallback() 152 // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) 153 if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) 154 ImGui_ImplAllegro5_SetupRenderState(draw_data); 155 else 156 pcmd->UserCallback(cmd_list, pcmd); 157 } 158 else 159 { 160 // Draw 161 ALLEGRO_BITMAP* texture = (ALLEGRO_BITMAP*)pcmd->GetTexID(); 162 al_set_clipping_rectangle(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y, pcmd->ClipRect.z - pcmd->ClipRect.x, pcmd->ClipRect.w - pcmd->ClipRect.y); 163 al_draw_prim(&vertices[0], g_VertexDecl, texture, idx_offset, idx_offset + pcmd->ElemCount, ALLEGRO_PRIM_TRIANGLE_LIST); 164 } 165 idx_offset += pcmd->ElemCount; 166 } 167 } 168 169 // Restore modified Allegro state 170 al_set_blender(last_blender_op, last_blender_src, last_blender_dst); 171 al_set_clipping_rectangle(last_clip_x, last_clip_y, last_clip_w, last_clip_h); 172 al_use_transform(&last_transform); 173 al_use_projection_transform(&last_projection_transform); 174 } 175 176 bool ImGui_ImplAllegro5_CreateDeviceObjects() 177 { 178 // Build texture atlas 179 ImGuiIO& io = ImGui::GetIO(); 180 unsigned char* pixels; 181 int width, height; 182 io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); 183 184 // Create texture 185 int flags = al_get_new_bitmap_flags(); 186 int fmt = al_get_new_bitmap_format(); 187 al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP | ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR); 188 al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE); 189 ALLEGRO_BITMAP* img = al_create_bitmap(width, height); 190 al_set_new_bitmap_flags(flags); 191 al_set_new_bitmap_format(fmt); 192 if (!img) 193 return false; 194 195 ALLEGRO_LOCKED_REGION* locked_img = al_lock_bitmap(img, al_get_bitmap_format(img), ALLEGRO_LOCK_WRITEONLY); 196 if (!locked_img) 197 { 198 al_destroy_bitmap(img); 199 return false; 200 } 201 memcpy(locked_img->data, pixels, sizeof(int) * width * height); 202 al_unlock_bitmap(img); 203 204 // Convert software texture to hardware texture. 205 ALLEGRO_BITMAP* cloned_img = al_clone_bitmap(img); 206 al_destroy_bitmap(img); 207 if (!cloned_img) 208 return false; 209 210 // Store our identifier 211 io.Fonts->SetTexID((void*)cloned_img); 212 g_Texture = cloned_img; 213 214 // Create an invisible mouse cursor 215 // Because al_hide_mouse_cursor() seems to mess up with the actual inputs.. 216 ALLEGRO_BITMAP* mouse_cursor = al_create_bitmap(8, 8); 217 g_MouseCursorInvisible = al_create_mouse_cursor(mouse_cursor, 0, 0); 218 al_destroy_bitmap(mouse_cursor); 219 220 return true; 221 } 222 223 void ImGui_ImplAllegro5_InvalidateDeviceObjects() 224 { 225 if (g_Texture) 226 { 227 ImGuiIO& io = ImGui::GetIO(); 228 io.Fonts->SetTexID(NULL); 229 al_destroy_bitmap(g_Texture); 230 g_Texture = NULL; 231 } 232 if (g_MouseCursorInvisible) 233 { 234 al_destroy_mouse_cursor(g_MouseCursorInvisible); 235 g_MouseCursorInvisible = NULL; 236 } 237 } 238 239 #if ALLEGRO_HAS_CLIPBOARD 240 static const char* ImGui_ImplAllegro5_GetClipboardText(void*) 241 { 242 if (g_ClipboardTextData) 243 al_free(g_ClipboardTextData); 244 g_ClipboardTextData = al_get_clipboard_text(g_Display); 245 return g_ClipboardTextData; 246 } 247 248 static void ImGui_ImplAllegro5_SetClipboardText(void*, const char* text) 249 { 250 al_set_clipboard_text(g_Display, text); 251 } 252 #endif 253 254 bool ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display) 255 { 256 g_Display = display; 257 258 // Setup backend capabilities flags 259 ImGuiIO& io = ImGui::GetIO(); 260 io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) 261 io.BackendPlatformName = io.BackendRendererName = "imgui_impl_allegro5"; 262 263 // Create custom vertex declaration. 264 // Unfortunately Allegro doesn't support 32-bit packed colors so we have to convert them to 4 floats. 265 // We still use a custom declaration to use 'ALLEGRO_PRIM_TEX_COORD' instead of 'ALLEGRO_PRIM_TEX_COORD_PIXEL' else we can't do a reliable conversion. 266 ALLEGRO_VERTEX_ELEMENT elems[] = 267 { 268 { ALLEGRO_PRIM_POSITION, ALLEGRO_PRIM_FLOAT_2, IM_OFFSETOF(ImDrawVertAllegro, pos) }, 269 { ALLEGRO_PRIM_TEX_COORD, ALLEGRO_PRIM_FLOAT_2, IM_OFFSETOF(ImDrawVertAllegro, uv) }, 270 { ALLEGRO_PRIM_COLOR_ATTR, 0, IM_OFFSETOF(ImDrawVertAllegro, col) }, 271 { 0, 0, 0 } 272 }; 273 g_VertexDecl = al_create_vertex_decl(elems, sizeof(ImDrawVertAllegro)); 274 275 io.KeyMap[ImGuiKey_Tab] = ALLEGRO_KEY_TAB; 276 io.KeyMap[ImGuiKey_LeftArrow] = ALLEGRO_KEY_LEFT; 277 io.KeyMap[ImGuiKey_RightArrow] = ALLEGRO_KEY_RIGHT; 278 io.KeyMap[ImGuiKey_UpArrow] = ALLEGRO_KEY_UP; 279 io.KeyMap[ImGuiKey_DownArrow] = ALLEGRO_KEY_DOWN; 280 io.KeyMap[ImGuiKey_PageUp] = ALLEGRO_KEY_PGUP; 281 io.KeyMap[ImGuiKey_PageDown] = ALLEGRO_KEY_PGDN; 282 io.KeyMap[ImGuiKey_Home] = ALLEGRO_KEY_HOME; 283 io.KeyMap[ImGuiKey_End] = ALLEGRO_KEY_END; 284 io.KeyMap[ImGuiKey_Insert] = ALLEGRO_KEY_INSERT; 285 io.KeyMap[ImGuiKey_Delete] = ALLEGRO_KEY_DELETE; 286 io.KeyMap[ImGuiKey_Backspace] = ALLEGRO_KEY_BACKSPACE; 287 io.KeyMap[ImGuiKey_Space] = ALLEGRO_KEY_SPACE; 288 io.KeyMap[ImGuiKey_Enter] = ALLEGRO_KEY_ENTER; 289 io.KeyMap[ImGuiKey_Escape] = ALLEGRO_KEY_ESCAPE; 290 io.KeyMap[ImGuiKey_KeyPadEnter] = ALLEGRO_KEY_PAD_ENTER; 291 io.KeyMap[ImGuiKey_A] = ALLEGRO_KEY_A; 292 io.KeyMap[ImGuiKey_C] = ALLEGRO_KEY_C; 293 io.KeyMap[ImGuiKey_V] = ALLEGRO_KEY_V; 294 io.KeyMap[ImGuiKey_X] = ALLEGRO_KEY_X; 295 io.KeyMap[ImGuiKey_Y] = ALLEGRO_KEY_Y; 296 io.KeyMap[ImGuiKey_Z] = ALLEGRO_KEY_Z; 297 io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); 298 299 #if ALLEGRO_HAS_CLIPBOARD 300 io.SetClipboardTextFn = ImGui_ImplAllegro5_SetClipboardText; 301 io.GetClipboardTextFn = ImGui_ImplAllegro5_GetClipboardText; 302 io.ClipboardUserData = NULL; 303 #endif 304 305 return true; 306 } 307 308 void ImGui_ImplAllegro5_Shutdown() 309 { 310 ImGui_ImplAllegro5_InvalidateDeviceObjects(); 311 312 g_Display = NULL; 313 g_Time = 0.0; 314 315 if (g_VertexDecl) 316 al_destroy_vertex_decl(g_VertexDecl); 317 g_VertexDecl = NULL; 318 319 if (g_ClipboardTextData) 320 al_free(g_ClipboardTextData); 321 g_ClipboardTextData = NULL; 322 } 323 324 // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. 325 // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. 326 // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. 327 // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. 328 bool ImGui_ImplAllegro5_ProcessEvent(ALLEGRO_EVENT* ev) 329 { 330 ImGuiIO& io = ImGui::GetIO(); 331 332 switch (ev->type) 333 { 334 case ALLEGRO_EVENT_MOUSE_AXES: 335 if (ev->mouse.display == g_Display) 336 { 337 io.MouseWheel += ev->mouse.dz; 338 io.MouseWheelH -= ev->mouse.dw; 339 io.MousePos = ImVec2(ev->mouse.x, ev->mouse.y); 340 } 341 return true; 342 case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN: 343 case ALLEGRO_EVENT_MOUSE_BUTTON_UP: 344 if (ev->mouse.display == g_Display && ev->mouse.button <= 5) 345 io.MouseDown[ev->mouse.button - 1] = (ev->type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN); 346 return true; 347 case ALLEGRO_EVENT_TOUCH_MOVE: 348 if (ev->touch.display == g_Display) 349 io.MousePos = ImVec2(ev->touch.x, ev->touch.y); 350 return true; 351 case ALLEGRO_EVENT_TOUCH_BEGIN: 352 case ALLEGRO_EVENT_TOUCH_END: 353 case ALLEGRO_EVENT_TOUCH_CANCEL: 354 if (ev->touch.display == g_Display && ev->touch.primary) 355 io.MouseDown[0] = (ev->type == ALLEGRO_EVENT_TOUCH_BEGIN); 356 return true; 357 case ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY: 358 if (ev->mouse.display == g_Display) 359 io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); 360 return true; 361 case ALLEGRO_EVENT_KEY_CHAR: 362 if (ev->keyboard.display == g_Display) 363 if (ev->keyboard.unichar != 0) 364 io.AddInputCharacter((unsigned int)ev->keyboard.unichar); 365 return true; 366 case ALLEGRO_EVENT_KEY_DOWN: 367 case ALLEGRO_EVENT_KEY_UP: 368 if (ev->keyboard.display == g_Display) 369 io.KeysDown[ev->keyboard.keycode] = (ev->type == ALLEGRO_EVENT_KEY_DOWN); 370 return true; 371 } 372 return false; 373 } 374 375 static void ImGui_ImplAllegro5_UpdateMouseCursor() 376 { 377 ImGuiIO& io = ImGui::GetIO(); 378 if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) 379 return; 380 381 ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); 382 if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) 383 { 384 // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor 385 al_set_mouse_cursor(g_Display, g_MouseCursorInvisible); 386 } 387 else 388 { 389 ALLEGRO_SYSTEM_MOUSE_CURSOR cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_DEFAULT; 390 switch (imgui_cursor) 391 { 392 case ImGuiMouseCursor_TextInput: cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_EDIT; break; 393 case ImGuiMouseCursor_ResizeAll: cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_MOVE; break; 394 case ImGuiMouseCursor_ResizeNS: cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_N; break; 395 case ImGuiMouseCursor_ResizeEW: cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_E; break; 396 case ImGuiMouseCursor_ResizeNESW: cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_NE; break; 397 case ImGuiMouseCursor_ResizeNWSE: cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_RESIZE_NW; break; 398 case ImGuiMouseCursor_NotAllowed: cursor_id = ALLEGRO_SYSTEM_MOUSE_CURSOR_UNAVAILABLE; break; 399 } 400 al_set_system_mouse_cursor(g_Display, cursor_id); 401 } 402 } 403 404 void ImGui_ImplAllegro5_NewFrame() 405 { 406 if (!g_Texture) 407 ImGui_ImplAllegro5_CreateDeviceObjects(); 408 409 ImGuiIO& io = ImGui::GetIO(); 410 411 // Setup display size (every frame to accommodate for window resizing) 412 int w, h; 413 w = al_get_display_width(g_Display); 414 h = al_get_display_height(g_Display); 415 io.DisplaySize = ImVec2((float)w, (float)h); 416 417 // Setup time step 418 double current_time = al_get_time(); 419 io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f); 420 g_Time = current_time; 421 422 // Setup inputs 423 ALLEGRO_KEYBOARD_STATE keys; 424 al_get_keyboard_state(&keys); 425 io.KeyCtrl = al_key_down(&keys, ALLEGRO_KEY_LCTRL) || al_key_down(&keys, ALLEGRO_KEY_RCTRL); 426 io.KeyShift = al_key_down(&keys, ALLEGRO_KEY_LSHIFT) || al_key_down(&keys, ALLEGRO_KEY_RSHIFT); 427 io.KeyAlt = al_key_down(&keys, ALLEGRO_KEY_ALT) || al_key_down(&keys, ALLEGRO_KEY_ALTGR); 428 io.KeySuper = al_key_down(&keys, ALLEGRO_KEY_LWIN) || al_key_down(&keys, ALLEGRO_KEY_RWIN); 429 430 ImGui_ImplAllegro5_UpdateMouseCursor(); 431 }