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
35 static GtkWidget* g_GtkGlArea = NULL;
36 static guint64 g_Time = 0;
37 static bool g_MousePressed[5] = { false, false, false, false, false };
38 static ImVec2 g_MousePosition = ImVec2(-1, -1);
39 static float g_MouseWheel = 0.0f;
40 static int g_NumRedraws = 0;
41 static guint g_RedrawTimeout = 0;
42
ImGui_ImplGtk3_GetClipboardText(void * user_data)43 static 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
ImGui_ImplGtk3_SetClipboardText(void * user_data,const char * text)52 static void ImGui_ImplGtk3_SetClipboardText(void* user_data, const char* text)
53 {
54 gtk_clipboard_set_text(GTK_CLIPBOARD(user_data), text, -1);
55 }
56
ImGui_ImplGtk3_HandleEvent(GdkEvent * event)57 void 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
handle_gdk_event(GtkWidget * widget,GdkEvent * event,void * data)173 static gboolean handle_gdk_event(GtkWidget *widget, GdkEvent *event, void *data)
174 {
175 ImGui_ImplGtk3_HandleEvent(event);
176 return TRUE;
177 }
178
ImGui_ImplGtk3_Init(GtkWidget * gl_area,bool install_callbacks)179 bool 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
ImGui_ImplGtk3_Shutdown()207 void 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
timeout_callback(gpointer data)214 static gboolean timeout_callback(gpointer data)
215 {
216 gtk_widget_queue_draw(g_GtkGlArea);
217 g_RedrawTimeout = 0;
218 return FALSE;
219 }
220
kick_timeout_redraw(float timeout)221 static 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
ImGui_ImplGtk3_NewFrame()228 void 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