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