1// ImGui GLFW binding with OpenGL3 + shaders 2// In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ about ImTextureID in imgui.cpp. 3 4// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 5// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). 6// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. 7// https://github.com/ocornut/imgui 8 9#include <stdio.h> 10 11#include "imgui/imgui.h" 12#include "imgui_impl_gtk3.h" 13 14#include <gtk/gtk.h> 15 16#define ARRAY_SIZE(arg) (sizeof(arg) / sizeof((arg)[0])) 17 18#define EVENT_MASK \ 19 ((GdkEventMask) \ 20 (GDK_STRUCTURE_MASK | \ 21 GDK_FOCUS_CHANGE_MASK | \ 22 GDK_EXPOSURE_MASK | \ 23 GDK_PROPERTY_CHANGE_MASK | \ 24 GDK_ENTER_NOTIFY_MASK | \ 25 GDK_LEAVE_NOTIFY_MASK | \ 26 GDK_KEY_PRESS_MASK | \ 27 GDK_KEY_RELEASE_MASK | \ 28 GDK_BUTTON_PRESS_MASK | \ 29 GDK_BUTTON_RELEASE_MASK | \ 30 GDK_POINTER_MOTION_MASK | \ 31 GDK_SMOOTH_SCROLL_MASK | \ 32 GDK_SCROLL_MASK)) 33 34// Data 35static GtkWidget* g_GtkGlArea = NULL; 36static guint64 g_Time = 0; 37static bool g_MousePressed[5] = { false, false, false, false, false }; 38static ImVec2 g_MousePosition = ImVec2(-1, -1); 39static float g_MouseWheel = 0.0f; 40static int g_NumRedraws = 0; 41static guint g_RedrawTimeout = 0; 42 43static const char* ImGui_ImplGtk3_GetClipboardText(void* user_data) 44{ 45 static char *last_clipboard = NULL; 46 47 g_clear_pointer(&last_clipboard, g_free); 48 last_clipboard = gtk_clipboard_wait_for_text(GTK_CLIPBOARD(user_data)); 49 return last_clipboard; 50} 51 52static void ImGui_ImplGtk3_SetClipboardText(void* user_data, const char* text) 53{ 54 gtk_clipboard_set_text(GTK_CLIPBOARD(user_data), text, -1); 55} 56 57void ImGui_ImplGtk3_HandleEvent(GdkEvent *event) 58{ 59 ImGuiIO& io = ImGui::GetIO(); 60 61 GdkEventType type = gdk_event_get_event_type(event); 62 switch (type) 63 { 64 case GDK_MOTION_NOTIFY: 65 { 66 gdouble x = 0.0f, y = 0.0f; 67 if (gdk_event_get_coords(event, &x, &y)) 68 g_MousePosition = ImVec2(x, y); 69 break; 70 } 71 case GDK_BUTTON_PRESS: 72 case GDK_BUTTON_RELEASE: 73 { 74 guint button = 0; 75 if (gdk_event_get_button(event, &button) && button > 0 && button <= 5) 76 { 77 if (type == GDK_BUTTON_PRESS) 78 g_MousePressed[button - 1] = true; 79 } 80 break; 81 } 82 case GDK_SCROLL: 83 { 84 gdouble x, y; 85 if (gdk_event_get_scroll_deltas(event, &x, &y)) 86 g_MouseWheel = -y; 87 break; 88 } 89 case GDK_KEY_PRESS: 90 case GDK_KEY_RELEASE: 91 { 92 GdkEventKey *e = (GdkEventKey *) event; 93 94 static const struct 95 { 96 enum ImGuiKey_ imgui; 97 guint gdk; 98 } gdk_key_to_imgui_key[] = 99 { 100 { ImGuiKey_Tab, GDK_KEY_Tab }, 101 { ImGuiKey_Tab, GDK_KEY_ISO_Left_Tab }, 102 { ImGuiKey_LeftArrow, GDK_KEY_Left }, 103 { ImGuiKey_RightArrow, GDK_KEY_Right }, 104 { ImGuiKey_UpArrow, GDK_KEY_Up }, 105 { ImGuiKey_DownArrow, GDK_KEY_Down }, 106 { ImGuiKey_PageUp, GDK_KEY_Page_Up }, 107 { ImGuiKey_PageDown, GDK_KEY_Page_Down }, 108 { ImGuiKey_Home, GDK_KEY_Home }, 109 { ImGuiKey_End, GDK_KEY_End }, 110 { ImGuiKey_Delete, GDK_KEY_Delete }, 111 { ImGuiKey_Backspace, GDK_KEY_BackSpace }, 112 { ImGuiKey_Enter, GDK_KEY_Return }, 113 { ImGuiKey_Escape, GDK_KEY_Escape }, 114 { ImGuiKey_A, GDK_KEY_a }, 115 { ImGuiKey_C, GDK_KEY_c }, 116 { ImGuiKey_V, GDK_KEY_v }, 117 { ImGuiKey_X, GDK_KEY_x }, 118 { ImGuiKey_Y, GDK_KEY_y }, 119 { ImGuiKey_Z, GDK_KEY_z }, 120 }; 121 for (unsigned i = 0; i < ARRAY_SIZE(gdk_key_to_imgui_key); i++) 122 { 123 if (e->keyval == gdk_key_to_imgui_key[i].gdk) 124 io.KeysDown[gdk_key_to_imgui_key[i].imgui] = type == GDK_KEY_PRESS; 125 } 126 gunichar c = gdk_keyval_to_unicode(e->keyval); 127 if (g_unichar_isprint(c) && ImGuiKey_COUNT + c < ARRAY_SIZE(io.KeysDown)) 128 io.KeysDown[ImGuiKey_COUNT + c] = type == GDK_KEY_PRESS; 129 130 if (type == GDK_KEY_PRESS && e->string) 131 io.AddInputCharactersUTF8(e->string); 132 133 struct { 134 bool *var; 135 GdkModifierType modifier; 136 guint keyvals[3]; 137 } mods[] = { 138 { &io.KeyCtrl, GDK_CONTROL_MASK, 139 { GDK_KEY_Control_L, GDK_KEY_Control_R, 0 }, }, 140 { &io.KeyShift, GDK_SHIFT_MASK, 141 { GDK_KEY_Shift_L, GDK_KEY_Shift_R, 0 }, }, 142 { &io.KeyAlt, GDK_MOD1_MASK, 143 { GDK_KEY_Alt_L, GDK_KEY_Alt_R, 0 }, }, 144 { &io.KeySuper, GDK_SUPER_MASK, 145 { GDK_KEY_Super_L, GDK_KEY_Super_R, 0 }, } 146 }; 147 for (unsigned i = 0; i < ARRAY_SIZE(mods); i++) 148 { 149 *mods[i].var = (mods[i].modifier & e->state); 150 151 bool match = false; 152 for (int j = 0; mods[i].keyvals[j] != 0; j++) 153 if (e->keyval == mods[i].keyvals[j]) 154 match = true; 155 156 if (match) 157 *mods[i].var = type == GDK_KEY_PRESS; 158 } 159 break; 160 } 161 default: 162 break; 163 } 164 165 // We trigger 2 subsequent redraws for each event because of the 166 // way some ImGui widgets work. For example a Popup menu will only 167 // appear a frame after a click happened. 168 g_NumRedraws = 2; 169 170 gtk_widget_queue_draw(g_GtkGlArea); 171} 172 173static gboolean handle_gdk_event(GtkWidget *widget, GdkEvent *event, void *data) 174{ 175 ImGui_ImplGtk3_HandleEvent(event); 176 return TRUE; 177} 178 179bool ImGui_ImplGtk3_Init(GtkWidget* gl_area, bool install_callbacks) 180{ 181 g_clear_pointer(&g_GtkGlArea, g_object_unref); 182 183 g_GtkGlArea = GTK_WIDGET(g_object_ref(gl_area)); 184 gtk_widget_realize(g_GtkGlArea); 185 gtk_widget_set_can_focus(g_GtkGlArea, TRUE); 186 gtk_widget_grab_focus(g_GtkGlArea); 187 188 if (install_callbacks) { 189 gtk_widget_add_events(g_GtkGlArea, EVENT_MASK); 190 g_signal_connect(g_GtkGlArea, "event", G_CALLBACK(handle_gdk_event), NULL); 191 } 192 193 ImGuiIO& io = ImGui::GetIO(); 194 for (int i = 0; i < ImGuiKey_COUNT; i++) 195 { 196 io.KeyMap[i] = i; 197 } 198 199 io.SetClipboardTextFn = ImGui_ImplGtk3_SetClipboardText; 200 io.GetClipboardTextFn = ImGui_ImplGtk3_GetClipboardText; 201 io.ClipboardUserData = gtk_widget_get_clipboard(g_GtkGlArea, 202 GDK_SELECTION_CLIPBOARD); 203 204 return true; 205} 206 207void ImGui_ImplGtk3_Shutdown() 208{ 209 g_clear_pointer(&g_GtkGlArea, g_object_unref); 210 if (g_RedrawTimeout) 211 g_source_remove(g_RedrawTimeout); 212} 213 214static gboolean timeout_callback(gpointer data) 215{ 216 gtk_widget_queue_draw(g_GtkGlArea); 217 g_RedrawTimeout = 0; 218 return FALSE; 219} 220 221static void kick_timeout_redraw(float timeout) 222{ 223 if (g_RedrawTimeout) 224 return; 225 g_RedrawTimeout = g_timeout_add(timeout * 1000, timeout_callback, NULL); 226} 227 228void ImGui_ImplGtk3_NewFrame() 229{ 230 bool next_redraw = false; 231 if (g_NumRedraws > 0) 232 { 233 gtk_widget_queue_draw(g_GtkGlArea); 234 g_NumRedraws--; 235 next_redraw = true; 236 } 237 238 ImGuiIO& io = ImGui::GetIO(); 239 240 // Setup display size (every frame to accommodate for window resizing) 241 io.DisplaySize = ImVec2((float)gtk_widget_get_allocated_width(g_GtkGlArea), 242 (float)gtk_widget_get_allocated_height(g_GtkGlArea)); 243 int scale_factor = gtk_widget_get_scale_factor(g_GtkGlArea); 244 io.DisplayFramebufferScale = ImVec2(scale_factor, scale_factor); 245 246 // Setup time step 247 guint64 current_time = g_get_monotonic_time(); 248 io.DeltaTime = g_Time > 0 ? ((float)(current_time - g_Time) / 1000000) : (float)(1.0f/60.0f); 249 g_Time = current_time; 250 251 // Setup inputs 252 if (gtk_widget_has_focus(g_GtkGlArea)) 253 { 254 io.MousePos = g_MousePosition; // Mouse position in screen coordinates (set to -1,-1 if no mouse / on another screen, etc.) 255 } 256 else 257 { 258 io.MousePos = ImVec2(-1,-1); 259 } 260 261 GdkWindow *window = gtk_widget_get_window(g_GtkGlArea); 262 GdkDevice *pointer = gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_display_get_default())); 263 GdkModifierType modifiers; 264 gdk_device_get_state(pointer, window, NULL, &modifiers); 265 266 for (int i = 0; i < 3; i++) 267 { 268 io.MouseDown[i] = g_MousePressed[i] || (modifiers & (GDK_BUTTON1_MASK << i)) != 0; 269 g_MousePressed[i] = false; 270 } 271 272 io.MouseWheel = g_MouseWheel; 273 g_MouseWheel = 0.0f; 274 275 // Hide OS mouse cursor if ImGui is drawing it 276 GdkDisplay *display = gdk_window_get_display(window); 277 GdkCursor *cursor = 278 gdk_cursor_new_from_name(display, io.MouseDrawCursor ? "none" : "default"); 279 gdk_window_set_cursor(window, cursor); 280 g_object_unref(cursor); 281 282 if (!next_redraw && io.WantTextInput) 283 kick_timeout_redraw(0.2); 284} 285